musa-dsl 0.41.0 → 0.42.0
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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/README.md +15 -1
- data/docs/README.md +1 -0
- data/docs/subsystems/datasets.md +75 -0
- data/docs/subsystems/generative.md +92 -6
- data/docs/subsystems/music.md +33 -14
- data/docs/subsystems/transport.md +26 -0
- data/lib/musa-dsl/datasets/dataset.rb +2 -0
- data/lib/musa-dsl/datasets/gdv.rb +3 -3
- data/lib/musa-dsl/datasets/p.rb +1 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +4 -2
- data/lib/musa-dsl/datasets/score.rb +3 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +3 -1
- data/lib/musa-dsl/generative/markov.rb +1 -1
- data/lib/musa-dsl/midi/midi-voices.rb +3 -1
- data/lib/musa-dsl/music/chord-definition.rb +7 -5
- data/lib/musa-dsl/music/chord-definitions.rb +37 -0
- data/lib/musa-dsl/music/chords.rb +69 -47
- data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +1 -1
- data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +1 -1
- data/lib/musa-dsl/music/scales.rb +219 -107
- data/lib/musa-dsl/musicxml/builder/note.rb +31 -92
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +33 -94
- data/lib/musa-dsl/musicxml/builder/rest.rb +30 -91
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +31 -91
- data/lib/musa-dsl/neumas/array-to-neumas.rb +1 -1
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +2 -2
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +367 -3
- data/lib/musa-dsl/series/base-series.rb +250 -240
- data/lib/musa-dsl/series/buffer-serie.rb +10 -5
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +6 -3
- data/lib/musa-dsl/series/main-serie-constructors.rb +19 -15
- data/lib/musa-dsl/series/main-serie-operations.rb +74 -29
- data/lib/musa-dsl/series/proxy-serie.rb +5 -1
- data/lib/musa-dsl/series/quantizer-serie.rb +4 -2
- data/lib/musa-dsl/series/queue-serie.rb +2 -1
- data/lib/musa-dsl/series/series-composer.rb +5 -2
- data/lib/musa-dsl/series/timed-serie.rb +8 -4
- data/lib/musa-dsl/transport/timer-clock.rb +4 -2
- data/lib/musa-dsl/transport/timer.rb +27 -4
- data/lib/musa-dsl/version.rb +1 -2
- data/musa-dsl.gemspec +0 -2
- metadata +1 -1
|
@@ -104,7 +104,7 @@ module Musa
|
|
|
104
104
|
# Makes the scale system available via symbol lookup and dynamic method.
|
|
105
105
|
# Optionally marks it as the default system.
|
|
106
106
|
#
|
|
107
|
-
# @param scale_system [Class] the ScaleSystem subclass to register
|
|
107
|
+
# @param scale_system [Class<ScaleSystem>] the ScaleSystem subclass to register
|
|
108
108
|
# @param default [Boolean] whether to set as default system
|
|
109
109
|
# @return [self]
|
|
110
110
|
#
|
|
@@ -126,20 +126,24 @@ module Musa
|
|
|
126
126
|
# Retrieves a registered scale system by ID.
|
|
127
127
|
#
|
|
128
128
|
# @param id [Symbol] the scale system identifier
|
|
129
|
-
# @return [Class] the ScaleSystem subclass
|
|
129
|
+
# @return [Class<ScaleSystem>] the ScaleSystem subclass
|
|
130
130
|
# @raise [KeyError] if scale system not found
|
|
131
131
|
#
|
|
132
132
|
# @example
|
|
133
133
|
# Scales[:et12] # => EquallyTempered12ToneScaleSystem
|
|
134
|
-
def self.
|
|
134
|
+
def self.get(id)
|
|
135
135
|
raise KeyError, "Scale system :#{id} not found" unless @scale_systems.key?(id)
|
|
136
136
|
|
|
137
137
|
@scale_systems[id]
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
+
class << self
|
|
141
|
+
alias_method :[], :get
|
|
142
|
+
end
|
|
143
|
+
|
|
140
144
|
# Returns the default scale system.
|
|
141
145
|
#
|
|
142
|
-
# @return [Class] the default ScaleSystem subclass
|
|
146
|
+
# @return [Class<ScaleSystem>] the default ScaleSystem subclass
|
|
143
147
|
#
|
|
144
148
|
# @example
|
|
145
149
|
# Scales.default_system # => EquallyTempered12ToneScaleSystem
|
|
@@ -319,7 +323,7 @@ module Musa
|
|
|
319
323
|
#
|
|
320
324
|
# @example Modern high pitch
|
|
321
325
|
# modern = ScaleSystem[442.0]
|
|
322
|
-
def self.
|
|
326
|
+
def self.get(a_frequency)
|
|
323
327
|
a_frequency = a_frequency.to_f
|
|
324
328
|
|
|
325
329
|
@a_tunings ||= {}
|
|
@@ -328,6 +332,10 @@ module Musa
|
|
|
328
332
|
@a_tunings[a_frequency]
|
|
329
333
|
end
|
|
330
334
|
|
|
335
|
+
class << self
|
|
336
|
+
alias_method :[], :get
|
|
337
|
+
end
|
|
338
|
+
|
|
331
339
|
# Returns semitone offset for a named interval.
|
|
332
340
|
#
|
|
333
341
|
# @param name [Symbol] interval name (e.g., :M3, :P5)
|
|
@@ -351,7 +359,7 @@ module Musa
|
|
|
351
359
|
|
|
352
360
|
# Registers a scale kind (major, minor, etc.) with this system.
|
|
353
361
|
#
|
|
354
|
-
# @param scale_kind_class [Class] ScaleKind subclass to register
|
|
362
|
+
# @param scale_kind_class [Class<ScaleKind>] ScaleKind subclass to register
|
|
355
363
|
# @return [self]
|
|
356
364
|
#
|
|
357
365
|
# @example
|
|
@@ -368,7 +376,7 @@ module Musa
|
|
|
368
376
|
# Retrieves a registered scale kind by ID.
|
|
369
377
|
#
|
|
370
378
|
# @param id [Symbol] scale kind identifier
|
|
371
|
-
# @return [Class] ScaleKind subclass
|
|
379
|
+
# @return [Class<ScaleKind>] ScaleKind subclass
|
|
372
380
|
# @raise [KeyError] if not found
|
|
373
381
|
def self.scale_kind_class(id)
|
|
374
382
|
raise KeyError, "Scale kind class [#{id}] not found in scale system [#{self.id}]" unless @scale_kind_classes.key? id
|
|
@@ -393,7 +401,7 @@ module Musa
|
|
|
393
401
|
|
|
394
402
|
# Returns the chromatic scale kind class.
|
|
395
403
|
#
|
|
396
|
-
# @return [Class] chromatic ScaleKind subclass
|
|
404
|
+
# @return [Class<ScaleKind>] chromatic ScaleKind subclass
|
|
397
405
|
# @raise [RuntimeError] if chromatic scale not defined
|
|
398
406
|
def self.chromatic_class
|
|
399
407
|
raise "Chromatic scale kind class for [#{self.id}] scale system undefined" if @chromatic_scale_kind_class.nil?
|
|
@@ -452,6 +460,12 @@ module Musa
|
|
|
452
460
|
class ScaleSystemTuning
|
|
453
461
|
extend Forwardable
|
|
454
462
|
|
|
463
|
+
# Creates a tuning instance for a scale system.
|
|
464
|
+
#
|
|
465
|
+
# @param scale_system [Class<ScaleSystem>] the ScaleSystem subclass
|
|
466
|
+
# @param a_frequency [Numeric] reference A frequency in Hz
|
|
467
|
+
#
|
|
468
|
+
# @api private
|
|
455
469
|
def initialize(scale_system, a_frequency)
|
|
456
470
|
@scale_system = scale_system
|
|
457
471
|
@a_frequency = a_frequency
|
|
@@ -468,18 +482,75 @@ module Musa
|
|
|
468
482
|
|
|
469
483
|
# 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
|
|
470
484
|
|
|
485
|
+
# @!method notes_in_octave
|
|
486
|
+
# Returns the number of notes in one octave.
|
|
487
|
+
# Delegated from {ScaleSystem.notes_in_octave}.
|
|
488
|
+
# @return [Integer] notes per octave (e.g., 12 for chromatic)
|
|
489
|
+
|
|
490
|
+
# @!method offset_of_interval(name)
|
|
491
|
+
# Returns semitone offset for a named interval.
|
|
492
|
+
# Delegated from {ScaleSystem.offset_of_interval}.
|
|
493
|
+
# @param name [Symbol] interval name (e.g., :M3, :P5)
|
|
494
|
+
# @return [Integer] semitone offset
|
|
495
|
+
|
|
471
496
|
def_delegators :@scale_system, :notes_in_octave, :offset_of_interval
|
|
472
497
|
|
|
473
|
-
|
|
498
|
+
# Reference A frequency in Hz.
|
|
499
|
+
# @return [Float]
|
|
500
|
+
attr_reader :a_frequency
|
|
501
|
+
|
|
502
|
+
# The parent scale system.
|
|
503
|
+
# @return [Class<ScaleSystem>] ScaleSystem subclass
|
|
504
|
+
attr_reader :scale_system
|
|
474
505
|
|
|
475
|
-
|
|
506
|
+
# Retrieves a scale kind by ID.
|
|
507
|
+
#
|
|
508
|
+
# Creates and caches {ScaleKind} instances for efficient reuse.
|
|
509
|
+
#
|
|
510
|
+
# @param scale_kind_class_id [Symbol] scale kind identifier (e.g., :major, :minor)
|
|
511
|
+
# @return [ScaleKind] scale kind instance
|
|
512
|
+
# @raise [KeyError] if scale kind not found
|
|
513
|
+
#
|
|
514
|
+
# @example
|
|
515
|
+
# tuning[:major][60] # C major scale
|
|
516
|
+
# tuning[:minor][69] # A minor scale
|
|
517
|
+
#
|
|
518
|
+
# @see ScaleKind
|
|
519
|
+
def get(scale_kind_class_id)
|
|
476
520
|
@scale_kinds[scale_kind_class_id] ||= @scale_system.scale_kind_class(scale_kind_class_id).new self
|
|
477
521
|
end
|
|
478
522
|
|
|
523
|
+
alias_method :[], :get
|
|
524
|
+
|
|
525
|
+
# Returns the chromatic scale kind.
|
|
526
|
+
#
|
|
527
|
+
# Provides access to the chromatic scale, which contains all pitches
|
|
528
|
+
# in the scale system. Used as fallback for non-diatonic notes.
|
|
529
|
+
#
|
|
530
|
+
# @return [ScaleKind] chromatic scale kind instance
|
|
531
|
+
#
|
|
532
|
+
# @example
|
|
533
|
+
# tuning.chromatic[60] # Chromatic scale at C
|
|
534
|
+
#
|
|
535
|
+
# @see ScaleSystem.chromatic_class
|
|
479
536
|
def chromatic
|
|
480
537
|
@chromatic_scale_kind
|
|
481
538
|
end
|
|
482
539
|
|
|
540
|
+
# Calculates frequency for a MIDI pitch.
|
|
541
|
+
#
|
|
542
|
+
# Delegates to the scale system's frequency calculation using
|
|
543
|
+
# this tuning's A frequency as reference.
|
|
544
|
+
#
|
|
545
|
+
# @param pitch [Numeric] MIDI pitch number (60 = middle C)
|
|
546
|
+
# @param root [Numeric] root pitch of the scale (for non-equal temperaments)
|
|
547
|
+
# @return [Float] frequency in Hz
|
|
548
|
+
#
|
|
549
|
+
# @example
|
|
550
|
+
# tuning.frequency_of_pitch(69, 60) # => 440.0 (A4)
|
|
551
|
+
# tuning.frequency_of_pitch(60, 60) # => ~261.63 (C4)
|
|
552
|
+
#
|
|
553
|
+
# @see ScaleSystem.frequency_of_pitch
|
|
483
554
|
def frequency_of_pitch(pitch, root)
|
|
484
555
|
@scale_system.frequency_of_pitch(pitch, root, @a_frequency)
|
|
485
556
|
end
|
|
@@ -492,7 +563,7 @@ module Musa
|
|
|
492
563
|
#
|
|
493
564
|
# @param metadata_criteria [Hash] metadata key-value pairs to match
|
|
494
565
|
# @yield [kind_class] optional block for custom filtering
|
|
495
|
-
# @yieldparam kind_class [Class] the ScaleKind subclass
|
|
566
|
+
# @yieldparam kind_class [Class<ScaleKind>] the ScaleKind subclass
|
|
496
567
|
# @yieldreturn [Boolean] true to include this scale kind
|
|
497
568
|
# @return [Array<ScaleKind>] matching scale kind instances
|
|
498
569
|
#
|
|
@@ -543,27 +614,33 @@ module Musa
|
|
|
543
614
|
# @example Search G7 in greek mode scales
|
|
544
615
|
# tuning = Scales.et12[440.0]
|
|
545
616
|
# g7 = tuning.major[60].dominant.chord :seventh
|
|
546
|
-
# tuning.
|
|
617
|
+
# tuning.search_chord_in_scales(g7, family: :greek_modes)
|
|
547
618
|
#
|
|
548
619
|
# @example Search with brightness filter
|
|
549
|
-
# tuning.
|
|
620
|
+
# tuning.search_chord_in_scales(g7, brightness: -1..1)
|
|
550
621
|
#
|
|
551
622
|
# @example Search in all scale types
|
|
552
|
-
# tuning.
|
|
623
|
+
# tuning.search_chord_in_scales(g7)
|
|
553
624
|
#
|
|
554
|
-
# @see ScaleKind#
|
|
555
|
-
# @see Musa::Chords::Chord#
|
|
556
|
-
def
|
|
625
|
+
# @see ScaleKind#find_chord_in_scales
|
|
626
|
+
# @see Musa::Chords::Chord#search_in_scales
|
|
627
|
+
def search_chord_in_scales(chord, roots: nil, **metadata_criteria)
|
|
557
628
|
roots ||= 0...notes_in_octave
|
|
558
629
|
kinds = filtered_scale_kind_ids(**metadata_criteria)
|
|
559
630
|
|
|
560
631
|
kinds.flat_map do |kind_id|
|
|
561
|
-
self[kind_id].
|
|
632
|
+
self[kind_id].find_chord_in_scales(chord, roots: roots)
|
|
562
633
|
end
|
|
563
634
|
end
|
|
564
635
|
|
|
565
636
|
private
|
|
566
637
|
|
|
638
|
+
# Returns scale kind IDs filtered by metadata criteria.
|
|
639
|
+
#
|
|
640
|
+
# @param metadata_criteria [Hash] key-value pairs to match
|
|
641
|
+
# @return [Array<Symbol>] matching scale kind IDs
|
|
642
|
+
#
|
|
643
|
+
# @api private
|
|
567
644
|
def filtered_scale_kind_ids(**metadata_criteria)
|
|
568
645
|
kinds = @scale_system.scale_kind_classes.keys
|
|
569
646
|
|
|
@@ -575,6 +652,13 @@ module Musa
|
|
|
575
652
|
end
|
|
576
653
|
end
|
|
577
654
|
|
|
655
|
+
# Checks if a scale kind class matches the given criteria.
|
|
656
|
+
#
|
|
657
|
+
# @param kind_class [Class<ScaleKind>] ScaleKind subclass to check
|
|
658
|
+
# @param criteria [Hash] metadata key-value pairs to match
|
|
659
|
+
# @return [Boolean] true if all criteria match
|
|
660
|
+
#
|
|
661
|
+
# @api private
|
|
578
662
|
def matches_metadata?(kind_class, criteria)
|
|
579
663
|
criteria.all? do |key, value|
|
|
580
664
|
actual = kind_class.metadata[key]
|
|
@@ -591,12 +675,21 @@ module Musa
|
|
|
591
675
|
|
|
592
676
|
public
|
|
593
677
|
|
|
678
|
+
# Checks tuning equality.
|
|
679
|
+
#
|
|
680
|
+
# Tunings are equal if they have the same scale system and A frequency.
|
|
681
|
+
#
|
|
682
|
+
# @param other [ScaleSystemTuning] the tuning to compare
|
|
683
|
+
# @return [Boolean] true if equal
|
|
594
684
|
def ==(other)
|
|
595
685
|
self.class == other.class &&
|
|
596
686
|
@scale_system == other.scale_system &&
|
|
597
687
|
@a_frequency == other.a_frequency
|
|
598
688
|
end
|
|
599
689
|
|
|
690
|
+
# Returns string representation.
|
|
691
|
+
#
|
|
692
|
+
# @return [String] human-readable description
|
|
600
693
|
def inspect
|
|
601
694
|
"<ScaleSystemTuning: scale_system = #{@scale_system} a_frequency = #{@a_frequency}>"
|
|
602
695
|
end
|
|
@@ -684,11 +777,13 @@ module Musa
|
|
|
684
777
|
# major_kind = tuning[:major]
|
|
685
778
|
# c_major = major_kind[60] # C major
|
|
686
779
|
# g_major = major_kind[67] # G major
|
|
687
|
-
def
|
|
780
|
+
def get(root_pitch)
|
|
688
781
|
@scales[root_pitch] = Scale.new(self, root_pitch: root_pitch) unless @scales.key?(root_pitch)
|
|
689
782
|
@scales[root_pitch]
|
|
690
783
|
end
|
|
691
784
|
|
|
785
|
+
alias_method :[], :get
|
|
786
|
+
|
|
692
787
|
# Returns scale with default root (middle C, MIDI 60).
|
|
693
788
|
#
|
|
694
789
|
# @return [Scale] scale rooted on middle C
|
|
@@ -722,19 +817,19 @@ module Musa
|
|
|
722
817
|
# @example Find G major triad in all major scales
|
|
723
818
|
# tuning = Scales.et12[440.0]
|
|
724
819
|
# g_triad = tuning.major[60].dominant.chord
|
|
725
|
-
# tuning.major.
|
|
820
|
+
# tuning.major.find_chord_in_scales(g_triad)
|
|
726
821
|
# # => [Chord in C major (V), Chord in G major (I), Chord in D major (IV)]
|
|
727
822
|
#
|
|
728
|
-
# @see
|
|
729
|
-
# @see ScaleSystemTuning#
|
|
730
|
-
def
|
|
823
|
+
# @see Musa::Chords::Chord#as_chord_in_scale
|
|
824
|
+
# @see ScaleSystemTuning#search_chord_in_scales
|
|
825
|
+
def find_chord_in_scales(chord, roots: nil)
|
|
731
826
|
roots ||= 0...tuning.notes_in_octave
|
|
732
827
|
base_pitch = chord.root.pitch % tuning.notes_in_octave
|
|
733
828
|
|
|
734
829
|
roots.filter_map do |root_offset|
|
|
735
830
|
root_pitch = base_pitch + root_offset
|
|
736
831
|
scale = self[root_pitch]
|
|
737
|
-
|
|
832
|
+
chord.as_chord_in_scale(scale)
|
|
738
833
|
end
|
|
739
834
|
end
|
|
740
835
|
|
|
@@ -1091,9 +1186,9 @@ module Musa
|
|
|
1091
1186
|
# scale[:V] # Fifth degree
|
|
1092
1187
|
# scale[:IV] # Fourth degree
|
|
1093
1188
|
#
|
|
1094
|
-
# **With accidentals** (sharp # or flat _)
|
|
1189
|
+
# **With accidentals** (sharp # or flat _). Use strings for #:
|
|
1095
1190
|
#
|
|
1096
|
-
# scale[
|
|
1191
|
+
# scale['I#'] # Raised tonic
|
|
1097
1192
|
# scale[:V_] # Flatted dominant
|
|
1098
1193
|
# scale['II##'] # Double-raised second
|
|
1099
1194
|
#
|
|
@@ -1120,8 +1215,8 @@ module Musa
|
|
|
1120
1215
|
# c_major.dominant.pitch # => 67 (G)
|
|
1121
1216
|
# c_major[:III].pitch # => 64 (E)
|
|
1122
1217
|
#
|
|
1123
|
-
# @example Chromatic alterations
|
|
1124
|
-
# c_major[
|
|
1218
|
+
# @example Chromatic alterations (use strings for #)
|
|
1219
|
+
# c_major['I#'].pitch # => 61 (C#)
|
|
1125
1220
|
# c_major[:V_].pitch # => 66 (F#/Gb)
|
|
1126
1221
|
#
|
|
1127
1222
|
# @example Building chords
|
|
@@ -1156,7 +1251,16 @@ module Musa
|
|
|
1156
1251
|
freeze
|
|
1157
1252
|
end
|
|
1158
1253
|
|
|
1159
|
-
#
|
|
1254
|
+
# @!method tuning
|
|
1255
|
+
# Returns the tuning system associated with this scale.
|
|
1256
|
+
#
|
|
1257
|
+
# Delegated from ScaleKind#tuning.
|
|
1258
|
+
#
|
|
1259
|
+
# @return [ScaleSystemTuning] the tuning system used by this scale
|
|
1260
|
+
#
|
|
1261
|
+
# @example
|
|
1262
|
+
# scale = Scales.et12[440.0].major[60]
|
|
1263
|
+
# scale.tuning # => ScaleSystemTuning for 12-TET at A=440Hz
|
|
1160
1264
|
def_delegators :@kind, :tuning
|
|
1161
1265
|
|
|
1162
1266
|
# Scale kind (major, minor, etc.).
|
|
@@ -1241,11 +1345,11 @@ module Musa
|
|
|
1241
1345
|
# scale[:V] # Dominant
|
|
1242
1346
|
# scale[:IV] # Subdominant
|
|
1243
1347
|
#
|
|
1244
|
-
# @example With accidentals
|
|
1245
|
-
# scale[
|
|
1348
|
+
# @example With accidentals (use strings for #)
|
|
1349
|
+
# scale['I#'] # Raised tonic
|
|
1246
1350
|
# scale[:V_] # Flatted dominant
|
|
1247
1351
|
# scale['II##'] # Double-raised second
|
|
1248
|
-
def
|
|
1352
|
+
def get(grade_or_symbol)
|
|
1249
1353
|
|
|
1250
1354
|
raise ArgumentError, "grade_or_symbol '#{grade_or_symbol}' should be a Integer, String or Symbol" unless grade_or_symbol.is_a?(Symbol) || grade_or_symbol.is_a?(String) || grade_or_symbol.is_a?(Integer)
|
|
1251
1355
|
|
|
@@ -1269,6 +1373,8 @@ module Musa
|
|
|
1269
1373
|
@notes_by_grade[wide_grade].sharp(sharps)
|
|
1270
1374
|
end
|
|
1271
1375
|
|
|
1376
|
+
alias_method :[], :get
|
|
1377
|
+
|
|
1272
1378
|
# Converts grade specifier to numeric grade and accidentals.
|
|
1273
1379
|
#
|
|
1274
1380
|
# @param grade_or_string_or_symbol [Integer, Symbol, String] grade specifier
|
|
@@ -1431,38 +1537,45 @@ module Musa
|
|
|
1431
1537
|
note&.grade
|
|
1432
1538
|
end
|
|
1433
1539
|
|
|
1434
|
-
# Creates
|
|
1540
|
+
# Creates a chord rooted on the specified scale degree.
|
|
1435
1541
|
#
|
|
1436
|
-
#
|
|
1437
|
-
#
|
|
1438
|
-
# duplicate settings) is preserved.
|
|
1542
|
+
# This is a convenience method that combines scale note access with
|
|
1543
|
+
# chord creation. It's equivalent to `scale[grade].chord(...)`.
|
|
1439
1544
|
#
|
|
1440
|
-
# @param
|
|
1441
|
-
# @
|
|
1545
|
+
# @param grade [Integer, Symbol, String] scale degree (0-based numeric, function name like :tonic, or Roman numeral like :V)
|
|
1546
|
+
# @param feature_values [Array<Symbol>] chord feature values (:seventh, :major, etc.)
|
|
1547
|
+
# @param allow_chromatic [Boolean] allow non-diatonic chord notes
|
|
1548
|
+
# @param move [Hash{Symbol => Integer}] initial octave moves for chord tones
|
|
1549
|
+
# @param duplicate [Hash{Symbol => Integer, Array}] initial duplications
|
|
1550
|
+
# @param features_hash [Hash] additional feature key-value pairs
|
|
1551
|
+
# @return [Chords::Chord] chord rooted on the specified degree
|
|
1442
1552
|
#
|
|
1443
|
-
# @example
|
|
1444
|
-
#
|
|
1445
|
-
#
|
|
1553
|
+
# @example Create triads
|
|
1554
|
+
# scale.chord_on(0) # Tonic triad (I)
|
|
1555
|
+
# scale.chord_on(:dominant) # Dominant triad (V)
|
|
1556
|
+
# scale.chord_on(:IV) # Subdominant triad
|
|
1446
1557
|
#
|
|
1447
|
-
#
|
|
1448
|
-
#
|
|
1449
|
-
#
|
|
1558
|
+
# @example Create extended chords
|
|
1559
|
+
# scale.chord_on(4, :seventh) # V7
|
|
1560
|
+
# scale.chord_on(:dominant, :ninth) # V9
|
|
1561
|
+
# scale.chord_on(0, :seventh, :major) # Imaj7
|
|
1450
1562
|
#
|
|
1451
|
-
# @
|
|
1452
|
-
#
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1563
|
+
# @example With voicing
|
|
1564
|
+
# scale.chord_on(:I, :seventh, move: {root: -1})
|
|
1565
|
+
# scale.chord_on(0, :triad, duplicate: {root: 1})
|
|
1566
|
+
#
|
|
1567
|
+
# @see NoteInScale#chord
|
|
1568
|
+
# @see #get
|
|
1569
|
+
def chord_on(grade, *feature_values,
|
|
1570
|
+
allow_chromatic: nil,
|
|
1571
|
+
move: nil,
|
|
1572
|
+
duplicate: nil,
|
|
1573
|
+
**features_hash)
|
|
1574
|
+
self[grade].chord(*feature_values,
|
|
1575
|
+
allow_chromatic: allow_chromatic,
|
|
1576
|
+
move: move,
|
|
1577
|
+
duplicate: duplicate,
|
|
1578
|
+
**features_hash)
|
|
1466
1579
|
end
|
|
1467
1580
|
|
|
1468
1581
|
# Checks scale equality.
|
|
@@ -1536,9 +1649,10 @@ module Musa
|
|
|
1536
1649
|
#
|
|
1537
1650
|
# ## Scale Navigation
|
|
1538
1651
|
#
|
|
1539
|
-
# note.scale
|
|
1540
|
-
# note.
|
|
1541
|
-
# note.
|
|
1652
|
+
# note.scale # Parent scale this note belongs to
|
|
1653
|
+
# note.as_root_of(:minor) # New minor scale with this pitch as root
|
|
1654
|
+
# note.minor # Same as note.as_root_of(:minor)
|
|
1655
|
+
# note.chromatic # Same as note.as_root_of(:chromatic)
|
|
1542
1656
|
#
|
|
1543
1657
|
# ## Chord Construction
|
|
1544
1658
|
#
|
|
@@ -1602,7 +1716,7 @@ module Musa
|
|
|
1602
1716
|
|
|
1603
1717
|
@scale.kind.tuning.scale_system.scale_kind_classes.each_key do |name|
|
|
1604
1718
|
define_singleton_method name do
|
|
1605
|
-
|
|
1719
|
+
as_root_of(name)
|
|
1606
1720
|
end
|
|
1607
1721
|
end
|
|
1608
1722
|
end
|
|
@@ -1625,34 +1739,27 @@ module Musa
|
|
|
1625
1739
|
@scale.kind.class.pitches[grade][:functions]
|
|
1626
1740
|
end
|
|
1627
1741
|
|
|
1628
|
-
#
|
|
1629
|
-
#
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
#
|
|
1742
|
+
# Current octave relative to scale root.
|
|
1743
|
+
# @return [Integer]
|
|
1744
|
+
attr_reader :octave
|
|
1745
|
+
|
|
1746
|
+
# Returns note transposed by octave offset.
|
|
1633
1747
|
#
|
|
1634
|
-
# @param
|
|
1748
|
+
# @param offset [Integer] octave offset (positive = up, negative = down)
|
|
1635
1749
|
# @param absolute [Boolean] if true, ignore current octave
|
|
1636
|
-
# @return [
|
|
1637
|
-
# @raise [ArgumentError] if
|
|
1638
|
-
#
|
|
1639
|
-
# @example Query octave
|
|
1640
|
-
# note.octave # => 0 (at scale root octave)
|
|
1750
|
+
# @return [NoteInScale] transposed note
|
|
1751
|
+
# @raise [ArgumentError] if offset is not integer
|
|
1641
1752
|
#
|
|
1642
1753
|
# @example Transpose relative
|
|
1643
|
-
# note.
|
|
1644
|
-
# note.
|
|
1754
|
+
# note.at_octave(1).pitch # Up one octave from current
|
|
1755
|
+
# note.at_octave(-1).pitch # Down one octave from current
|
|
1645
1756
|
#
|
|
1646
1757
|
# @example Transpose absolute
|
|
1647
|
-
# note.
|
|
1648
|
-
def
|
|
1649
|
-
|
|
1650
|
-
@octave
|
|
1651
|
-
else
|
|
1652
|
-
raise ArgumentError, "#{octave} is not integer" unless octave == octave.to_i
|
|
1758
|
+
# note.at_octave(2, absolute: true).pitch # At octave 2, regardless of current
|
|
1759
|
+
def at_octave(offset, absolute: false)
|
|
1760
|
+
raise ArgumentError, "#{offset} is not integer" unless offset == offset.to_i
|
|
1653
1761
|
|
|
1654
|
-
|
|
1655
|
-
end
|
|
1762
|
+
@scale[@grade + ((absolute ? 0 : @octave) + offset) * @scale.kind.class.grades]
|
|
1656
1763
|
end
|
|
1657
1764
|
|
|
1658
1765
|
# Creates a copy with background scale context.
|
|
@@ -1747,6 +1854,13 @@ module Musa
|
|
|
1747
1854
|
end
|
|
1748
1855
|
end
|
|
1749
1856
|
|
|
1857
|
+
# Calculates note at given pitch offset from current note.
|
|
1858
|
+
#
|
|
1859
|
+
# @param in_scale_pitch [Numeric] base pitch
|
|
1860
|
+
# @param sharps [Integer] number of semitones to add (negative for flats)
|
|
1861
|
+
# @return [NoteInScale] resulting note
|
|
1862
|
+
#
|
|
1863
|
+
# @api private
|
|
1750
1864
|
def calculate_note_of_pitch(in_scale_pitch, sharps)
|
|
1751
1865
|
pitch = in_scale_pitch + sharps * @scale.kind.tuning.scale_system.part_of_tone_size
|
|
1752
1866
|
|
|
@@ -1818,33 +1932,31 @@ module Musa
|
|
|
1818
1932
|
@scale.kind.tuning.frequency_of_pitch(@pitch, @scale.root_pitch)
|
|
1819
1933
|
end
|
|
1820
1934
|
|
|
1821
|
-
#
|
|
1822
|
-
#
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
#
|
|
1935
|
+
# Parent scale this note belongs to.
|
|
1936
|
+
# @return [Scale]
|
|
1937
|
+
attr_reader :scale
|
|
1938
|
+
|
|
1939
|
+
# Creates a new scale with this note's pitch as the root.
|
|
1826
1940
|
#
|
|
1827
|
-
# @param kind_id_or_kind [Symbol, ScaleKind
|
|
1828
|
-
# @return [Scale
|
|
1941
|
+
# @param kind_id_or_kind [Symbol, ScaleKind] scale kind or ID
|
|
1942
|
+
# @return [Scale] new scale rooted at this pitch
|
|
1829
1943
|
#
|
|
1830
|
-
# @example
|
|
1831
|
-
#
|
|
1944
|
+
# @example Create minor scale from a note
|
|
1945
|
+
# e = c_major[64] # E in C major
|
|
1946
|
+
# e_minor = e.as_root_of(:minor) # E minor scale
|
|
1832
1947
|
#
|
|
1833
|
-
# @example
|
|
1834
|
-
#
|
|
1948
|
+
# @example With ScaleKind object
|
|
1949
|
+
# minor_kind = tuning[:minor]
|
|
1950
|
+
# e_minor = e.as_root_of(minor_kind)
|
|
1835
1951
|
#
|
|
1836
|
-
# @example Dynamic method
|
|
1837
|
-
# note.minor # Same as note.
|
|
1838
|
-
# note.major # Same as note.
|
|
1839
|
-
def
|
|
1840
|
-
if kind_id_or_kind.
|
|
1841
|
-
@
|
|
1952
|
+
# @example Dynamic method (equivalent)
|
|
1953
|
+
# note.minor # Same as note.as_root_of(:minor)
|
|
1954
|
+
# note.major # Same as note.as_root_of(:major)
|
|
1955
|
+
def as_root_of(kind_id_or_kind)
|
|
1956
|
+
if kind_id_or_kind.is_a? ScaleKind
|
|
1957
|
+
kind_id_or_kind[@pitch]
|
|
1842
1958
|
else
|
|
1843
|
-
|
|
1844
|
-
kind_id_or_kind[@pitch]
|
|
1845
|
-
else
|
|
1846
|
-
@scale.kind.tuning[kind_id_or_kind][@pitch]
|
|
1847
|
-
end
|
|
1959
|
+
@scale.kind.tuning[kind_id_or_kind][@pitch]
|
|
1848
1960
|
end
|
|
1849
1961
|
end
|
|
1850
1962
|
|
|
@@ -1877,7 +1989,7 @@ module Musa
|
|
|
1877
1989
|
# @param move [Hash{Symbol => Integer}] initial octave moves
|
|
1878
1990
|
# @param duplicate [Hash{Symbol => Integer, Array<Integer>}] initial duplications
|
|
1879
1991
|
# @param features_hash [Hash] feature key-value pairs
|
|
1880
|
-
# @return [Chord] chord rooted on this note
|
|
1992
|
+
# @return [Chords::Chord] chord rooted on this note
|
|
1881
1993
|
#
|
|
1882
1994
|
# @example Default triad
|
|
1883
1995
|
# note.chord # Major triad
|