Skip to content

Commit 576d560

Browse files
seeksortSleeplessByte
authored andcommitted
add scale-generator exercise (#754)
* add scale-generator exercise
1 parent 188908f commit 576d560

File tree

8 files changed

+313
-0
lines changed

8 files changed

+313
-0
lines changed

config.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,19 @@
990990
"games"
991991
]
992992
},
993+
{
994+
"slug": "scale-generator",
995+
"uuid": "b9c586e8-998b-4f5d-ab98-a08be29a9f19",
996+
"core": false,
997+
"unlocked_by": "pangram",
998+
"difficulty": 3,
999+
"topics": [
1000+
"loops",
1001+
"pattern_recognition",
1002+
"strings",
1003+
"arrays"
1004+
]
1005+
},
9931006
{
9941007
"slug": "connect",
9951008
"uuid": "2fa2c262-77ae-409b-bfd8-1d643faae772",

exercises/scale-generator/.eslintrc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"root": true,
3+
"parser": "babel-eslint",
4+
"parserOptions": {
5+
"ecmaVersion": 7,
6+
"sourceType": "module"
7+
},
8+
"env": {
9+
"es6": true,
10+
"node": true,
11+
"jest": true
12+
},
13+
"extends": [
14+
"eslint:recommended",
15+
"plugin:import/errors",
16+
"plugin:import/warnings"
17+
],
18+
"rules": {
19+
"linebreak-style": "off",
20+
21+
"import/extensions": "off",
22+
"import/no-default-export": "off",
23+
"import/no-unresolved": "off",
24+
"import/prefer-default-export": "off"
25+
}
26+
}

exercises/scale-generator/README.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Scale Generator
2+
3+
Given a tonic, or starting note, and a set of intervals, generate
4+
the musical scale starting with the tonic and following the
5+
specified interval pattern.
6+
7+
Scales in Western music are based on the chromatic (12-note) scale. This
8+
scale can be expressed as the following group of pitches:
9+
10+
A, A#, B, C, C#, D, D#, E, F, F#, G, G#
11+
12+
A given sharp note (indicated by a #) can also be expressed as the flat
13+
of the note above it (indicated by a b) so the chromatic scale can also be
14+
written like this:
15+
16+
A, Bb, B, C, Db, D, Eb, E, F, Gb, G, Ab
17+
18+
The major and minor scale and modes are subsets of this twelve-pitch
19+
collection. They have seven pitches, and are called diatonic scales.
20+
The collection of notes in these scales is written with either sharps or
21+
flats, depending on the tonic. Here is a list of which are which:
22+
23+
No Sharps or Flats:
24+
C major
25+
a minor
26+
27+
Use Sharps:
28+
G, D, A, E, B, F# major
29+
e, b, f#, c#, g#, d# minor
30+
31+
Use Flats:
32+
F, Bb, Eb, Ab, Db, Gb major
33+
d, g, c, f, bb, eb minor
34+
35+
The diatonic scales, and all other scales that derive from the
36+
chromatic scale, are built upon intervals. An interval is the space
37+
between two pitches.
38+
39+
The simplest interval is between two adjacent notes, and is called a
40+
"half step", or "minor second" (sometimes written as a lower-case "m").
41+
The interval between two notes that have an interceding note is called
42+
a "whole step" or "major second" (written as an upper-case "M"). The
43+
diatonic scales are built using only these two intervals between
44+
adjacent notes.
45+
46+
Non-diatonic scales can contain other intervals. An "augmented first"
47+
interval, written "A", has two interceding notes (e.g., from A to C or
48+
Db to E). There are also smaller and larger intervals, but they will not
49+
figure into this exercise.
50+
51+
## Setup
52+
53+
Go through the setup instructions for Javascript to install the necessary
54+
dependencies:
55+
56+
[https://exercism.io/tracks/javascript/installation](https://exercism.io/tracks/javascript/installation)
57+
58+
## Requirements
59+
60+
Install assignment dependencies:
61+
62+
```bash
63+
$ npm install
64+
```
65+
66+
## Making the test suite pass
67+
68+
Execute the tests with:
69+
70+
```bash
71+
$ npm test
72+
```
73+
74+
In the test suites all tests but the first have been skipped.
75+
76+
Once you get a test passing, you can enable the next one by changing `xtest` to
77+
`test`.
78+
79+
## Submitting Incomplete Solutions
80+
81+
It's possible to submit an incomplete solution so you can see how others have
82+
completed the exercise.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module.exports = {
2+
presets: [
3+
[
4+
'@babel/env',
5+
{
6+
targets: {
7+
node: 'current',
8+
},
9+
useBuiltIns: false,
10+
},
11+
12+
],
13+
],
14+
};

exercises/scale-generator/example.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
export class Scale {
2+
constructor(tonic) {
3+
this.INTERVAL_STEPS = ['m', 'M', 'A']
4+
this.SHARPS_SCALE = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#']
5+
this.FLATS_SCALE = ['A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab']
6+
this.USE_FLATS = ['F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'd', 'g', 'c', 'f', 'bb', 'eb']
7+
8+
this.tonic = tonic.slice(0, 1).toUpperCase() + tonic.slice(1);
9+
// note use of original tonic argument
10+
this.chromaticScale = this.USE_FLATS.includes(tonic) ? this.FLATS_SCALE : this.SHARPS_SCALE
11+
}
12+
13+
chromatic() {
14+
return this.reorderChromaticScale()
15+
}
16+
17+
interval(intervals) {
18+
const scale = this.reorderChromaticScale()
19+
const result = []
20+
let currentIndex = 0
21+
22+
for (const step of intervals) {
23+
result.push(scale[currentIndex])
24+
currentIndex = currentIndex + (this.INTERVAL_STEPS.indexOf(step) + 1)
25+
}
26+
return result
27+
}
28+
29+
reorderChromaticScale() {
30+
const tonicIndex = this.chromaticScale.indexOf(this.tonic)
31+
return this.chromaticScale.slice(tonicIndex).concat(this.chromaticScale.slice(0, tonicIndex))
32+
}
33+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "exercism-javascript",
3+
"description": "Exercism exercises in Javascript.",
4+
"author": "Katrina Owen",
5+
"private": true,
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/exercism/javascript"
9+
},
10+
"devDependencies": {
11+
"@babel/cli": "^7.5.5",
12+
"@babel/core": "^7.5.5",
13+
"@babel/preset-env": "^7.5.5",
14+
"@types/jest": "^24.0.16",
15+
"@types/node": "^12.6.8",
16+
"babel-eslint": "^10.0.2",
17+
"babel-jest": "^24.8.0",
18+
"eslint": "^6.1.0",
19+
"eslint-plugin-import": "^2.18.2",
20+
"jest": "^24.8.0"
21+
},
22+
"jest": {
23+
"modulePathIgnorePatterns": [
24+
"package.json"
25+
]
26+
},
27+
"scripts": {
28+
"test": "jest --no-cache ./*",
29+
"watch": "jest --no-cache --watch ./*",
30+
"lint": "eslint .",
31+
"lint-test": "eslint . && jest --no-cache ./* "
32+
},
33+
"license": "MIT",
34+
"dependencies": {}
35+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//
2+
// This is only a SKELETON file for the 'Scale Generator' exercise. It's been provided as a
3+
// convenience to get you started writing code faster.
4+
//
5+
6+
export class Scale {
7+
constructor(tonic) {
8+
throw new Error("Remove this statement and implement this function");
9+
}
10+
11+
chromatic() {
12+
throw new Error("Remove this statement and implement this function");
13+
}
14+
15+
interval(intervals) {
16+
throw new Error("Remove this statement and implement this function");
17+
}
18+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import { Scale } from './scale-generator'
2+
3+
describe('ScaleGenerator', () => {
4+
describe('Chromatic scales', () => {
5+
test('Chromatic scale with sharps', () => {
6+
const expected = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']
7+
expect(new Scale('C').chromatic()).toEqual(expected)
8+
})
9+
10+
xtest('Chromatic scale with flats', () => {
11+
const expected = ['F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E']
12+
expect(new Scale('F').chromatic()).toEqual(expected)
13+
})
14+
})
15+
16+
describe('Scales with specified intervals', () => {
17+
xtest('Simple major scale', () => {
18+
const expected = ['C', 'D', 'E', 'F', 'G', 'A', 'B']
19+
expect(new Scale('C').interval('MMmMMMm')).toEqual(expected)
20+
})
21+
22+
xtest('Major scale with sharps', () => {
23+
const expected = ['G', 'A', 'B', 'C', 'D', 'E', 'F#']
24+
expect(new Scale('G').interval('MMmMMMm')).toEqual(expected)
25+
})
26+
27+
xtest('Major scale with flats', () => {
28+
const expected = ['F', 'G', 'A', 'Bb', 'C', 'D', 'E']
29+
expect(new Scale('F').interval('MMmMMMm')).toEqual(expected)
30+
})
31+
32+
xtest('Minor scale with sharps', () => {
33+
const expected = ['F#', 'G#', 'A', 'B', 'C#', 'D', 'E']
34+
expect(new Scale('f#').interval('MmMMmMM')).toEqual(expected)
35+
})
36+
37+
xtest('Minor scale with flats', () => {
38+
const expected = ['Bb', 'C', 'Db', 'Eb', 'F', 'Gb', 'Ab']
39+
expect(new Scale('bb').interval('MmMMmMM')).toEqual(expected)
40+
})
41+
42+
xtest('Dorian mode', () => {
43+
const expected = ['D', 'E', 'F', 'G', 'A', 'B', 'C']
44+
expect(new Scale('d').interval('MmMMMmM')).toEqual(expected)
45+
})
46+
47+
xtest('Mixolydian mode', () => {
48+
const expected = ['Eb', 'F', 'G', 'Ab', 'Bb', 'C', 'Db']
49+
expect(new Scale('Eb').interval('MMmMMmM')).toEqual(expected)
50+
})
51+
52+
xtest('Lydian mode', () => {
53+
const expected = ['A', 'B', 'C#', 'D#', 'E', 'F#', 'G#']
54+
expect(new Scale('a').interval('MMMmMMm')).toEqual(expected)
55+
})
56+
57+
xtest('Phrygian mode', () => {
58+
const expected = ['E', 'F', 'G', 'A', 'B', 'C', 'D']
59+
expect(new Scale('e').interval('mMMMmMM')).toEqual(expected)
60+
})
61+
62+
xtest('Locrian mode', () => {
63+
const expected = ['G', 'Ab', 'Bb', 'C', 'Db', 'Eb', 'F']
64+
expect(new Scale('g').interval('mMMmMMM')).toEqual(expected)
65+
})
66+
67+
xtest('Harmonic minor', () => {
68+
const expected = ['D', 'E', 'F', 'G', 'A', 'Bb', 'Db']
69+
expect(new Scale('d').interval('MmMMmAm')).toEqual(expected)
70+
})
71+
72+
xtest('Octatonic', () => {
73+
const expected = ['C', 'D', 'D#', 'F', 'F#', 'G#', 'A', 'B']
74+
expect(new Scale('C').interval('MmMmMmMm')).toEqual(expected)
75+
})
76+
77+
xtest('Hexatonic', () => {
78+
const expected = ['Db', 'Eb', 'F', 'G', 'A', 'B']
79+
expect(new Scale('Db').interval('MMMMMM')).toEqual(expected)
80+
})
81+
82+
xtest('Pentatonic', () => {
83+
const expected = ['A', 'B', 'C#', 'E', 'F#']
84+
expect(new Scale('A').interval('MMAMA')).toEqual(expected)
85+
})
86+
87+
xtest('Enigmatic', () => {
88+
const expected = ['G', 'G#', 'B', 'C#', 'D#', 'F', 'F#']
89+
expect(new Scale('G').interval('mAMMMmm')).toEqual(expected)
90+
})
91+
})
92+
})

0 commit comments

Comments
 (0)