Skip to content

Commit 4c4a76f

Browse files
authored
Add scale-generator exercise (#50)
1 parent 0431038 commit 4c4a76f

File tree

7 files changed

+372
-0
lines changed

7 files changed

+372
-0
lines changed

config.json

+8
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,14 @@
171171
"prerequisites": [],
172172
"difficulty": 3
173173
},
174+
{
175+
"slug": "scale-generator",
176+
"name": "Scale Generator",
177+
"uuid": "c9d72016-4314-4086-8642-1e9e006e19ab",
178+
"practices": [],
179+
"prerequisites": [],
180+
"difficulty": 3
181+
},
174182
{
175183
"slug": "spiral-matrix",
176184
"name": "Spiral Matrix",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Instructions
2+
3+
## Chromatic Scales
4+
5+
Scales in Western music are based on the chromatic (12-note) scale.
6+
This scale can be expressed as the following group of pitches:
7+
8+
> A, A♯, B, C, C♯, D, D♯, E, F, F♯, G, G♯
9+
10+
A given sharp note (indicated by a ♯) can also be expressed as the flat of the note above it (indicated by a ♭) so the chromatic scale can also be written like this:
11+
12+
> A, B♭, B, C, D♭, D, E♭, E, F, G♭, G, A♭
13+
14+
The major and minor scale and modes are subsets of this twelve-pitch collection.
15+
They have seven pitches, and are called diatonic scales.
16+
The collection of notes in these scales is written with either sharps or flats, depending on the tonic (starting note).
17+
Here is a table indicating whether the flat expression or sharp expression of the scale would be used for a given tonic:
18+
19+
| Key Signature | Major | Minor |
20+
| ------------- | --------------------- | -------------------- |
21+
| Natural | C | a |
22+
| Sharp | G, D, A, E, B, F♯ | e, b, f♯, c♯, g♯, d♯ |
23+
| Flat | F, B♭, E♭, A♭, D♭, G♭ | d, g, c, f, b♭, e♭ |
24+
25+
Note that by common music theory convention the natural notes "C" and "a" follow the sharps scale when ascending and the flats scale when descending.
26+
For the scope of this exercise the scale is only ascending.
27+
28+
### Task
29+
30+
Given a tonic, generate the 12 note chromatic scale starting with the tonic.
31+
32+
- Shift the base scale appropriately so that all 12 notes are returned starting with the given tonic.
33+
- For the given tonic, determine if the scale is to be returned with flats or sharps.
34+
- Return all notes in uppercase letters (except for the `b` for flats) irrespective of the casing of the given tonic.
35+
36+
## Diatonic Scales
37+
38+
The diatonic scales, and all other scales that derive from the chromatic scale, are built upon intervals.
39+
An interval is the space between two pitches.
40+
41+
The simplest interval is between two adjacent notes, and is called a "half step", or "minor second" (sometimes written as a lower-case "m").
42+
The interval between two notes that have an interceding note is called a "whole step" or "major second" (written as an upper-case "M").
43+
The diatonic scales are built using only these two intervals between adjacent notes.
44+
45+
Non-diatonic scales can contain other intervals.
46+
An "augmented second" interval, written "A", has two interceding notes (e.g., from A to C or D♭ to E) or a "whole step" plus a "half step".
47+
There are also smaller and larger intervals, but they will not figure into this exercise.
48+
49+
### Task
50+
51+
Given a tonic and a set of intervals, generate the musical scale starting with the tonic and following the specified interval pattern.
52+
53+
This is similar to generating chromatic scales except that instead of returning 12 notes, you will return N+1 notes for N intervals.
54+
The first note is always the given tonic.
55+
Then, for each interval in the pattern, the next note is determined by starting from the previous note and skipping the number of notes indicated by the interval.
56+
57+
For example, starting with G and using the seven intervals MMmMMMm, there would be the following eight notes:
58+
59+
| Note | Reason |
60+
| ---- | ------------------------------------------------- |
61+
| G | Tonic |
62+
| A | M indicates a whole step from G, skipping G♯ |
63+
| B | M indicates a whole step from A, skipping A♯ |
64+
| C | m indicates a half step from B, skipping nothing |
65+
| D | M indicates a whole step from C, skipping C♯ |
66+
| E | M indicates a whole step from D, skipping D♯ |
67+
| F♯ | M indicates a whole step from E, skipping F |
68+
| G | m indicates a half step from F♯, skipping nothing |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"authors": [
3+
"pfertyk"
4+
],
5+
"files": {
6+
"solution": [
7+
"scale_generator.gd"
8+
],
9+
"test": [
10+
"scale_generator_test.gd"
11+
],
12+
"example": [
13+
".meta/example.gd"
14+
]
15+
},
16+
"blurb": "Generate musical scales, given a starting note and a set of intervals."
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const ASCENDING_INTERVALS = ['m', 'M', 'A']
2+
const CHROMATIC_SCALE = ['A', 'A#', 'B', 'C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#']
3+
const FLAT_CHROMATIC_SCALE = ['A', 'Bb', 'B', 'C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab']
4+
const FLAT_KEYS = ['F', 'Bb', 'Eb', 'Ab', 'Db', 'Gb', 'd', 'g', 'c', 'f', 'bb', 'eb']
5+
6+
@export var tonic : String
7+
8+
9+
func chromatic():
10+
return _reorder_chromatic_scale()
11+
12+
13+
func interval(intervals):
14+
var last_index = 0
15+
var pitches = []
16+
var scale = _reorder_chromatic_scale()
17+
18+
for interval in intervals:
19+
pitches.append(scale[last_index])
20+
last_index += ASCENDING_INTERVALS.find(interval) + 1
21+
22+
pitches.append(tonic.capitalize())
23+
24+
return pitches
25+
26+
27+
func _reorder_chromatic_scale():
28+
var chromatic_scale = (FLAT_CHROMATIC_SCALE if tonic in FLAT_KEYS else CHROMATIC_SCALE)
29+
var index = chromatic_scale.find(tonic.capitalize())
30+
return chromatic_scale.slice(index) + chromatic_scale.slice(0, index)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[10ea7b14-8a49-40be-ac55-7c62b55f9b47]
13+
description = "Chromatic scales -> Chromatic scale with sharps"
14+
15+
[af8381de-9a72-4efd-823a-48374dbfe76f]
16+
description = "Chromatic scales -> Chromatic scale with flats"
17+
18+
[7195998a-7be7-40c9-8877-a1d7949e061b]
19+
description = "Scales with specified intervals -> Simple major scale"
20+
reimplements = "6f5b1410-1dd7-4c6c-b410-6b7e986f6f1e"
21+
22+
[fe853b97-1878-4090-b218-4029246abb91]
23+
description = "Scales with specified intervals -> Major scale with sharps"
24+
reimplements = "13a92f89-a83e-40b5-b9d4-01136931ba02"
25+
26+
[d60cb414-cc02-4fcb-ad7a-fc7ef0a9eead]
27+
description = "Scales with specified intervals -> Major scale with flats"
28+
reimplements = "aa3320f6-a761-49a1-bcf6-978e0c81080a"
29+
30+
[77dab9b3-1bbc-4f9a-afd8-06da693bcc67]
31+
description = "Scales with specified intervals -> Minor scale with sharps"
32+
reimplements = "63daeb2f-c3f9-4c45-92be-5bf97f61ff94"
33+
34+
[5fa1728f-5b66-4b43-9b7c-84359b7069d4]
35+
description = "Scales with specified intervals -> Minor scale with flats"
36+
reimplements = "616594d0-9c48-4301-949e-af1d4fad16fd"
37+
38+
[f3f1c353-8f7b-4a85-a5b5-ae06c2645823]
39+
description = "Scales with specified intervals -> Dorian mode"
40+
reimplements = "390bd12c-5ac7-4ec7-bdde-4e58d5c78b0a"
41+
42+
[5fe14e5a-3ddc-4202-a158-2c1158beb5d0]
43+
description = "Scales with specified intervals -> Mixolydian mode"
44+
reimplements = "846d0862-0f3e-4f3b-8a2d-9cc74f017848"
45+
46+
[e6307799-b7f6-43fc-a6d8-a4834d6e2bdb]
47+
description = "Scales with specified intervals -> Lydian mode"
48+
reimplements = "7d49a8bb-b5f7-46ad-a207-83bd5032291a"
49+
50+
[7c4a95cd-ecf4-448d-99bc-dbbca51856e0]
51+
description = "Scales with specified intervals -> Phrygian mode"
52+
reimplements = "a4e4dac5-1891-4160-a19f-bb06d653d4d0"
53+
54+
[f476f9c9-5a13-473d-bb6c-f884cf8fd9f2]
55+
description = "Scales with specified intervals -> Locrian mode"
56+
reimplements = "ef3650af-90f8-4ad9-9ef6-fdbeae07dcaa"
57+
58+
[87fdbcca-d3dd-46d5-9c56-ec79e25b19f4]
59+
description = "Scales with specified intervals -> Harmonic minor"
60+
reimplements = "70517400-12b7-4530-b861-fa940ae69ee8"
61+
62+
[b28ecc18-88db-4fd5-a973-cfe6361e2b24]
63+
description = "Scales with specified intervals -> Octatonic"
64+
reimplements = "37114c0b-c54d-45da-9f4b-3848201470b0"
65+
66+
[a1c7d333-6fb3-4f3b-9178-8a0cbe043134]
67+
description = "Scales with specified intervals -> Hexatonic"
68+
reimplements = "496466e7-aa45-4bbd-a64d-f41030feed9c"
69+
70+
[9acfd139-0781-4926-8273-66a478c3b287]
71+
description = "Scales with specified intervals -> Pentatonic"
72+
reimplements = "bee5d9ec-e226-47b6-b62b-847a9241f3cc"
73+
74+
[31c933ca-2251-4a5b-92dd-9d5831bc84ad]
75+
description = "Scales with specified intervals -> Enigmatic"
76+
reimplements = "dbee06a6-7535-4ab7-98e8-d8a36c8402d1"
77+
78+
[6f5b1410-1dd7-4c6c-b410-6b7e986f6f1e]
79+
description = "Scales with specified intervals -> Simple major scale"
80+
include = false
81+
82+
[13a92f89-a83e-40b5-b9d4-01136931ba02]
83+
description = "Scales with specified intervals -> Major scale with sharps"
84+
include = false
85+
86+
[aa3320f6-a761-49a1-bcf6-978e0c81080a]
87+
description = "Scales with specified intervals -> Major scale with flats"
88+
include = false
89+
90+
[63daeb2f-c3f9-4c45-92be-5bf97f61ff94]
91+
description = "Scales with specified intervals -> Minor scale with sharps"
92+
include = false
93+
94+
[616594d0-9c48-4301-949e-af1d4fad16fd]
95+
description = "Scales with specified intervals -> Minor scale with flats"
96+
include = false
97+
98+
[390bd12c-5ac7-4ec7-bdde-4e58d5c78b0a]
99+
description = "Scales with specified intervals -> Dorian mode"
100+
include = false
101+
102+
[846d0862-0f3e-4f3b-8a2d-9cc74f017848]
103+
description = "Scales with specified intervals -> Mixolydian mode"
104+
include = false
105+
106+
[7d49a8bb-b5f7-46ad-a207-83bd5032291a]
107+
description = "Scales with specified intervals -> Lydian mode"
108+
include = false
109+
110+
[a4e4dac5-1891-4160-a19f-bb06d653d4d0]
111+
description = "Scales with specified intervals -> Phrygian mode"
112+
include = false
113+
114+
[ef3650af-90f8-4ad9-9ef6-fdbeae07dcaa]
115+
description = "Scales with specified intervals -> Locrian mode"
116+
include = false
117+
118+
[70517400-12b7-4530-b861-fa940ae69ee8]
119+
description = "Scales with specified intervals -> Harmonic minor"
120+
include = false
121+
122+
[37114c0b-c54d-45da-9f4b-3848201470b0]
123+
description = "Scales with specified intervals -> Octatonic"
124+
include = false
125+
126+
[496466e7-aa45-4bbd-a64d-f41030feed9c]
127+
description = "Scales with specified intervals -> Hexatonic"
128+
include = false
129+
130+
[bee5d9ec-e226-47b6-b62b-847a9241f3cc]
131+
description = "Scales with specified intervals -> Pentatonic"
132+
include = false
133+
134+
[dbee06a6-7535-4ab7-98e8-d8a36c8402d1]
135+
description = "Scales with specified intervals -> Enigmatic"
136+
include = false
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@export var tonic : String
2+
3+
4+
func chromatic(self):
5+
pass
6+
7+
8+
func interval(self, intervals):
9+
pass
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
# Test chromatic scales
2+
func test_chromatic_scale_with_sharps(scale):
3+
scale.tonic = "C"
4+
var expected = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
5+
return [scale.chromatic(), expected]
6+
7+
8+
func test_chromatic_scale_with_flats(scale):
9+
scale.tonic = "F"
10+
var expected = ["F", "Gb", "G", "Ab", "A", "Bb", "B", "C", "Db", "D", "Eb", "E"]
11+
return [scale.chromatic(), expected]
12+
13+
14+
# Test scales with specified intervals
15+
func test_simple_major_scale(scale):
16+
scale.tonic = "C"
17+
var expected = ["C", "D", "E", "F", "G", "A", "B", "C"]
18+
return [scale.interval("MMmMMMm"), expected]
19+
20+
21+
func test_major_scale_with_sharps(scale):
22+
scale.tonic = "G"
23+
var expected = ["G", "A", "B", "C", "D", "E", "F#", "G"]
24+
return [scale.interval("MMmMMMm"), expected]
25+
26+
27+
func test_major_scale_with_flats(scale):
28+
scale.tonic = "F"
29+
var expected = ["F", "G", "A", "Bb", "C", "D", "E", "F"]
30+
return [scale.interval("MMmMMMm"), expected]
31+
32+
33+
func test_minor_scale_with_sharps(scale):
34+
scale.tonic = "f#"
35+
var expected = ["F#", "G#", "A", "B", "C#", "D", "E", "F#"]
36+
return [scale.interval("MmMMmMM"), expected]
37+
38+
39+
func test_minor_scale_with_flats(scale):
40+
scale.tonic = "bb"
41+
var expected = ["Bb", "C", "Db", "Eb", "F", "Gb", "Ab", "Bb"]
42+
return [scale.interval("MmMMmMM"), expected]
43+
44+
45+
func test_dorian_mode(scale):
46+
scale.tonic = "d"
47+
var expected = ["D", "E", "F", "G", "A", "B", "C", "D"]
48+
return [scale.interval("MmMMMmM"), expected]
49+
50+
51+
func test_mixolydian_mode(scale):
52+
scale.tonic = "Eb"
53+
var expected = ["Eb", "F", "G", "Ab", "Bb", "C", "Db", "Eb"]
54+
return [scale.interval("MMmMMmM"), expected]
55+
56+
57+
func test_lydian_mode(scale):
58+
scale.tonic = "a"
59+
var expected = ["A", "B", "C#", "D#", "E", "F#", "G#", "A"]
60+
return [scale.interval("MMMmMMm"), expected]
61+
62+
63+
func test_phrygian_mode(scale):
64+
scale.tonic = "e"
65+
var expected = ["E", "F", "G", "A", "B", "C", "D", "E"]
66+
return [scale.interval("mMMMmMM"), expected]
67+
68+
69+
func test_locrian_mode(scale):
70+
scale.tonic = "g"
71+
var expected = ["G", "Ab", "Bb", "C", "Db", "Eb", "F", "G"]
72+
return [scale.interval("mMMmMMM"), expected]
73+
74+
75+
func test_harmonic_minor(scale):
76+
scale.tonic = "d"
77+
var expected = ["D", "E", "F", "G", "A", "Bb", "Db", "D"]
78+
return [scale.interval("MmMMmAm"), expected]
79+
80+
81+
func test_octatonic(scale):
82+
scale.tonic = "C"
83+
var expected = ["C", "D", "D#", "F", "F#", "G#", "A", "B", "C"]
84+
return [scale.interval("MmMmMmMm"), expected]
85+
86+
87+
func test_hexatonic(scale):
88+
scale.tonic = "Db"
89+
var expected = ["Db", "Eb", "F", "G", "A", "B", "Db"]
90+
return [scale.interval("MMMMMM"), expected]
91+
92+
93+
func test_pentatonic(scale):
94+
scale.tonic = "A"
95+
var expected = ["A", "B", "C#", "E", "F#", "A"]
96+
return [scale.interval("MMAMA"), expected]
97+
98+
99+
func test_enigmatic(scale):
100+
scale.tonic = "G"
101+
var expected = ["G", "G#", "B", "C#", "D#", "F", "F#", "G"]
102+
return [scale.interval("mAMMMmm"), expected]
103+
104+

0 commit comments

Comments
 (0)