musa-dsl 0.14.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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