musa-dsl 0.14.16

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/Gemfile +20 -0
  4. data/LICENSE.md +157 -0
  5. data/README.md +8 -0
  6. data/lib/musa-dsl/core-ext/array-apply-get.rb +18 -0
  7. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +29 -0
  8. data/lib/musa-dsl/core-ext/array-to-neumas.rb +28 -0
  9. data/lib/musa-dsl/core-ext/array-to-serie.rb +20 -0
  10. data/lib/musa-dsl/core-ext/arrayfy.rb +15 -0
  11. data/lib/musa-dsl/core-ext/as-context-run.rb +44 -0
  12. data/lib/musa-dsl/core-ext/duplicate.rb +134 -0
  13. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +55 -0
  14. data/lib/musa-dsl/core-ext/inspect-nice.rb +28 -0
  15. data/lib/musa-dsl/core-ext/key-parameters-procedure-binder.rb +85 -0
  16. data/lib/musa-dsl/core-ext/proc-nice.rb +13 -0
  17. data/lib/musa-dsl/core-ext/send-nice.rb +21 -0
  18. data/lib/musa-dsl/core-ext/string-to-neumas.rb +27 -0
  19. data/lib/musa-dsl/core-ext.rb +13 -0
  20. data/lib/musa-dsl/datasets/gdv-decorators.rb +221 -0
  21. data/lib/musa-dsl/datasets/gdv.rb +499 -0
  22. data/lib/musa-dsl/datasets/pdv.rb +44 -0
  23. data/lib/musa-dsl/datasets.rb +5 -0
  24. data/lib/musa-dsl/generative/darwin.rb +145 -0
  25. data/lib/musa-dsl/generative/generative-grammar.rb +294 -0
  26. data/lib/musa-dsl/generative/markov.rb +78 -0
  27. data/lib/musa-dsl/generative/rules.rb +282 -0
  28. data/lib/musa-dsl/generative/variatio.rb +331 -0
  29. data/lib/musa-dsl/generative.rb +5 -0
  30. data/lib/musa-dsl/midi/midi-recorder.rb +83 -0
  31. data/lib/musa-dsl/midi/midi-voices.rb +274 -0
  32. data/lib/musa-dsl/midi.rb +2 -0
  33. data/lib/musa-dsl/music/chord-definition.rb +99 -0
  34. data/lib/musa-dsl/music/chord-definitions.rb +13 -0
  35. data/lib/musa-dsl/music/chords.rb +326 -0
  36. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +204 -0
  37. data/lib/musa-dsl/music/scales.rb +584 -0
  38. data/lib/musa-dsl/music.rb +6 -0
  39. data/lib/musa-dsl/neuma/neuma.rb +181 -0
  40. data/lib/musa-dsl/neuma.rb +1 -0
  41. data/lib/musa-dsl/neumalang/neumalang.citrus +294 -0
  42. data/lib/musa-dsl/neumalang/neumalang.rb +179 -0
  43. data/lib/musa-dsl/neumalang.rb +3 -0
  44. data/lib/musa-dsl/repl/repl.rb +143 -0
  45. data/lib/musa-dsl/repl.rb +1 -0
  46. data/lib/musa-dsl/sequencer/base-sequencer-implementation-control.rb +189 -0
  47. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +354 -0
  48. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +382 -0
  49. data/lib/musa-dsl/sequencer/base-sequencer-public.rb +261 -0
  50. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +94 -0
  51. data/lib/musa-dsl/sequencer/sequencer.rb +3 -0
  52. data/lib/musa-dsl/sequencer.rb +1 -0
  53. data/lib/musa-dsl/series/base-series.rb +245 -0
  54. data/lib/musa-dsl/series/hash-serie-splitter.rb +194 -0
  55. data/lib/musa-dsl/series/holder-serie.rb +87 -0
  56. data/lib/musa-dsl/series/main-serie-constructors.rb +726 -0
  57. data/lib/musa-dsl/series/main-serie-operations.rb +1151 -0
  58. data/lib/musa-dsl/series/proxy-serie.rb +69 -0
  59. data/lib/musa-dsl/series/queue-serie.rb +94 -0
  60. data/lib/musa-dsl/series/series.rb +8 -0
  61. data/lib/musa-dsl/series.rb +1 -0
  62. data/lib/musa-dsl/transport/clock.rb +36 -0
  63. data/lib/musa-dsl/transport/dummy-clock.rb +47 -0
  64. data/lib/musa-dsl/transport/external-tick-clock.rb +31 -0
  65. data/lib/musa-dsl/transport/input-midi-clock.rb +124 -0
  66. data/lib/musa-dsl/transport/timer-clock.rb +102 -0
  67. data/lib/musa-dsl/transport/timer.rb +40 -0
  68. data/lib/musa-dsl/transport/transport.rb +137 -0
  69. data/lib/musa-dsl/transport.rb +9 -0
  70. data/lib/musa-dsl.rb +17 -0
  71. data/musa-dsl.gemspec +17 -0
  72. metadata +174 -0
@@ -0,0 +1,99 @@
1
+ module Musa
2
+ class ChordDefinition
3
+ class << self
4
+ def [](name)
5
+ @definitions[name]
6
+ end
7
+
8
+ def register(name, offsets:, **features)
9
+ definition = ChordDefinition.new(name, offsets: offsets, **features).freeze
10
+
11
+ @definitions ||= {}
12
+ @definitions[definition.name] = definition
13
+
14
+ @features_by_value ||= {}
15
+ definition.features.each { |k, v| @features_by_value[v] = k }
16
+
17
+ self
18
+ end
19
+
20
+ def find_by_pitches(pitches)
21
+ @definitions.values.find { |d| d.matches(pitches) }
22
+ end
23
+
24
+ def features_from(values = nil, hash = nil)
25
+ values ||= []
26
+ hash ||= {}
27
+
28
+ features = hash.dup
29
+ values.each { |v| features[@features_by_value[v]] = v }
30
+
31
+ features
32
+ end
33
+
34
+ def find_by_features(*values, **hash)
35
+ features = features_from(values, hash)
36
+ @definitions.values.select { |d| features <= d.features }
37
+ end
38
+
39
+ def feature_key_of(feature_value)
40
+ @features_by_value[feature_value]
41
+ end
42
+ end
43
+
44
+ def initialize(name, offsets:, **features)
45
+ @name = name
46
+ @features = features.clone.freeze
47
+ @pitch_offsets = offsets.clone.freeze
48
+ @pitch_names = offsets.collect { |k, v| [v, k] }.to_h
49
+ end
50
+
51
+ attr_reader :name, :features, :pitch_offsets, :pitch_names
52
+
53
+ def pitches(root_pitch)
54
+ @pitch_offsets.values.collect { |offset| root_pitch + offset }
55
+ end
56
+
57
+ def named_pitches(elements_or_pitches, &block)
58
+ pitches = elements_or_pitches.collect do |element_or_pitch|
59
+ [if block_given?
60
+ yield element_or_pitch
61
+ else
62
+ element_or_pitch
63
+ end,
64
+ element_or_pitch]
65
+ end.to_h
66
+
67
+ root_pitch = pitches.keys.find do |candidate_root_pitch|
68
+ candidate_pitches = pitches.keys.collect { |p| p - candidate_root_pitch }
69
+ octave_reduce(candidate_pitches).uniq == octave_reduce(@pitch_offsets.values).uniq
70
+ end
71
+
72
+ # TODO: OJO: problema con las notas duplicadas, con la identificación de inversiones y con las notas a distancias de más de una octava
73
+
74
+ pitches.collect do |pitch, element|
75
+ [@pitch_names[pitch - root_pitch], [element]]
76
+ end.to_h
77
+ end
78
+
79
+ def matches(pitches)
80
+ reduced_pitches = octave_reduce(pitches).uniq
81
+
82
+ !!reduced_pitches.find do |candidate_root_pitch|
83
+ reduced_pitches.sort == octave_reduce(pitches(candidate_root_pitch)).uniq.sort
84
+ end
85
+ end
86
+
87
+ def inspect
88
+ "<ChordDefinition: name = #{@name} features = #{@features} pitch_offsets = #{@pitch_offsets}>"
89
+ end
90
+
91
+ alias to_s inspect
92
+
93
+ protected
94
+
95
+ def octave_reduce(pitches)
96
+ pitches.collect { |p| p % 12 }
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,13 @@
1
+ Musa::ChordDefinition.register :maj, quality: :major, size: :triad, offsets: { root: 0, third: 4, fifth: 7 }
2
+ Musa::ChordDefinition.register :min, quality: :minor, size: :triad, offsets: { root: 0, third: 3, fifth: 7 }
3
+
4
+ Musa::ChordDefinition.register :maj7, quality: :major, size: :seventh, offsets: { root: 0, third: 4, fifth: 7, seventh: 11 }
5
+ Musa::ChordDefinition.register :maj7, quality: :major, size: :seventh, dominant: :dominant , offsets: { root: 0, third: 4, fifth: 7, seventh: 10 }
6
+
7
+ Musa::ChordDefinition.register :min7, quality: :minor, size: :seventh, offsets: { root: 0, third: 3, fifth: 7, seventh: 11 }
8
+
9
+ Musa::ChordDefinition.register :maj9, quality: :major, size: :ninth, offsets: { root: 0, third: 4, fifth: 7, seventh: 11, ninth: 14 }
10
+ Musa::ChordDefinition.register :min9, quality: :minor, size: :ninth, offsets: { root: 0, third: 3, fifth: 7, seventh: 11, ninth: 14 }
11
+
12
+ Musa::ChordDefinition.register :maj11, quality: :major, size: :eleventh, offsets: { root: 0, third: 4, fifth: 7, seventh: 11, ninth: 14, eleventh: 17 }
13
+ Musa::ChordDefinition.register :min11, quality: :minor, size: :eleventh, offsets: { root: 0, third: 3, fifth: 7, seventh: 11, ninth: 14, eleventh: 17 }
@@ -0,0 +1,326 @@
1
+ require_relative 'scales'
2
+ require_relative 'chord-definition'
3
+
4
+ module Musa
5
+ class Chord
6
+ def initialize(name_or_notes_or_pitches = nil, # name | [notes] | [pitches]
7
+ # definitory
8
+ name: nil,
9
+ root: nil, root_grade: nil,
10
+ notes: nil, pitches: nil,
11
+ features: nil,
12
+ # target scale (or scale reference)
13
+ scale: nil,
14
+ allow_chromatic: nil,
15
+ # operations
16
+ inversion: nil, state: nil,
17
+ position: nil,
18
+ move: nil,
19
+ duplicate: nil,
20
+ add: nil,
21
+ drop: nil,
22
+ #
23
+ _source: nil)
24
+
25
+ # Preparing notes and pitches Arrays: they will we used to collect further notes and pitches
26
+ #
27
+ if notes
28
+ notes = notes.collect do |n|
29
+ case n
30
+ when Musa::NoteInScale
31
+ n
32
+ when Numeric, Symbol
33
+ scale[n]
34
+ else
35
+ raise ArgumentError, "Can't recognize #{n} in notes list #{notes}"
36
+ end
37
+ end
38
+ end
39
+
40
+ pitches = pitches.clone if pitches
41
+
42
+ # Preparing root_pitch
43
+ #
44
+
45
+ root_pitch = nil
46
+
47
+ raise ArgumentError, "Duplicate parameter: root: #{root} and root_grade: #{root_grade}" if root && root_grade
48
+
49
+ allow_chromatic ||= scale.nil?
50
+
51
+ if root && root.is_a?(Musa::NoteInScale)
52
+ root_pitch = root.pitch
53
+ scale ||= root.scale
54
+ end
55
+
56
+ raise ArgumentError, "Don't know how to recognize root_grade #{root_grade}: scale is not provided" if root_grade && !scale
57
+
58
+ root_pitch = scale[root_grade].pitch if root_grade && scale
59
+
60
+ # Parse name_or_notes_or_pitches to name, notes, pitches
61
+ #
62
+ #
63
+ case name_or_notes_or_pitches
64
+ when Symbol
65
+ raise ArgumentError, "Duplicate parameter #{name_or_notes_or_pitches} and name: #{name}" if name
66
+
67
+ name = name_or_notes_or_pitches
68
+
69
+ when Array
70
+ name_or_notes_or_pitches.each do |note_or_pitch|
71
+ case note_or_pitch
72
+ when Musa::NoteInScale
73
+ notes ||= [] << note_or_pitch
74
+ when Numeric
75
+ if scale
76
+ notes ||= [] << scale[note_or_pitch]
77
+ else
78
+ pitches ||= [] << note_or_pitch
79
+ end
80
+ when Symbol
81
+ raise ArgumentError, "Don't know how to recognize #{note_or_pitch} in parameter list #{name_or_notes_or_pitches}: it's a symbol but the scale is not provided" unless scale
82
+
83
+ notes ||= [] << scale[note_or_pitch]
84
+ else
85
+ raise ArgumentError, "Can't recognize #{note_or_pitch} in parameter list #{name_or_notes_or_pitches}"
86
+ end
87
+ end
88
+
89
+ when nil
90
+ # nothing happens
91
+ else
92
+ raise ArgumentError, "Can't recognize #{name_or_notes_or_pitches}"
93
+ end
94
+
95
+ # Eval definitory atributes
96
+ #
97
+
98
+ if _source.nil?
99
+ @notes = compute_notes(name, root_pitch, scale, notes, pitches, features, allow_chromatic)
100
+ else
101
+ @notes = compute_notes_from_source(_source, name, root_pitch, scale, notes, pitches, features, allow_chromatic)
102
+ end
103
+
104
+ # Eval adding / droping operations
105
+ #
106
+
107
+ if add
108
+ add.each do |to_add|
109
+ case to_add
110
+ when NoteInScale
111
+ @notes << to_add
112
+ when Numeric # pitch increment
113
+ pitch = root_pitch + to_add
114
+ @notes << scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)
115
+ when Symbol # interval name
116
+ pitch = root_pitch + scale.offset_of_interval(to_add)
117
+ @notes << scale.note_of_pitch(pitch)
118
+ else
119
+ raise ArgumentError, "Can't recognize element to add #{to_add}"
120
+ end
121
+ end
122
+ end
123
+
124
+ # TODO: Missing chord operations: drop, inversion, state, position
125
+ #
126
+ raise NotImplementedError, 'Missing chord operations: drop, inversion, state, position' if drop || inversion || state || position
127
+
128
+ # Eval voice increment operations
129
+ #
130
+
131
+ if move
132
+ raise ArgumentError, 'move: expected a Hash' unless move.is_a?(Hash)
133
+
134
+ move.each do |position, octave|
135
+ @notes[position][0] = @notes[position][0].octave(octave)
136
+ end
137
+ end
138
+
139
+ if duplicate
140
+ raise ArgumentError, 'duplicate: expected a Hash' unless duplicate.is_a?(Hash)
141
+
142
+ duplicate.each do |position, octave|
143
+ octave.arrayfy.each do |octave|
144
+ @notes[position] << @notes[position][0].octave(octave)
145
+ end
146
+ end
147
+ end
148
+
149
+ # Identify chord
150
+ #
151
+
152
+ @notes.freeze
153
+
154
+ @chord_definition = ChordDefinition.find_by_pitches(@notes.values.flatten(1).collect(&:pitch))
155
+ end
156
+
157
+ attr_reader :notes, :chord_definition
158
+
159
+ def name(name = nil)
160
+ if name.nil?
161
+ @chord_definition.name if @chord_definition
162
+ else
163
+ Chord.new(_source: self, name: name)
164
+ end
165
+ end
166
+
167
+ def features
168
+ @chord_definition.features if @chord_definition
169
+ end
170
+
171
+ def featuring(*values, allow_chromatic: nil, **hash)
172
+ features = @chord_definition.features.dup if @chord_definition
173
+ features ||= {}
174
+
175
+ ChordDefinition.features_from(values, hash).each { |k, v| features[k] = v }
176
+
177
+ Chord.new(_source: self, allow_chromatic: allow_chromatic, features: features)
178
+ end
179
+
180
+ def root(root = nil)
181
+ if root.nil?
182
+ @notes[:root]
183
+ else
184
+ Chord.new(_source: self, root: root)
185
+ end
186
+ end
187
+
188
+ def [](position)
189
+ case position
190
+ when Numeric
191
+ @notes.values[position]
192
+ when Symbol
193
+ @notes[position]
194
+ end
195
+ end
196
+
197
+ def move(**octaves)
198
+ Chord.new(_source: self, move: octaves)
199
+ end
200
+
201
+ def duplicate(**octaves)
202
+ Chord.new(_source: self, duplicate: octaves)
203
+ end
204
+
205
+ def scale
206
+ scales = @notes.values.flatten(1).collect(&:scale).uniq
207
+ scales.first if scales.size == 1
208
+ end
209
+
210
+ # Converts the chord to a specific scale with the notes in the chord
211
+ def as_scale
212
+ end
213
+
214
+
215
+ def project_on_all(*scales, allow_chromatic: nil)
216
+ # TODO add match to other chords... what does it means?
217
+ allow_chromatic ||= false
218
+
219
+ note_sets = {}
220
+ scales.each do |scale|
221
+ if allow_chromatic
222
+ note_sets[scale] = @notes.values.flatten(1).collect { |n| n.on(scale) || n.on(scale.chromatic) }
223
+ else
224
+ note_sets[scale] = @notes.values.flatten(1).collect { |n| n.on(scale) }
225
+ end
226
+ end
227
+
228
+ note_sets_in_scale = note_sets.values.reject { |notes| notes.include?(nil) }
229
+ note_sets_in_scale.collect { |notes| Chord.new(notes: notes) }
230
+ end
231
+
232
+ def project_on(*scales, allow_chromatic: nil)
233
+ allow_chromatic ||= false
234
+ project_on_all(*scales, allow_chromatic: allow_chromatic).first
235
+ end
236
+
237
+ def ==(other)
238
+ self.class == other.class && @notes == other.notes
239
+ end
240
+
241
+ def inspect
242
+ "<Chord: notes = #{@notes}>"
243
+ end
244
+
245
+ alias to_s inspect
246
+
247
+ private
248
+
249
+ def compute_notes(name, root_pitch, scale, notes, pitches, features, allow_chromatic)
250
+ if name && root_pitch && scale && !(notes || pitches || features)
251
+
252
+ chord_definition = ChordDefinition[name]
253
+
254
+ raise ArgumentError, "Unrecognized #{name} chord" unless chord_definition
255
+
256
+ chord_definition.pitch_offsets.transform_values do |offset|
257
+ pitch = root_pitch + offset
258
+ [scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)]
259
+ end
260
+
261
+ elsif root_pitch && features && scale && !(name || notes || pitches)
262
+
263
+ chord_definitions = ChordDefinition.find_by_features(**features)
264
+
265
+ unless allow_chromatic
266
+ chord_definitions.reject! do |chord_definition|
267
+ chord_definition.pitches(root_pitch).find { |chord_pitch| scale.note_of_pitch(chord_pitch).nil? }
268
+ end
269
+ end
270
+
271
+ selected = chord_definitions.first
272
+
273
+ unless selected
274
+ raise ArgumentError, "Don't know how to create a chord with root pitch #{root_pitch}"\
275
+ " and features #{features} based on scale #{scale.kind.class} with root on #{scale.root}: "\
276
+ " no suitable definition found (allow_chromatic is #{allow_chromatic})"
277
+ end
278
+
279
+ selected.pitch_offsets.transform_values do |offset|
280
+ pitch = root_pitch + offset
281
+ [scale.note_of_pitch(pitch) || scale.chromatic.note_of_pitch(pitch)]
282
+ end
283
+
284
+ elsif (notes || pitches && scale) && !(name || root_pitch || features)
285
+
286
+ notes ||= []
287
+
288
+ notes += pitches.collect { |p| scale.note_of_pitch(p) } if pitches
289
+
290
+ chord_definition = ChordDefinition.find_by_pitches(notes.collect(&:pitch))
291
+
292
+ raise "Can't find a chord definition for pitches #{pitches} on scale #{scale.kind.id} based on #{scale.root}" unless chord_definition
293
+
294
+ chord_definition.named_pitches(notes, &:pitch)
295
+ else
296
+ pattern = { name: name, root: root_pitch, scale: scale, notes: notes, pitches: pitches, features: features, allow_chromatic: allow_chromatic }
297
+ raise ArgumentError, "Can't understand chord definition pattern #{pattern}"
298
+ end
299
+ end
300
+
301
+ def compute_notes_from_source(source, name, root_pitch, scale, notes, pitches, features, allow_chromatic)
302
+ if !(name || root_pitch || scale || notes || pitches || features)
303
+ source.notes
304
+
305
+ elsif features && !(name || root_pitch || scale || notes || pitches)
306
+ compute_notes(nil, source.root.first.pitch, source.root.first.scale, nil, nil, features, allow_chromatic)
307
+
308
+ else
309
+ pattern = { name: name, root: root_pitch, scale: scale, notes: notes, pitches: pitches, features: features, allow_chromatic: allow_chromatic }
310
+ raise ArgumentError, "Can't understand chord definition pattern #{pattern}"
311
+ end
312
+ end
313
+
314
+ def method_missing(method_name, *args, **key_args, &block)
315
+ if ChordDefinition.feature_key_of(method_name) && args.empty? && key_args.empty? && !block
316
+ featuring(method_name)
317
+ else
318
+ super
319
+ end
320
+ end
321
+
322
+ def respond_to_missing?(method_name, include_private)
323
+ ChordDefinition.feature_key_of(method_name) || super
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,204 @@
1
+ module Musa
2
+ class TwelveSemitonesScaleSystem < ScaleSystem
3
+ class << self
4
+ @@intervals = { P0: 0, m2: 1, M2: 2, m3: 3, M3: 4, P4: 5, TT: 6, P5: 7, m6: 8, M6: 9, m7: 10, M7: 11, P8: 12 }
5
+
6
+ def id
7
+ :et12
8
+ end
9
+
10
+ def notes_in_octave
11
+ 12
12
+ end
13
+
14
+ def part_of_tone_size
15
+ 1
16
+ end
17
+
18
+ def intervals
19
+ @@intervals
20
+ end
21
+ end
22
+ end
23
+
24
+ class EquallyTempered12ToneScaleSystem < TwelveSemitonesScaleSystem
25
+ class << self
26
+ def frequency_of_pitch(pitch, _root_pitch, a_frequency)
27
+ (a_frequency * Rational(2)**Rational(pitch - 69, 12)).to_f
28
+ end
29
+ end
30
+
31
+ Scales.register EquallyTempered12ToneScaleSystem, default: true
32
+ end
33
+
34
+
35
+ class ChromaticScaleKind < ScaleKind
36
+ class << self
37
+ @@pitches =
38
+ [{ functions: [:_1], pitch: 0 },
39
+ { functions: [:_2], pitch: 1 },
40
+ { functions: [:_3], pitch: 2 },
41
+ { functions: [:_4], pitch: 3 },
42
+ { functions: [:_5], pitch: 4 },
43
+ { functions: [:_6], pitch: 5 },
44
+ { functions: [:_7], pitch: 6 },
45
+ { functions: [:_8], pitch: 7 },
46
+ { functions: [:_9], pitch: 8 },
47
+ { functions: [:_10], pitch: 9 },
48
+ { functions: [:_11], pitch: 10 },
49
+ { functions: [:_12], pitch: 11 }].freeze
50
+
51
+ def pitches
52
+ @@pitches
53
+ end
54
+
55
+ def id
56
+ :chromatic
57
+ end
58
+
59
+ def chromatic?
60
+ true
61
+ end
62
+ end
63
+
64
+ EquallyTempered12ToneScaleSystem.register ChromaticScaleKind
65
+ end
66
+
67
+ class MajorScaleKind < ScaleKind
68
+ class << self
69
+ @@pitches =
70
+ [{ functions: %i[I _1 tonic],
71
+ pitch: 0 },
72
+ { functions: %i[II _2 supertonic],
73
+ pitch: 2 },
74
+ { functions: %i[III _3 mediant],
75
+ pitch: 4 },
76
+ { functions: %i[IV _4 subdominant],
77
+ pitch: 5 },
78
+ { functions: %i[V _5 dominant],
79
+ pitch: 7 },
80
+ { functions: %i[VI _6 submediant relative relative_minor],
81
+ pitch: 9 },
82
+ { functions: %i[VII _7 leading],
83
+ pitch: 11 },
84
+ { functions: %i[VIII _8],
85
+ pitch: 12 },
86
+ { functions: %i[IX _9],
87
+ pitch: 12 + 2 },
88
+ { functions: %i[X _10],
89
+ pitch: 12 + 4 },
90
+ { functions: %i[XI _11],
91
+ pitch: 12 + 5 },
92
+ { functions: %i[XII _12],
93
+ pitch: 12 + 7 },
94
+ { functions: %i[XIII _13],
95
+ pitch: 12 + 9 }].freeze
96
+
97
+ def pitches
98
+ @@pitches
99
+ end
100
+
101
+ def grades
102
+ 7
103
+ end
104
+
105
+ def id
106
+ :major
107
+ end
108
+ end
109
+
110
+ EquallyTempered12ToneScaleSystem.register MajorScaleKind
111
+ end
112
+
113
+ class MinorScaleKind < ScaleKind
114
+ class << self
115
+ @@pitches =
116
+ [{ functions: %i[i _1 tonic],
117
+ pitch: 0 },
118
+ { functions: %i[ii _2 supertonic],
119
+ pitch: 2 },
120
+ { functions: %i[iii _3 mediant relative relative_major],
121
+ pitch: 3 },
122
+ { functions: %i[iv _4 subdominant],
123
+ pitch: 5 },
124
+ { functions: %i[v _5 dominant],
125
+ pitch: 7 },
126
+ { functions: %i[vi _6 submediant],
127
+ pitch: 8 },
128
+ { functions: %i[vii _7],
129
+ pitch: 10 },
130
+ { functions: %i[viii _8],
131
+ pitch: 12 },
132
+ { functions: %i[ix _9],
133
+ pitch: 12 + 2 },
134
+ { functions: %i[x _10],
135
+ pitch: 12 + 3 },
136
+ { functions: %i[xi _11],
137
+ pitch: 12 + 5 },
138
+ { functions: %i[xii _12],
139
+ pitch: 12 + 7 },
140
+ { functions: %i[xiii _13],
141
+ pitch: 12 + 8 }].freeze
142
+
143
+ def pitches
144
+ @@pitches
145
+ end
146
+
147
+ def grades
148
+ 7
149
+ end
150
+
151
+ def id
152
+ :minor
153
+ end
154
+ end
155
+
156
+ EquallyTempered12ToneScaleSystem.register MinorScaleKind
157
+ end
158
+
159
+ class MinorHarmonicScaleKind < ScaleKind
160
+ class << self
161
+ @@pitches =
162
+ [{ functions: %i[i _1 tonic],
163
+ pitch: 0 },
164
+ { functions: %i[ii _2 supertonic],
165
+ pitch: 2 },
166
+ { functions: %i[iii _3 mediant relative relative_major],
167
+ pitch: 3 },
168
+ { functions: %i[iv _4 subdominant],
169
+ pitch: 5 },
170
+ { functions: %i[v _5 dominant],
171
+ pitch: 7 },
172
+ { functions: %i[vi _6 submediant],
173
+ pitch: 8 },
174
+ { functions: %i[vii _7 leading],
175
+ pitch: 11 },
176
+ { functions: %i[viii _8],
177
+ pitch: 12 },
178
+ { functions: %i[ix _9],
179
+ pitch: 12 + 2 },
180
+ { functions: %i[x _10],
181
+ pitch: 12 + 3 },
182
+ { functions: %i[xi _11],
183
+ pitch: 12 + 5 },
184
+ { functions: %i[xii _12],
185
+ pitch: 12 + 7 },
186
+ { functions: %i[xiii _13],
187
+ pitch: 12 + 8 }].freeze
188
+
189
+ def pitches
190
+ @@pitches
191
+ end
192
+
193
+ def grades
194
+ 7
195
+ end
196
+
197
+ def id
198
+ :minor_harmonic
199
+ end
200
+ end
201
+
202
+ EquallyTempered12ToneScaleSystem.register MinorHarmonicScaleKind
203
+ end
204
+ end