musa-dsl 0.40.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/Gemfile +0 -1
  4. data/README.md +15 -1
  5. data/docs/README.md +1 -0
  6. data/docs/subsystems/datasets.md +75 -0
  7. data/docs/subsystems/generative.md +92 -6
  8. data/docs/subsystems/music.md +349 -19
  9. data/docs/subsystems/transport.md +26 -0
  10. data/lib/musa-dsl/datasets/dataset.rb +2 -0
  11. data/lib/musa-dsl/datasets/gdv.rb +3 -3
  12. data/lib/musa-dsl/datasets/p.rb +1 -1
  13. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +4 -2
  14. data/lib/musa-dsl/datasets/score.rb +3 -1
  15. data/lib/musa-dsl/generative/darwin.rb +36 -1
  16. data/lib/musa-dsl/generative/generative-grammar.rb +31 -1
  17. data/lib/musa-dsl/generative/markov.rb +3 -1
  18. data/lib/musa-dsl/generative/rules.rb +54 -0
  19. data/lib/musa-dsl/generative/variatio.rb +69 -0
  20. data/lib/musa-dsl/midi/midi-recorder.rb +4 -0
  21. data/lib/musa-dsl/midi/midi-voices.rb +13 -1
  22. data/lib/musa-dsl/music/chord-definition.rb +7 -5
  23. data/lib/musa-dsl/music/chord-definitions.rb +37 -0
  24. data/lib/musa-dsl/music/chords.rb +88 -21
  25. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -521
  26. data/lib/musa-dsl/music/scale_kinds/bebop/bebop_dominant_scale_kind.rb +110 -0
  27. data/lib/musa-dsl/music/scale_kinds/bebop/bebop_major_scale_kind.rb +110 -0
  28. data/lib/musa-dsl/music/scale_kinds/bebop/bebop_minor_scale_kind.rb +110 -0
  29. data/lib/musa-dsl/music/scale_kinds/blues/blues_major_scale_kind.rb +100 -0
  30. data/lib/musa-dsl/music/scale_kinds/blues/blues_scale_kind.rb +99 -0
  31. data/lib/musa-dsl/music/scale_kinds/chromatic_scale_kind.rb +79 -0
  32. data/lib/musa-dsl/music/scale_kinds/ethnic/double_harmonic_scale_kind.rb +102 -0
  33. data/lib/musa-dsl/music/scale_kinds/ethnic/hungarian_minor_scale_kind.rb +102 -0
  34. data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_major_scale_kind.rb +102 -0
  35. data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_minor_scale_kind.rb +101 -0
  36. data/lib/musa-dsl/music/scale_kinds/ethnic/phrygian_dominant_scale_kind.rb +103 -0
  37. data/lib/musa-dsl/music/scale_kinds/harmonic_major/harmonic_major_scale_kind.rb +104 -0
  38. data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +110 -0
  39. data/lib/musa-dsl/music/scale_kinds/melodic_minor/altered_scale_kind.rb +106 -0
  40. data/lib/musa-dsl/music/scale_kinds/melodic_minor/dorian_b2_scale_kind.rb +104 -0
  41. data/lib/musa-dsl/music/scale_kinds/melodic_minor/locrian_sharp2_scale_kind.rb +103 -0
  42. data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_augmented_scale_kind.rb +103 -0
  43. data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_dominant_scale_kind.rb +106 -0
  44. data/lib/musa-dsl/music/scale_kinds/melodic_minor/melodic_minor_scale_kind.rb +104 -0
  45. data/lib/musa-dsl/music/scale_kinds/melodic_minor/mixolydian_b6_scale_kind.rb +103 -0
  46. data/lib/musa-dsl/music/scale_kinds/minor_harmonic_scale_kind.rb +125 -0
  47. data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +123 -0
  48. data/lib/musa-dsl/music/scale_kinds/modes/dorian_scale_kind.rb +111 -0
  49. data/lib/musa-dsl/music/scale_kinds/modes/locrian_scale_kind.rb +114 -0
  50. data/lib/musa-dsl/music/scale_kinds/modes/lydian_scale_kind.rb +111 -0
  51. data/lib/musa-dsl/music/scale_kinds/modes/mixolydian_scale_kind.rb +111 -0
  52. data/lib/musa-dsl/music/scale_kinds/modes/phrygian_scale_kind.rb +111 -0
  53. data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_major_scale_kind.rb +93 -0
  54. data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_minor_scale_kind.rb +99 -0
  55. data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_hw_scale_kind.rb +110 -0
  56. data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_wh_scale_kind.rb +110 -0
  57. data/lib/musa-dsl/music/scale_kinds/symmetric/whole_tone_scale_kind.rb +99 -0
  58. data/lib/musa-dsl/music/scale_systems/equally_tempered_12_tone_scale_system.rb +80 -0
  59. data/lib/musa-dsl/music/scale_systems/twelve_semitones_scale_system.rb +60 -0
  60. data/lib/musa-dsl/music/scales.rb +606 -67
  61. data/lib/musa-dsl/musicxml/builder/note.rb +31 -92
  62. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +33 -94
  63. data/lib/musa-dsl/musicxml/builder/rest.rb +30 -91
  64. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +31 -91
  65. data/lib/musa-dsl/neumas/array-to-neumas.rb +1 -1
  66. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +2 -2
  67. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +367 -3
  68. data/lib/musa-dsl/series/base-series.rb +250 -240
  69. data/lib/musa-dsl/series/buffer-serie.rb +16 -5
  70. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +29 -3
  71. data/lib/musa-dsl/series/main-serie-constructors.rb +19 -15
  72. data/lib/musa-dsl/series/main-serie-operations.rb +74 -29
  73. data/lib/musa-dsl/series/proxy-serie.rb +5 -1
  74. data/lib/musa-dsl/series/quantizer-serie.rb +16 -2
  75. data/lib/musa-dsl/series/queue-serie.rb +15 -1
  76. data/lib/musa-dsl/series/series-composer.rb +5 -2
  77. data/lib/musa-dsl/series/timed-serie.rb +8 -4
  78. data/lib/musa-dsl/transport/timer-clock.rb +4 -2
  79. data/lib/musa-dsl/transport/timer.rb +27 -4
  80. data/lib/musa-dsl/version.rb +1 -1
  81. data/musa-dsl.gemspec +18 -15
  82. metadata +85 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 603ecc3ddd2b6bcead498e08a747035df5ff2c2e87e4f527c66c48d5631ab435
4
- data.tar.gz: 37dde89a2c5ff9dda9691ff245fa06b4e870475fb7320ef10bca5ef0a7a84ed2
3
+ metadata.gz: 6f157001c0d45494eda872c34d4d73c161b686da78f0192868637873845ca62e
4
+ data.tar.gz: 2d93034d622985b67ae894d0cf7f4a5acdba787b4453fd6dbad08aa88d096866
5
5
  SHA512:
6
- metadata.gz: 8b1bd0544fd6b6fe611718809e50422e3accf376e88da0832e7b22b7f5b279eb52d9cab2fe3c3f75d2e996e17f0c1a604a2e56676249c946dae533988db8a54c
7
- data.tar.gz: 4c4dbcbfb7243f751205f275eaba483e8d29ce3ca5f59e600a1b8be9d1687c0815572916415b4dfbb0ac14ae7f2bbaec3ee610af2922fe8ab9de42e4ab3a04b2
6
+ metadata.gz: 4dc00ab7d48971758d26bf982e8b61771fe81c26bd9e7cdedba97e457d3c97ff8286f7e53689712c97bb10674fe5f59261ee1f1186a1f20dbda87b62a16aa858
7
+ data.tar.gz: 9da464c4a8cfce824392e1c37b4987b84bde21c59924da5daf34b97b46c183d70d06afff212ab0de7d5b45971c7267aab5260730509b21522f12c49ab5474d46
data/.gitignore CHANGED
@@ -12,3 +12,6 @@ test.musicxml
12
12
  doc
13
13
  .yardoc
14
14
  *.backup
15
+ .vscode
16
+ .ruby-lsp
17
+
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
- gem 'rubocop', group: 'development'
data/README.md CHANGED
@@ -58,6 +58,20 @@ Detailed tutorial showing the Neuma notation system for composing melodies with
58
58
 
59
59
  **📖 [Complete Tutorial](docs/getting-started/tutorial.md)**
60
60
 
61
+ ## Demo Projects
62
+
63
+ A collection of 22+ working demo projects covering the full spectrum of Musa DSL capabilities:
64
+
65
+ - **Basic concepts**: Setup, series, neumas, canon
66
+ - **Generative tools**: Markov chains, Variatio, Darwin, Grammar, Rules, Matrix
67
+ - **DAW integration**: MIDI sync, live coding, clock modes
68
+ - **External protocols**: OSC with SuperCollider and Max/MSP
69
+ - **Advanced patterns**: Event architecture, parameter automation, multi-phase compositions
70
+
71
+ Each demo is a complete, runnable project with documentation explaining the concepts demonstrated.
72
+
73
+ **📦 [musadsl-demo Repository](https://github.com/javier-sy/musadsl-demo)**
74
+
61
75
  ## System Architecture
62
76
 
63
77
  MusaDSL is a comprehensive ecosystem consisting of a core framework (musa-dsl) and associated projects for communication, development, and integration.
@@ -230,4 +244,4 @@ Ruby refinements and metaprogramming utilities: Arrayfy, Hashify, ExplodeRanges,
230
244
 
231
245
  ## License
232
246
 
233
- [Musa-DSL](https://github.com/javier-sy/musa-dsl) Copyright (c) 2016-2025 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
247
+ [Musa-DSL](https://github.com/javier-sy/musa-dsl) Copyright (c) 2016-2026 [Javier Sánchez Yeste](https://yeste.studio), licensed under LGPL 3.0 License
data/docs/README.md CHANGED
@@ -52,6 +52,7 @@ For users extending the DSL or integrating deeply:
52
52
  2. Explore [Music](subsystems/music.md) (scales & chords)
53
53
  3. Try [Generative](subsystems/generative.md) algorithms
54
54
  4. Use [MusicXML Builder](subsystems/musicxml-builder.md) for scores
55
+ 5. Explore [Demo Projects](https://github.com/javier-sy/musadsl-demo) - 22+ working examples
55
56
 
56
57
  ### Live Coding?
57
58
  1. Set up [REPL](subsystems/repl.md)
@@ -52,6 +52,51 @@ E (base event)
52
52
  - Glissandi and parameter sweeps (PS)
53
53
  - Dynamics changes and other evolving parameters
54
54
 
55
+ ## Duration Types in AbsD
56
+
57
+ AbsD defines three duration concepts that enable complex rhythmic structures:
58
+
59
+ | Field | Purpose | Default |
60
+ |-------|---------|---------|
61
+ | `:duration` | Total event process time | Required |
62
+ | `:note_duration` | Actual sound length (staccato/legato) | = duration |
63
+ | `:forward_duration` | Time until next event starts | = duration |
64
+
65
+ **Examples:**
66
+
67
+ ```ruby
68
+ include Musa::Datasets
69
+
70
+ # Normal note - all durations equal
71
+ { pitch: 60, duration: 1.0 }.extend(AbsD)
72
+
73
+ # Staccato - sounds shorter than it "lasts"
74
+ { pitch: 60, duration: 1.0, note_duration: 0.5 }.extend(AbsD)
75
+
76
+ # Chord (simultaneous notes) - next event starts immediately
77
+ { pitch: 60, duration: 1.0, forward_duration: 0 }.extend(AbsD)
78
+
79
+ # Overlap (legato) - next note starts before this one ends
80
+ { pitch: 60, duration: 1.0, forward_duration: 0.8 }.extend(AbsD)
81
+ ```
82
+
83
+ **Understanding DeltaD:**
84
+
85
+ DeltaD is the delta-encoding counterpart to AbsD. While AbsD stores absolute durations, DeltaD stores changes relative to a previous event. If duration hasn't changed from the previous event, it can be omitted for compression (as shown in GDV → GDVd conversions).
86
+
87
+ ## Natural Keys
88
+
89
+ Each dataset type defines "natural keys" - semantically meaningful fields for that type:
90
+
91
+ | Module | Natural Keys |
92
+ |--------|--------------|
93
+ | **E** | `[]` (none) |
94
+ | **AbsD** | `[:duration, :note_duration, :forward_duration]` |
95
+ | **PDV** | AbsD keys + `[:pitch, :velocity]` |
96
+ | **GDV** | AbsD keys + `[:grade, :sharps, :octave, :velocity, :silence]` |
97
+
98
+ Custom keys (any not in NaturalKeys) are preserved through conversions, enabling you to attach composition-specific metadata that travels with your musical data.
99
+
55
100
  ## Extensibility
56
101
 
57
102
  All datasets support custom parameters beyond their natural keys:
@@ -102,6 +147,36 @@ gdv.is_a?(Abs) # => true (GDV includes Abs)
102
147
  gdv.is_a?(AbsD) # => true (GDV includes AbsD for duration)
103
148
  ```
104
149
 
150
+ ## Using AbsD for Containers
151
+
152
+ AbsD is not just for notes - use it for any time-spanning structure that needs a duration. This is useful for chord progression steps, sections, or any compositional element that occupies time but isn't itself a single note.
153
+
154
+ ```ruby
155
+ include Musa::Datasets
156
+
157
+ scale = Musa::Scales::Scales.et12[440.0].major[60]
158
+
159
+ # A chord progression step (not a note, but has duration)
160
+ step = {
161
+ duration: 2, # How long this step lasts
162
+ symbol: :I, # Chord symbol for reference
163
+ bass: { grade: 0, octave: -1, duration: 2, velocity: 0 }.extend(GDV),
164
+ chord: scale[:I].chord, # Chord object
165
+ chord_duration: 7/4r,
166
+ chord_velocity: -1
167
+ }.extend(AbsD)
168
+
169
+ # Now the sequencer can use step.duration for timing
170
+ # while the step contains all the musical information needed to render it
171
+ ```
172
+
173
+ **When to use AbsD directly:**
174
+
175
+ - Containers that hold multiple notes (chords, arpeggios)
176
+ - Section markers with timing information
177
+ - Harmonic rhythm steps in a progression
178
+ - Any structure where you need `:duration` but PDV/GDV semantics don't apply
179
+
105
180
  ## Dataset Conversions
106
181
 
107
182
  Datasets provide rich conversion capabilities for transforming between different representations, each optimized for specific compositional tasks:
@@ -80,18 +80,30 @@ limited_chords = variatio.on(root: [60, 64])
80
80
 
81
81
  Production system with growth and pruning rules (similar to L-systems). Generates tree structures by applying sequential growth rules to create branches and validation rules to prune invalid paths. Useful for harmonic progressions with voice leading rules, melodic variations with contour constraints, or rhythmic patterns following metric rules.
82
82
 
83
+ ### Execution Model
84
+
85
+ **Critical concept: Each `grow` rule = 1 level of depth in the tree.**
86
+
87
+ ```
88
+ Seed → GrowRule1 → GrowRule2 → GrowRule3 → ... → Result
89
+ ↓ ↓ ↓
90
+ branches branches branches
91
+ ```
92
+
93
+ Rules are processed **sequentially**: the first grow rule applies to the seed, the second grow rule applies to all results from the first, and so on. If you define N grow rules, you get N levels of transformation.
94
+
83
95
  **Constructor parameters:**
84
96
  - `&block` - DSL block defining grow rules, cut rules, and end condition
85
97
 
86
98
  **DSL methods:**
87
99
  - `grow(name, &block)` - Define growth rule that generates new branches
88
- - Block receives: `|object, history, **params|`
100
+ - Block receives: `|object|` or `|object, history|` or `|object, history, **params|`
89
101
  - Use `branch(new_object)` to create new possibilities
90
102
  - `cut(reason, &block)` - Define pruning rule to eliminate invalid paths
91
- - Block receives: `|object, history, **params|`
103
+ - Block receives: `|object|` or `|object, history|` or `|object, history, **params|`
92
104
  - Use `prune` to reject current branch
93
105
  - `ended_when(&block)` - Define end condition to mark complete branches
94
- - Block receives: `|object, history, **params|`
106
+ - Block receives: `|object|` or `|object, history|` or `|object, history, **params|`
95
107
  - Return `true` to mark branch as complete
96
108
 
97
109
  **Execution methods:**
@@ -103,23 +115,83 @@ Production system with growth and pruning rules (similar to L-systems). Generate
103
115
  - `combinations` - Returns array of all valid complete paths through tree
104
116
  - `fish` - Returns array of all valid endpoint objects
105
117
 
118
+ ### Important: The `history` Parameter
119
+
120
+ **Warning:** When using a single seed (the typical case), the `history` parameter is **always an empty array `[]`** in all grow rules. This is because `history` contains the path of *confirmed* objects from previous seeds when using multiple seeds.
121
+
122
+ **Do NOT rely on `history` for tracking sequence length with a single seed:**
123
+ ```ruby
124
+ # THIS DOES NOT WORK as expected with a single seed:
125
+ ended_when do |pitch, history|
126
+ history.size == 7 # Always false! history is []
127
+ end
128
+ ```
129
+
130
+ ### Recommended Pattern: Cumulative State
131
+
132
+ The recommended approach is to make your **object accumulate state** rather than relying on `history`:
133
+
134
+ ```ruby
135
+ require 'musa-dsl'
136
+
137
+ # Generate 8-note melodies with interval constraints
138
+ rules = Musa::Rules::Rules.new do
139
+ # Each grow rule adds ONE note. For 8 notes, we need 7 grow rules (seed + 7).
140
+ 7.times do
141
+ grow 'add next note' do |melody, max_interval:|
142
+ last_note = melody.last
143
+ (-max_interval..max_interval).each do |interval|
144
+ next if interval == 0 # Skip unisons
145
+ new_note = last_note + interval
146
+ branch melody + [new_note] if new_note.between?(48, 84) # Range limit
147
+ end
148
+ end
149
+ end
150
+
151
+ # Cut rule: no repeated notes
152
+ cut 'no repetition' do |melody|
153
+ prune if melody.size >= 2 && melody[-1] == melody[-2]
154
+ end
155
+
156
+ # End condition checks the OBJECT size, not history
157
+ ended_when do |melody|
158
+ melody.size == 8
159
+ end
160
+ end
161
+
162
+ # Seed is [[60]] - double array to prevent automatic flattening
163
+ tree = rules.apply([[60]], max_interval: 4)
164
+
165
+ # Extract all valid 8-note melodies
166
+ melodies = tree.combinations.map(&:last)
167
+ puts "Generated #{melodies.size} valid melodies"
168
+ ```
169
+
170
+ **Key points:**
171
+ - Use `N.times { grow }` to create N levels of depth
172
+ - The object (`melody`) accumulates state as an array
173
+ - Check `object.size` in `ended_when`, not `history.size`
174
+ - Seed with `[[value]]` (double array) to prevent Ruby's `arrayfy` from flattening
175
+
176
+ ### Basic Example: Chord Voicings
177
+
106
178
  ```ruby
107
179
  require 'musa-dsl'
108
180
 
109
181
  # Build chord voicings by adding notes sequentially
110
182
  rules = Musa::Rules::Rules.new do
111
- # Step 1: Choose root note
183
+ # Step 1: Choose root note (1st grow rule = 1st level)
112
184
  grow 'add root' do |seed|
113
185
  [60, 64, 67].each { |root| branch [root] } # C, E, G
114
186
  end
115
187
 
116
- # Step 2: Add third (major or minor)
188
+ # Step 2: Add third (2nd grow rule = 2nd level)
117
189
  grow 'add third' do |chord|
118
190
  branch chord + [chord[0] + 4] # Major third
119
191
  branch chord + [chord[0] + 3] # Minor third
120
192
  end
121
193
 
122
- # Step 3: Add fifth
194
+ # Step 3: Add fifth (3rd grow rule = 3rd level)
123
195
  grow 'add fifth' do |chord|
124
196
  branch chord + [chord[0] + 7]
125
197
  end
@@ -149,6 +221,20 @@ voicings = combinations.map { |path| path.last }
149
221
  tree_with_params = rules.apply(0, max_interval: 7)
150
222
  ```
151
223
 
224
+ ### Multiple Seeds (Advanced)
225
+
226
+ When passing an array of seeds `[s1, s2, s3]`, they are processed **sequentially and chained**: each seed starts from the valid endpoints of the previous seed's tree. In this case, `history` contains the confirmed path from previous seeds.
227
+
228
+ ```ruby
229
+ # With multiple seeds, history IS populated:
230
+ rules.apply([seed1, seed2, seed3])
231
+ # seed1 processes → fish valid endpoints
232
+ # seed2 starts from each endpoint, history contains seed1's path
233
+ # seed3 starts from seed2's endpoints, history contains seed1+seed2's paths
234
+ ```
235
+
236
+ This is useful for multi-phase generation where each phase has different rules, but for most use cases the single-seed cumulative state pattern is simpler and recommended.
237
+
152
238
  ## Generative Grammar
153
239
 
154
240
  Formal grammars with combinatorial generation using operators. Useful for generating melodic patterns with rhythmic constraints, harmonic progressions, or variations of musical motifs.