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,584 @@
1
+ module Musa
2
+ module Scales
3
+ class << self
4
+ def register(scale_system, default: nil)
5
+ @scale_systems ||= {}
6
+ @scale_systems[scale_system.id] = scale_system
7
+
8
+ @default_scale_system = scale_system if default
9
+ self
10
+ end
11
+
12
+ def [](id)
13
+ raise KeyError, "Scale system :#{id} not found" unless @scale_systems.key?(id)
14
+
15
+ @scale_systems[id]
16
+ end
17
+
18
+ def default_system
19
+ @default_scale_system
20
+ end
21
+
22
+ private
23
+
24
+ def method_missing(method_name, *args, **key_args, &block)
25
+ if @scale_systems.key?(method_name) && args.empty? && key_args.empty? && !block
26
+ self[method_name]
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def respond_to_missing?(method_name, include_private)
33
+ @scale_systems.key?(method_name) || super
34
+ end
35
+ end
36
+ end
37
+
38
+ class ScaleSystem
39
+ class << self
40
+ # @abstract Subclass is expected to implement names
41
+ # @!method id
42
+ # @return [Symbol] the id of the ScaleSystem as a symbol
43
+ #
44
+ def id
45
+ raise 'Method not implemented. Should be implemented in subclass.'
46
+ end
47
+
48
+ # @abstract Subclass is expected to implement notes_in_octave
49
+ # @!method notes_in_octave
50
+ # @return [Integer] the number of notes in one octave in the ScaleSystem
51
+ #
52
+ def notes_in_octave
53
+ raise 'Method not implemented. Should be implemented in subclass.'
54
+ end
55
+
56
+ # @abstract Subclass is expected to implement part_of_tone_size
57
+ # @!method part_of_tone_size
58
+ # @return [Integer] the size inside the ScaleSystem of the smaller part of a tone; used for calculate sharp and flat notes
59
+ #
60
+ def part_of_tone_size
61
+ raise 'Method not implemented. Should be implemented in subclass.'
62
+ end
63
+
64
+ # @abstract Subclass is expected to implement intervals
65
+ # @!method intervals
66
+ # @return [Hash] the intervals of the ScaleSystem as { name: semitones#, ... }
67
+ #
68
+ def intervals
69
+ # TODO: implementar intérvalos sinónimos (p.ej, m3 = A2)
70
+ # TODO: implementar identificación de intérvalos, teniendo en cuenta no sólo los semitonos sino los grados de separación
71
+ # TODO: implementar inversión de intérvalos
72
+ raise 'Method not implemented. Should be implemented in subclass.'
73
+ end
74
+
75
+ # @abstract Subclass is expected to implement frequency_of_pitch
76
+ # @!method frequency_of_pitch
77
+ # @param pitch [Number] The pitch (MIDI note numbers based) of the note to get the fundamental frequency
78
+ # @param root_pitch [Number] The pitch (MIDI note numbers based) of the root note of the scale (needed for not equally tempered scales)
79
+ # @param a_frequency [Number] The reference frequency of the mid A note
80
+ # @return [Number] the frequency of the fundamental tone of the pitch
81
+ #
82
+ def frequency_of_pitch(pitch, root_pitch, a_frequency)
83
+ raise 'Method not implemented. Should be implemented in subclass.'
84
+ end
85
+
86
+ # @abstract Subclass can implement default_a_frequency. If subclass doesn't implement default_a_frequency 440.0 Hz is assumed.
87
+ # @!method default_a_frequency
88
+ # @return [Number] the frequency A by default
89
+ #
90
+ def default_a_frequency
91
+ 440.0
92
+ end
93
+
94
+ def [](a_frequency)
95
+ a_frequency = a_frequency.to_f
96
+
97
+ @a_tunings ||= {}
98
+ @a_tunings[a_frequency] = ScaleSystemTuning.new self, a_frequency unless @a_tunings.key?(a_frequency)
99
+
100
+ @a_tunings[a_frequency]
101
+ end
102
+
103
+ def offset_of_interval(name)
104
+ intervals[name]
105
+ end
106
+
107
+ def default_tuning
108
+ self[default_a_frequency]
109
+ end
110
+
111
+ def register(scale_kind_class)
112
+ @scale_kind_classes ||= {}
113
+ @scale_kind_classes[scale_kind_class.id] = scale_kind_class
114
+ if scale_kind_class.chromatic?
115
+ @chromatic_scale_kind_class = scale_kind_class
116
+ end
117
+ self
118
+ end
119
+
120
+ def scale_kind_class(id)
121
+ raise KeyError, "Scale kind class [#{id}] not found in scale system [#{self.id}]" unless @scale_kind_classes.key? id
122
+
123
+ @scale_kind_classes[id]
124
+ end
125
+
126
+ def scale_kind_class?(id)
127
+ @scale_kind_classes.key? id
128
+ end
129
+
130
+ def chromatic_class
131
+ raise "Chromatic scale kind class for [#{self.id}] scale system undefined" if @chromatic_scale_kind_class.nil?
132
+
133
+ @chromatic_scale_kind_class
134
+ end
135
+ end
136
+
137
+ def ==(other)
138
+ self.class == other.class
139
+ end
140
+ end
141
+
142
+ class ScaleSystemTuning
143
+ extend Forwardable
144
+
145
+ def initialize(scale_system, a_frequency)
146
+ @scale_system = scale_system
147
+ @a_frequency = a_frequency
148
+ @scale_kinds = {}
149
+
150
+ @chromatic_scale_kind = self[@scale_system.chromatic_class.id]
151
+ end
152
+
153
+ # TODO: allow scales not based in octaves but in other intervals (like fifths or other ratios). Possibly based on intervals definition of ScaleSystem plus a "generator interval" attribute
154
+
155
+ def_delegators :@scale_system, :notes_in_octave, :offset_of_interval
156
+
157
+ attr_reader :a_frequency, :scale_system
158
+
159
+ def [](scale_kind_class_id)
160
+ @scale_kinds[scale_kind_class_id] ||= @scale_system.scale_kind_class(scale_kind_class_id).new self
161
+ end
162
+
163
+ def chromatic
164
+ @chromatic_scale_kind
165
+ end
166
+
167
+ def frequency_of_pitch(pitch, root)
168
+ @scale_system.frequency_of_pitch(pitch, root, @a_frequency)
169
+ end
170
+
171
+ def ==(other)
172
+ self.class == other.class &&
173
+ @scale_system == other.scale_system &&
174
+ @a_frequency == other.a_frequency
175
+ end
176
+
177
+ def inspect
178
+ "<ScaleSystemTuning: scale_system = #{@scale_system} a_frequency = #{@a_frequency}>"
179
+ end
180
+
181
+ alias to_s inspect
182
+
183
+ private
184
+
185
+ def method_missing(method_name, *args, **key_args, &block)
186
+ if @scale_system.scale_kind_class?(method_name) && args.empty? && key_args.empty? && !block
187
+ self[method_name]
188
+ else
189
+ super
190
+ end
191
+ end
192
+
193
+ def respond_to_missing?(method_name, include_private)
194
+ @scale_system.scale_kind_class?(method_name) || super
195
+ end
196
+ end
197
+
198
+ class ScaleKind
199
+ extend Forwardable
200
+
201
+ def initialize(tuning)
202
+ @tuning = tuning
203
+ @scales = {}
204
+ end
205
+
206
+ attr_reader :tuning
207
+
208
+ def [](root_pitch)
209
+ @scales[root_pitch] = Scale.new(self, root_pitch: root_pitch) unless @scales.key?(root_pitch)
210
+ @scales[root_pitch]
211
+ end
212
+
213
+ def absolut
214
+ self[0]
215
+ end
216
+
217
+ def ==(other)
218
+ self.class == other.class && @tuning == other.tuning
219
+ end
220
+
221
+ def inspect
222
+ "<#{self.class.name}: tuning = #{@tuning}>"
223
+ end
224
+
225
+ alias to_s inspect
226
+
227
+ class << self
228
+ # @abstract Subclass is expected to implement id
229
+ # @!method id
230
+ # @return [Symbol] the id of the ScaleKind as a symbol
231
+ def id
232
+ raise 'Method not implemented. Should be implemented in subclass.'
233
+ end
234
+
235
+ # @abstract Subclass is expected to implement pitches
236
+ # @!method pitches
237
+ # @return [Array] the pitches array of the ScaleKind as [ { functions: [ <symbol>, ...], pitch: <Number> }, ... ]
238
+ def pitches
239
+ raise 'Method not implemented. Should be implemented in subclass.'
240
+ end
241
+
242
+ # @abstract Subclass is expected to implement chromatic?. Only one of the subclasses should return true.
243
+ # @!method chromatic?
244
+ # @return [Boolean] wether the scales is a full scale (with all the notes in the ScaleSystem), sorted and to be considered canonical. I.e. a chromatic 12 semitones uprising serie in a 12 tone tempered ScaleSystem.
245
+ def chromatic?
246
+ false
247
+ end
248
+
249
+ # @abstract Subclass is expected to implement grades when the ScaleKind is defining more pitches than notes by octave has the scale. This can happen when there are pitches defined on upper octaves (i.e., to define XII grade, as a octave + fifth)
250
+ # @!method grades
251
+ # @return [Integer] Number of grades inside of a octave of the scale
252
+ def grades
253
+ pitches.length
254
+ end
255
+
256
+ def find_index(symbol)
257
+ init unless @index
258
+ @index[symbol]
259
+ end
260
+
261
+
262
+ private
263
+
264
+ def init
265
+ @index = {}
266
+ pitches.each_index do |i|
267
+ pitches[i][:functions].each do |function|
268
+ @index[function] = i
269
+ end
270
+ end
271
+ self
272
+ end
273
+ end
274
+ end
275
+
276
+ class Scale
277
+ extend Forwardable
278
+
279
+ def initialize(kind, root_pitch:)
280
+ @notes_by_grade = {}
281
+ @notes_by_pitch = {}
282
+
283
+ @kind = kind
284
+
285
+ @root_pitch = root_pitch
286
+ end
287
+
288
+ def_delegators :@kind, :a_tuning
289
+
290
+ attr_reader :kind, :root_pitch
291
+
292
+ def root
293
+ self[0]
294
+ end
295
+
296
+ def chromatic
297
+ @kind.tuning.chromatic[@root_pitch]
298
+ end
299
+
300
+ def absolut
301
+ @kind[0]
302
+ end
303
+
304
+ def octave(octave)
305
+ raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i
306
+
307
+ @kind[@root_pitch + octave * @kind.class.grades]
308
+ end
309
+
310
+ def [](grade_or_symbol)
311
+
312
+ raise ArgumentError, "grade_or_symbol '#{grade_or_symbol}' should be a Numeric, String or Symbol" unless grade_or_symbol.is_a?(Symbol) || grade_or_symbol.is_a?(String) || grade_or_symbol.is_a?(Integer)
313
+
314
+ wide_grade, sharps = grade_of(grade_or_symbol)
315
+
316
+ unless @notes_by_grade.key?(wide_grade)
317
+
318
+ octave = wide_grade / @kind.class.grades
319
+ grade = wide_grade % @kind.class.grades
320
+
321
+ pitch = @root_pitch +
322
+ octave * @kind.tuning.notes_in_octave +
323
+ @kind.class.pitches[grade][:pitch]
324
+
325
+ note = NoteInScale.new self, grade, octave, pitch
326
+
327
+ @notes_by_grade[wide_grade] = @notes_by_pitch[pitch] = note
328
+ end
329
+
330
+
331
+ @notes_by_grade[wide_grade].sharp(sharps)
332
+ end
333
+
334
+ def grade_of(grade_or_string_or_symbol)
335
+ sign, name, wide_grade, accidentals = Musa::Datasets::GDVd::Parser.parse_grade(grade_or_string_or_symbol)
336
+
337
+ raise ArgumentError, "Cannot parse sign on #{grade_or_string_or_symbol}" if sign
338
+
339
+ grade = @kind.class.find_index name if name
340
+
341
+ octave = wide_grade / @kind.class.grades if wide_grade
342
+ grade = wide_grade % @kind.class.grades if wide_grade
343
+
344
+ octave ||= 0
345
+
346
+ return octave * @kind.class.grades + grade, accidentals
347
+ end
348
+
349
+ def note_of_pitch(pitch, allow_chromatic: nil, allow_nearest: nil)
350
+ allow_chromatic ||= false
351
+ allow_nearest ||= false
352
+
353
+ note = @notes_by_pitch[pitch]
354
+
355
+ unless note
356
+ pitch_offset = pitch - @root_pitch
357
+
358
+ pitch_offset_in_octave = pitch_offset % @kind.tuning.scale_system.notes_in_octave
359
+ pitch_offset_octave = pitch_offset / @kind.tuning.scale_system.notes_in_octave
360
+
361
+ grade = @kind.class.pitches.find_index { |pitch_definition| pitch_definition[:pitch] == pitch_offset_in_octave }
362
+
363
+ if grade
364
+ wide_grade = pitch_offset_octave * @kind.class.grades + grade
365
+ note = self[wide_grade]
366
+
367
+ elsif allow_nearest
368
+ sharps = 0
369
+
370
+ until note
371
+ note = note_of_pitch(pitch - (sharps += 1) * @kind.tuning.scale_system.part_of_tone_size)
372
+ note ||= note_of_pitch(pitch + sharps * @kind.tuning.scale_system.part_of_tone_size)
373
+ end
374
+
375
+ elsif allow_chromatic
376
+ nearest = note_of_pitch(pitch, allow_nearest: true)
377
+
378
+ note = chromatic.note_of_pitch(pitch).with_background(scale: self, grade: nearest.grade, octave: nearest.octave, sharps: (pitch - nearest.pitch) / @kind.tuning.scale_system.part_of_tone_size)
379
+ end
380
+ end
381
+
382
+ note
383
+ end
384
+
385
+ def offset_of_interval(interval_name)
386
+ @kind.tuning.offset_of_interval(interval_name)
387
+ end
388
+
389
+ def chord_of(*grades_or_symbols)
390
+ Chord.new(notes: grades_or_symbols.collect { |g| self[g] })
391
+ end
392
+
393
+ def ==(other)
394
+ self.class == other.class &&
395
+ @kind == other.kind &&
396
+ @root_pitch == other.root_pitch
397
+ end
398
+
399
+ def inspect
400
+ "<Scale: kind = #{@kind} root_pitch = #{@root_pitch}>"
401
+ end
402
+
403
+ alias to_s inspect
404
+
405
+ private
406
+
407
+ def method_missing(method_name, *args, **key_args, &block)
408
+ if @kind.class.find_index(method_name) && args.empty? && key_args.empty? && !block
409
+ self[method_name]
410
+ else
411
+ super
412
+ end
413
+ end
414
+
415
+ def respond_to_missing?(method_name, include_private)
416
+ @kind.class.find_index(method_name) || super
417
+ end
418
+ end
419
+
420
+ class NoteInScale
421
+
422
+ # @param scale [Scale]
423
+ # @param grade []
424
+ # @param octave [Integer]
425
+ # @param pitch [Number] pitch of the note, based on MIDI note numbers. Can be Integer, Rational or Float to express fractions of a semitone
426
+ #
427
+ def initialize(scale, grade, octave, pitch, background_scale: nil, background_grade: nil, background_octave: nil, background_sharps: nil)
428
+ @scale = scale
429
+ @grade = grade
430
+ @octave = octave
431
+ @pitch = pitch
432
+
433
+ @background_scale = background_scale
434
+ @background_grade = background_grade
435
+ @background_octave = background_octave
436
+ @background_sharps = background_sharps
437
+ end
438
+
439
+ attr_reader :grade, :pitch
440
+
441
+ def functions
442
+ @scale.kind.class.pitches[grade][:functions]
443
+ end
444
+
445
+ def octave(octave = nil)
446
+ if octave.nil?
447
+ @octave
448
+ else
449
+ raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i
450
+
451
+ @scale[@grade + (@octave + octave) * @scale.kind.class.grades]
452
+ end
453
+ end
454
+
455
+ def with_background(scale:, grade: nil, octave: nil, sharps: nil)
456
+ NoteInScale.new(@scale, @grade, @octave, @pitch,
457
+ background_scale: scale,
458
+ background_grade: grade,
459
+ background_octave: octave,
460
+ background_sharps: sharps)
461
+ end
462
+
463
+ attr_reader :background_scale
464
+
465
+ def background_note
466
+ @background_scale[@background_grade + (@background_octave || 0) * @background_scale.kind.class.grades] if @background_grade
467
+ end
468
+
469
+ attr_reader :background_sharps
470
+
471
+ def wide_grade
472
+ @grade + @octave * @scale.kind.class.grades
473
+ end
474
+
475
+ def up(interval_name_or_interval, natural_or_chromatic = nil, sign: nil)
476
+
477
+ sign ||= 1
478
+
479
+ if interval_name_or_interval.is_a?(Numeric)
480
+ natural_or_chromatic ||= :natural
481
+ else
482
+ natural_or_chromatic = :chromatic
483
+ end
484
+
485
+ if natural_or_chromatic == :chromatic
486
+ interval = if interval_name_or_interval.is_a?(Symbol)
487
+ @scale.kind.tuning.offset_of_interval(interval_name_or_interval)
488
+ else
489
+ interval_name_or_interval
490
+ end
491
+
492
+ calculate_note_of_pitch(@pitch, sign * interval)
493
+ else
494
+ @scale[@grade + sign * interval_name_or_interval]
495
+ end
496
+ end
497
+
498
+ def calculate_note_of_pitch(in_scale_pitch, sharps)
499
+ pitch = in_scale_pitch + sharps * @scale.kind.tuning.scale_system.part_of_tone_size
500
+
501
+ if pitch == @pitch
502
+ self
503
+ else
504
+ note = @scale.note_of_pitch(pitch, allow_chromatic: true)
505
+ if @background_scale
506
+ note.on(@background_scale) || note
507
+ else
508
+ note
509
+ end
510
+ end
511
+ end
512
+
513
+ private :calculate_note_of_pitch
514
+
515
+ def down(interval_name_or_interval, natural_or_chromatic = nil)
516
+ up(interval_name_or_interval, natural_or_chromatic, sign: -1)
517
+ end
518
+
519
+ def sharp(count = nil)
520
+ count ||= 1
521
+ calculate_note_of_pitch(@pitch, count)
522
+ end
523
+
524
+ def flat(count = nil)
525
+ count ||= 1
526
+ sharp(-count)
527
+ end
528
+
529
+ def frequency
530
+ @scale.kind.tuning.frequency_of_pitch(@pitch, @scale.root)
531
+ end
532
+
533
+ def scale(kind_id_or_kind = nil)
534
+ if kind_id_or_kind.nil?
535
+ @scale
536
+ else
537
+ if kind_id_or_kind.is_a? ScaleKind
538
+ kind_id_or_kind[@pitch]
539
+ else
540
+ @scale.kind.tuning[kind_id_or_kind][@pitch]
541
+ end
542
+ end
543
+ end
544
+
545
+ def on(scale)
546
+ scale.note_of_pitch @pitch
547
+ end
548
+
549
+ def chord(*feature_values, allow_chromatic: nil, **features_hash)
550
+ features = { size: :triad } if feature_values.empty? && features_hash.empty?
551
+ features ||= ChordDefinition.features_from(feature_values, features_hash)
552
+
553
+ Musa::Chord.new(root: self, allow_chromatic: allow_chromatic, features: features)
554
+ end
555
+
556
+ def ==(other)
557
+ self.class == other.class &&
558
+ @scale == other.scale &&
559
+ @grade == other.grade &&
560
+ @octave == other.octave &&
561
+ @pitch == other.pitch
562
+ end
563
+
564
+ def inspect
565
+ "<NoteInScale: grade = #{@grade} octave = #{@octave} pitch = #{@pitch} scale = (#{@scale.kind.class.name} on #{scale.root_pitch})>"
566
+ end
567
+
568
+ alias to_s inspect
569
+
570
+ private
571
+
572
+ def method_missing(method_name, *args, **key_args, &block)
573
+ if @scale.kind.tuning[method_name] && args.empty? && key_args.empty? && !block
574
+ scale(method_name)
575
+ else
576
+ super
577
+ end
578
+ end
579
+
580
+ def respond_to_missing?(method_name, include_private)
581
+ @scale.kind.tuning[method_name] || super
582
+ end
583
+ end
584
+ end
@@ -0,0 +1,6 @@
1
+ require 'musa-dsl/music/scales'
2
+ require 'musa-dsl/music/equally-tempered-12-tone-scale-system'
3
+
4
+ require 'musa-dsl/music/chord-definition'
5
+ require 'musa-dsl/music/chords'
6
+ require 'musa-dsl/music/chord-definitions'