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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/README.md +15 -1
  4. data/docs/README.md +1 -0
  5. data/docs/subsystems/datasets.md +75 -0
  6. data/docs/subsystems/generative.md +92 -6
  7. data/docs/subsystems/music.md +33 -14
  8. data/docs/subsystems/transport.md +26 -0
  9. data/lib/musa-dsl/datasets/dataset.rb +2 -0
  10. data/lib/musa-dsl/datasets/gdv.rb +3 -3
  11. data/lib/musa-dsl/datasets/p.rb +1 -1
  12. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +4 -2
  13. data/lib/musa-dsl/datasets/score.rb +3 -1
  14. data/lib/musa-dsl/generative/generative-grammar.rb +3 -1
  15. data/lib/musa-dsl/generative/markov.rb +1 -1
  16. data/lib/musa-dsl/midi/midi-voices.rb +3 -1
  17. data/lib/musa-dsl/music/chord-definition.rb +7 -5
  18. data/lib/musa-dsl/music/chord-definitions.rb +37 -0
  19. data/lib/musa-dsl/music/chords.rb +69 -47
  20. data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +1 -1
  21. data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +1 -1
  22. data/lib/musa-dsl/music/scales.rb +219 -107
  23. data/lib/musa-dsl/musicxml/builder/note.rb +31 -92
  24. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +33 -94
  25. data/lib/musa-dsl/musicxml/builder/rest.rb +30 -91
  26. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +31 -91
  27. data/lib/musa-dsl/neumas/array-to-neumas.rb +1 -1
  28. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +2 -2
  29. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +367 -3
  30. data/lib/musa-dsl/series/base-series.rb +250 -240
  31. data/lib/musa-dsl/series/buffer-serie.rb +10 -5
  32. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +6 -3
  33. data/lib/musa-dsl/series/main-serie-constructors.rb +19 -15
  34. data/lib/musa-dsl/series/main-serie-operations.rb +74 -29
  35. data/lib/musa-dsl/series/proxy-serie.rb +5 -1
  36. data/lib/musa-dsl/series/quantizer-serie.rb +4 -2
  37. data/lib/musa-dsl/series/queue-serie.rb +2 -1
  38. data/lib/musa-dsl/series/series-composer.rb +5 -2
  39. data/lib/musa-dsl/series/timed-serie.rb +8 -4
  40. data/lib/musa-dsl/transport/timer-clock.rb +4 -2
  41. data/lib/musa-dsl/transport/timer.rb +27 -4
  42. data/lib/musa-dsl/version.rb +1 -2
  43. data/musa-dsl.gemspec +0 -2
  44. metadata +1 -1
@@ -47,13 +47,15 @@ module Musa
47
47
  #
48
48
  # ### Move - Relocate specific chord tones to different octaves:
49
49
  #
50
- # chord.move(root: -1, seventh: 1)
50
+ # chord.with_move(root: -1, seventh: 1)
51
51
  # # Root down one octave, seventh up one octave
52
+ # chord.move # => { root: -1, seventh: 1 } (current settings)
52
53
  #
53
54
  # ### Duplicate - Add copies of chord tones in other octaves:
54
55
  #
55
- # chord.duplicate(root: -2, third: [-1, 1])
56
+ # chord.with_duplicate(root: -2, third: [-1, 1])
56
57
  # # Add root 2 octaves down, third 1 octave down and 1 up
58
+ # chord.duplicate # => { root: -2, third: [-1, 1] } (current settings)
57
59
  #
58
60
  # ### Octave - Transpose entire chord:
59
61
  #
@@ -90,8 +92,8 @@ module Musa
90
92
  # @example Voicing with move and duplicate
91
93
  # scale = Scales::Scales.default_system.default_tuning.major[60]
92
94
  # chord = scale.dominant.chord(:seventh)
93
- # .move(root: -1, third: -1)
94
- # .duplicate(fifth: [0, 1])
95
+ # .with_move(root: -1, third: -1)
96
+ # .with_duplicate(fifth: [0, 1])
95
97
  #
96
98
  # @example Feature navigation
97
99
  # scale = Scales::Scales.default_system.default_tuning.major[60]
@@ -194,9 +196,9 @@ module Musa
194
196
  # Maps each chord position (root, third, fifth, etc.) to its corresponding
195
197
  # note in the scale or chromatic scale.
196
198
  #
197
- # @param root [NoteInScale] chord root note
199
+ # @param root [Scales::NoteInScale] chord root note
198
200
  # @param chord_definition [ChordDefinition] chord structure
199
- # @param scale [Scale] scale context
201
+ # @param scale [Scales::Scale] scale context
200
202
  # @return [Hash{Symbol => Array<NoteInScale>}] position to notes mapping
201
203
  #
202
204
  # @api private
@@ -214,7 +216,7 @@ module Musa
214
216
  #
215
217
  # @param root_pitch [Integer] MIDI pitch of chord root
216
218
  # @param features [Hash] desired chord features (quality:, size:, etc.)
217
- # @param scale [Scale] scale context for diatonic filtering
219
+ # @param scale [Scales::Scale] scale context for diatonic filtering
218
220
  # @param allow_chromatic [Boolean] allow non-diatonic chords
219
221
  # @return [ChordDefinition, nil] matching definition or nil
220
222
  #
@@ -242,7 +244,7 @@ module Musa
242
244
  # @!attribute grade
243
245
  # @return [Symbol] position name (:root, :third, :fifth, etc.)
244
246
  # @!attribute note
245
- # @return [NoteInScale] the note at this position
247
+ # @return [Scales::NoteInScale] the note at this position
246
248
  #
247
249
  # @api private
248
250
  ChordGradeNote = Struct.new(:grade, :note, keyword_init: true)
@@ -253,8 +255,8 @@ module Musa
253
255
  #
254
256
  # Use {with_root} or create chords from scale notes instead.
255
257
  #
256
- # @param root [NoteInScale] chord root note
257
- # @param scale [Scale, nil] scale context (nil if chromatic notes present)
258
+ # @param root [Scales::NoteInScale] chord root note
259
+ # @param scale [Scales::Scale, nil] scale context (nil if chromatic notes present)
258
260
  # @param chord_definition [ChordDefinition] chord structure
259
261
  # @param move [Hash, nil] octave moves for positions
260
262
  # @param duplicate [Hash, nil] octave duplications for positions
@@ -423,62 +425,48 @@ module Musa
423
425
  # chord.octave(2)
424
426
  def octave(octave)
425
427
  source_notes_map = @source_notes_map.transform_values do |notes|
426
- notes.collect { |note| note.octave(octave) }.freeze
428
+ notes.collect { |note| note.at_octave(octave) }.freeze
427
429
  end.freeze
428
430
 
429
- Chord.new(@root.octave(octave), @scale, chord_definition, @move, @duplicate, source_notes_map)
431
+ Chord.new(@root.at_octave(octave), @scale, chord_definition, @move, @duplicate, source_notes_map)
430
432
  end
431
433
 
432
- # Creates new chord with positions moved to different octaves, or returns current move settings.
434
+ # Creates new chord with positions moved to different octaves.
433
435
  #
434
- # When called with arguments, relocates specific chord positions to different
435
- # octaves while keeping other positions unchanged. Multiple positions can be
436
- # moved at once. Merges with existing moves.
437
- #
438
- # When called without arguments, returns the current move settings hash.
436
+ # Relocates specific chord positions to different octaves while keeping
437
+ # other positions unchanged. Multiple positions can be moved at once.
438
+ # Merges with existing moves.
439
439
  #
440
440
  # @param octaves [Hash{Symbol => Integer}] position to octave offset mapping
441
- # @return [Chord, Hash] new chord with moved positions, or current move settings if no arguments
441
+ # @return [Chord] new chord with moved positions
442
442
  #
443
443
  # @example Move root down, seventh up
444
- # chord.move(root: -1, seventh: 1)
444
+ # chord.with_move(root: -1, seventh: 1)
445
445
  #
446
446
  # @example Drop voicing (move third and seventh down)
447
- # chord.move(third: -1, seventh: -1)
448
- #
449
- # @example Get current move settings
450
- # chord.move # => { root: -1 }
451
- def move(**octaves)
452
- return @move if octaves.empty?
453
-
447
+ # chord.with_move(third: -1, seventh: -1)
448
+ def with_move(**octaves)
454
449
  Chord.new(@root, @scale, @chord_definition, @move.merge(octaves), @duplicate, @source_notes_map)
455
450
  end
456
451
 
457
- # Creates new chord with positions duplicated in other octaves, or returns current duplicate settings.
452
+ # Creates new chord with positions duplicated in other octaves.
458
453
  #
459
- # When called with arguments, adds copies of specific chord positions in
460
- # different octaves. Original positions remain at their current octave.
454
+ # Adds copies of specific chord positions in different octaves.
455
+ # Original positions remain at their current octave.
461
456
  # Merges with existing duplications.
462
457
  #
463
- # When called without arguments, returns the current duplicate settings hash.
464
- #
465
458
  # @param octaves [Hash{Symbol => Integer, Array<Integer>}] position to octave(s)
466
- # @return [Chord, Hash] new chord with duplicated positions, or current duplicate settings if no arguments
459
+ # @return [Chord] new chord with duplicated positions
467
460
  #
468
461
  # @example Duplicate root two octaves down
469
- # chord.duplicate(root: -2)
462
+ # chord.with_duplicate(root: -2)
470
463
  #
471
464
  # @example Duplicate third in multiple octaves
472
- # chord.duplicate(third: [-1, 1])
465
+ # chord.with_duplicate(third: [-1, 1])
473
466
  #
474
467
  # @example Duplicate multiple positions
475
- # chord.duplicate(root: -1, fifth: 1)
476
- #
477
- # @example Get current duplicate settings
478
- # chord.duplicate # => { root: -1 }
479
- def duplicate(**octaves)
480
- return @duplicate if octaves.empty?
481
-
468
+ # chord.with_duplicate(root: -1, fifth: 1)
469
+ def with_duplicate(**octaves)
482
470
  Chord.new(@root, @scale, @chord_definition, @move, @duplicate.merge(octaves), @source_notes_map)
483
471
  end
484
472
 
@@ -507,10 +495,44 @@ module Musa
507
495
  # end
508
496
  #
509
497
  # @see Musa::Scales::Scale#chord_on
510
- # @see Musa::Scales::ScaleSystemTuning#chords_of
511
- def in_scales(roots: nil, **metadata)
498
+ # @see Musa::Scales::ScaleSystemTuning#search_chord_in_scales
499
+ def search_in_scales(roots: nil, **metadata)
512
500
  tuning = @scale&.kind&.tuning || @root.scale.kind.tuning
513
- tuning.chords_of(self, roots: roots, **metadata)
501
+ tuning.search_chord_in_scales(self, roots: roots, **metadata)
502
+ end
503
+
504
+ # Creates an equivalent chord with the given scale as its context.
505
+ #
506
+ # Returns a new Chord object representing the same chord but with
507
+ # the specified scale as its harmonic context. Returns nil if the
508
+ # chord is not contained in the scale.
509
+ #
510
+ # @param scale [Musa::Scales::Scale] the target scale
511
+ # @return [Chord, nil] new chord with target scale, or nil if not contained
512
+ #
513
+ # @example
514
+ # c_major = Scales.et12[440.0].major[60]
515
+ # g7 = c_major.dominant.chord :seventh
516
+ #
517
+ # g_mixolydian = Scales.et12[440.0].mixolydian[67]
518
+ # g7_in_mixolydian = g7.as_chord_in_scale(g_mixolydian)
519
+ # g7_in_mixolydian.scale # => G Mixolydian scale
520
+ #
521
+ # @see #search_in_scales
522
+ # @see Musa::Scales::Scale#contains_chord?
523
+ def as_chord_in_scale(scale)
524
+ return nil unless scale.contains_chord?(self)
525
+
526
+ root_note = scale.note_of_pitch(@root.pitch, allow_chromatic: false)
527
+ return nil unless root_note
528
+
529
+ Chord.with_root(
530
+ root_note,
531
+ scale: scale,
532
+ name: @chord_definition.name,
533
+ move: @move.empty? ? nil : @move,
534
+ duplicate: @duplicate.empty? ? nil : @duplicate
535
+ )
514
536
  end
515
537
 
516
538
  # Checks chord equality.
@@ -549,12 +571,12 @@ module Musa
549
571
  notes_map = notes_map.transform_values(&:dup)
550
572
 
551
573
  moved&.each do |position, octave|
552
- notes_map[position][0] = notes_map[position][0].octave(octave)
574
+ notes_map[position][0] = notes_map[position][0].at_octave(octave)
553
575
  end
554
576
 
555
577
  duplicated&.each do |position, octave|
556
578
  octave.arrayfy.each do |octave|
557
- notes_map[position] << notes_map[position][0].octave(octave)
579
+ notes_map[position] << notes_map[position][0].at_octave(octave)
558
580
  end
559
581
  end
560
582
 
@@ -43,7 +43,7 @@ module Musa
43
43
  # c_major.tonic # C (60)
44
44
  # c_major.dominant # G (67)
45
45
  # c_major.VI # A (69) - relative minor root
46
- # c_major.relative_minor.scale(:minor) # A minor scale
46
+ # c_major.relative_minor.as_root_of(:minor) # A minor scale
47
47
  #
48
48
  # @see ScaleKind Abstract base class
49
49
  # @see MinorNaturalScaleKind Natural minor scale
@@ -56,7 +56,7 @@ module Musa
56
56
  # a_minor.tonic # A (69)
57
57
  # a_minor.dominant # E (76)
58
58
  # a_minor.iii # C (72) - relative major root
59
- # a_minor.relative_major.scale(:major) # C major scale
59
+ # a_minor.relative_major.as_root_of(:major) # C major scale
60
60
  #
61
61
  # @see ScaleKind Abstract base class
62
62
  # @see MajorScaleKind Major scale