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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a2ee1f86c7d0b4a6c6ecb95b92ea4b15ead3a305c9c6252ea74eb1d437b16fb
4
- data.tar.gz: 07fea02dd0fe457b16c79e18eb82fedb0236c929fa3d1b1e3b32b621d419e6f7
3
+ metadata.gz: 6f157001c0d45494eda872c34d4d73c161b686da78f0192868637873845ca62e
4
+ data.tar.gz: 2d93034d622985b67ae894d0cf7f4a5acdba787b4453fd6dbad08aa88d096866
5
5
  SHA512:
6
- metadata.gz: 0f186d4ac7ee38fb1984a7737e220c25a12411b104728181854898580a7059f87efc6828a769bee268695cb80d3d2818b6dadc0e21a4a1a3a57c853c7502817c
7
- data.tar.gz: 84bf07953180497c334da1d471067a4561957da58a9aac4fe454bbfac364f4c44295e1e12e7ad935ca04f3e304e248fe9d89f2d866aaed9c62d87634d2fcbe15
6
+ metadata.gz: 4dc00ab7d48971758d26bf982e8b61771fe81c26bd9e7cdedba97e457d3c97ff8286f7e53689712c97bb10674fe5f59261ee1f1186a1f20dbda87b62a16aa858
7
+ data.tar.gz: 9da464c4a8cfce824392e1c37b4987b84bde21c59924da5daf34b97b46c183d70d06afff212ab0de7d5b45971c7267aab5260730509b21522f12c49ab5474d46
data/.gitignore CHANGED
@@ -14,3 +14,4 @@ doc
14
14
  *.backup
15
15
  .vscode
16
16
  .ruby-lsp
17
+
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.
@@ -123,8 +123,8 @@ c_major[:V] # => Dominant (G)
123
123
  pitch = c_major.tonic.pitch # => 60
124
124
 
125
125
  # Navigate with octaves
126
- note = c_major[2].octave(1) # E in octave 1
127
- pitch_with_octave = note.pitch # => 76
126
+ note = c_major[2].at_octave(1) # E transposed up 1 octave
127
+ pitch_with_octave = note.pitch # => 76
128
128
 
129
129
  # Chromatic operations - sharp and flat
130
130
  c_sharp = c_major.tonic.sharp # => C# (chromatic, +1 semitone)
@@ -211,10 +211,12 @@ seventh_chord = i_chord.with_size(:seventh) # C major 7th
211
211
  ninth_chord = i_chord.with_size(:ninth) # C major 9th
212
212
 
213
213
  # Voicing modifications - move specific tones to different octaves
214
- voiced = i_chord.move(root: -1, fifth: 1) # Root down, fifth up
214
+ voiced = i_chord.with_move(root: -1, fifth: 1) # Root down, fifth up
215
+ i_chord.move # => { root: -1, fifth: 1 } (current settings)
215
216
 
216
217
  # Duplicate tones in other octaves
217
- doubled = i_chord.duplicate(root: -2, third: [-1, 1]) # Root 2 down, third 1 down and 1 up
218
+ doubled = i_chord.with_duplicate(root: -2, third: [-1, 1]) # Root 2 down, third 1 down and 1 up
219
+ i_chord.duplicate # => { root: -2, third: [-1, 1] } (current settings)
218
220
 
219
221
  # Transpose entire chord
220
222
  lower = i_chord.octave(-1) # Move chord down one octave
@@ -245,28 +247,45 @@ c_major.degree_of_chord(cm) # => nil
245
247
  ```ruby
246
248
  # Get the same chord but with a different scale as context
247
249
  g_mixolydian = Scales.et12[440.0].mixolydian[67]
248
- g7_in_mixolydian = g_mixolydian.chord_on(g7)
250
+ g7_in_mixolydian = g7.as_chord_in_scale(g_mixolydian)
249
251
  g7_in_mixolydian.scale # => G Mixolydian scale
250
252
  g_mixolydian.degree_of_chord(g7_in_mixolydian) # => 0 (I degree)
251
253
  ```
252
254
 
255
+ #### Creating Chords from Scale Degrees
256
+
257
+ ```ruby
258
+ # Create chords directly from scale degrees using Scale#chord_on
259
+ i_chord = scale.chord_on(0) # Tonic triad
260
+ v7 = scale.chord_on(:dominant, :seventh) # V7
261
+ iv_maj7 = scale.chord_on(:IV, :seventh, :major) # IVmaj7
262
+
263
+ # Equivalent to using the note method then chord:
264
+ scale[0].chord # Same as scale.chord_on(0)
265
+ scale[:dominant].chord(:seventh) # Same as scale.chord_on(:dominant, :seventh)
266
+
267
+ # With voicing parameters
268
+ scale.chord_on(:I, :seventh, move: {root: -1})
269
+ scale.chord_on(0, :triad, duplicate: {root: 1})
270
+ ```
271
+
253
272
  #### Finding Scales That Contain a Chord
254
273
 
255
274
  ```ruby
256
275
  g_triad = c_major.dominant.chord # G-B-D
257
276
 
258
277
  # Search in diatonic scales
259
- g_triad.in_scales(family: :diatonic)
278
+ g_triad.search_in_scales(family: :diatonic)
260
279
  # => [Chord in C major (V), Chord in G major (I), Chord in D major (IV), ...]
261
280
 
262
281
  # Search using metadata filters
263
- g_triad.in_scales(family: :greek_modes, brightness: -1..1)
282
+ g_triad.search_in_scales(family: :greek_modes, brightness: -1..1)
264
283
 
265
284
  # Search in all scale types
266
- g_triad.in_scales
285
+ g_triad.search_in_scales
267
286
 
268
287
  # Each result has its scale context
269
- g_triad.in_scales(family: :diatonic).each do |chord|
288
+ g_triad.search_in_scales(family: :diatonic).each do |chord|
270
289
  scale = chord.scale
271
290
  degree = scale.degree_of_chord(chord)
272
291
  puts "#{scale.kind.class.id} rooted on #{scale.root_pitch}: degree #{degree}"
@@ -283,10 +302,10 @@ end
283
302
  tuning = Scales.et12[440.0]
284
303
 
285
304
  # Search at ScaleKind level
286
- tuning.major.scales_containing(g_triad)
305
+ tuning.major.find_chord_in_scales(g_triad)
287
306
 
288
307
  # Search at ScaleSystemTuning level with metadata filters
289
- tuning.chords_of(g7, family: :diatonic, roots: 60..71)
308
+ tuning.search_chord_in_scales(g7, family: :diatonic, roots: 60..71)
290
309
  ```
291
310
 
292
311
  ### Scale Kind Metadata
@@ -432,16 +451,16 @@ tuning.scale_kinds(family: :greek_modes) { |klass| klass.metadata[:brightness]&.
432
451
 
433
452
  #### Integration with Chord Search
434
453
 
435
- The `chords_of` method uses the same metadata filtering:
454
+ The `search_chord_in_scales` method uses the same metadata filtering:
436
455
 
437
456
  ```ruby
438
457
  g7 = tuning.major[60].dominant.chord(:seventh)
439
458
 
440
459
  # Find G7 in diatonic scales
441
- tuning.chords_of(g7, family: :diatonic)
460
+ tuning.search_chord_in_scales(g7, family: :diatonic)
442
461
 
443
462
  # Find G7 in scales with specific brightness
444
- tuning.chords_of(g7, brightness: -1..1)
463
+ tuning.search_chord_in_scales(g7, brightness: -1..1)
445
464
  ```
446
465
 
447
466
  ## Defining Custom Scale Systems, Scale Kinds, and Chord Definitions
@@ -87,6 +87,32 @@ dummy_clock = Musa::Clock::DummyClock.new(100)
87
87
  4. **on_change_position** - Run when position jumps/seeks
88
88
  5. **after_stop** - Run when transport stops
89
89
 
90
+ ### Clean Shutdown
91
+
92
+ To cleanly terminate a transport using TimerClock:
93
+
94
+ 1. Call `transport.stop` from within a scheduled event
95
+ 2. This calls `clock.terminate` internally
96
+ 3. The clock's run loop exits
97
+ 4. `transport.start` returns
98
+ 5. Your code continues after `transport.start` for cleanup
99
+
100
+ ```ruby
101
+ # Example: Self-terminating composition
102
+ transport.sequencer.at 10 do
103
+ puts "Composition finished"
104
+ transport.stop # This will cause transport.start to return
105
+ end
106
+
107
+ transport.start # Blocks until transport.stop is called
108
+ puts "Cleanup..." # Executes after stop
109
+ output.close
110
+ ```
111
+
112
+ **Note:** For `InputMidiClock`, stop/start cycles are controlled by the DAW
113
+ and don't terminate the process. The `terminate` method can be used explicitly
114
+ if you need the run loop to exit.
115
+
90
116
  **Key methods:**
91
117
  - `start` - Start playback (blocks while running)
92
118
  - `stop` - Stop playback
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Musical dataset framework for MusaDSL.
2
4
  #
3
5
  # The Datasets module provides a comprehensive framework for representing and transforming
@@ -204,7 +204,7 @@ module Musa::Datasets
204
204
  pdv[:pitch] = if self[:silence]
205
205
  :silence
206
206
  else
207
- scale[self[:grade]].sharp(self[:sharps] || 0).octave(self[:octave] || 0).pitch
207
+ scale[self[:grade]].sharp(self[:sharps] || 0).at_octave(self[:octave] || 0).pitch
208
208
  end
209
209
  end
210
210
 
@@ -399,8 +399,8 @@ module Musa::Datasets
399
399
  (self[:sharps] || 0) != (previous[:sharps] || 0)
400
400
 
401
401
  gdvd[:delta_grade] =
402
- scale[self[:grade]].octave(self[:octave]).wide_grade -
403
- scale[previous[:grade]].octave(previous[:octave]).wide_grade
402
+ scale[self[:grade]].at_octave(self[:octave]).wide_grade -
403
+ scale[previous[:grade]].at_octave(previous[:octave]).wide_grade
404
404
 
405
405
  gdvd[:delta_sharps] = (self[:sharps] || 0) - (previous[:sharps] || 0)
406
406
  end
@@ -206,7 +206,7 @@ module Musa::Datasets
206
206
  #
207
207
  # @api private
208
208
  class PtoTimedSerie
209
- include Musa::Series::Serie.base
209
+ include Musa::Series::Serie::Base
210
210
 
211
211
  # Creates new timed serie adapter.
212
212
  #
@@ -42,7 +42,8 @@ module Musa::Datasets::Score::ToMXML
42
42
  # @note This method is experimental and currently unused. See TODO comment.
43
43
  #
44
44
  # @api private
45
- def initialize(element, bar, bar_size = 1r) # TODO remove (unused because of bad strategy to time groups)
45
+ def initialize(element, bar, bar_size = Rational(1))
46
+ # TODO remove (unused because of bad strategy to time groups)
46
47
  @continue_from_previous_bar = element[:start] < bar
47
48
  @continue_to_next_bar = element[:finish] >= bar + bar_size
48
49
 
@@ -99,7 +100,8 @@ module Musa::Datasets::Score::ToMXML
99
100
  #
100
101
  # @api private
101
102
  # @todo Complete or remove this experimental method
102
- def time_and_tuplet_optimize(elements, bar, bar_size = 1r) # TODO remove (unused because of bad strategy to time groups)
103
+ def time_and_tuplet_optimize(elements, bar, bar_size = Rational(1))
104
+ # TODO remove (unused because of bad strategy to time groups)
103
105
  decompositions = elements.collect { |pdv| ElementDurationDecomposition.new(pdv, bar, bar_size) }
104
106
 
105
107
  denominators = decompositions.collect { |g| g.duration_decomposition.collect { |d| d.to_r.denominator } }.flatten.uniq
@@ -143,12 +143,14 @@ module Musa::Datasets
143
143
  # @return [Object, nil] attribute value
144
144
  #
145
145
  # @api private
146
- def [](key)
146
+ def get(key)
147
147
  if NaturalKeys.include?(key) && self.respond_to?(key)
148
148
  self.send(key)
149
149
  end
150
150
  end
151
151
 
152
+ alias_method :[], :get
153
+
152
154
  # Returns latest finish time of all events.
153
155
  #
154
156
  # @return [Rational, nil] latest finish time, or nil if score is empty
@@ -369,10 +369,12 @@ module Musa
369
369
  # @param index [Integer] option index
370
370
  #
371
371
  # @return [Node] option converted to node
372
- def [](index)
372
+ def get(index)
373
373
  options[index].to_serie.to_node
374
374
  end
375
375
 
376
+ alias_method :[], :get
377
+
376
378
  # Converts options to series.
377
379
  #
378
380
  # Generates options and converts them to a {Musa::Series::Serie}.
@@ -97,7 +97,7 @@ module Musa
97
97
  # Implements {Musa::Series::Serie} interface for integration with series operations.
98
98
  class Markov
99
99
  # TODO: adapt to series prototyping
100
- include Musa::Series::Serie.base
100
+ include Musa::Series::Serie::Base
101
101
 
102
102
  # Creates Markov chain generator.
103
103
  #
@@ -385,10 +385,12 @@ module Musa
385
385
  end
386
386
 
387
387
  # @return [Integer, nil] last value assigned to the controller.
388
- def [](controller_number_or_symbol)
388
+ def get(controller_number_or_symbol)
389
389
  @controller[number_of(controller_number_or_symbol)]
390
390
  end
391
391
 
392
+ alias_method :[], :get
393
+
392
394
  # Resolves a controller reference to its MIDI CC number.
393
395
  #
394
396
  # @param controller_number_or_symbol [Integer, Symbol] CC number or alias.
@@ -1,5 +1,3 @@
1
- require 'set'
2
-
3
1
  module Musa
4
2
  # Chord construction and manipulation framework.
5
3
  #
@@ -115,10 +113,14 @@ module Musa
115
113
  # @example
116
114
  # ChordDefinition[:maj] # => <ChordDefinition :maj>
117
115
  # ChordDefinition[:min7] # => <ChordDefinition :min7>
118
- def self.[](name)
116
+ def self.get(name)
119
117
  @definitions[name]
120
118
  end
121
119
 
120
+ class << self
121
+ alias_method :[], :get
122
+ end
123
+
122
124
  # Registers a new chord definition.
123
125
  #
124
126
  # Creates and registers a chord definition with specified intervals and features.
@@ -150,7 +152,7 @@ module Musa
150
152
  definition.features.each { |k, v| @features_by_value[v] = k }
151
153
 
152
154
  @feature_keys ||= Set[]
153
- features.keys.each { |feature_name| @feature_keys << feature_name }
155
+ features.each_key { |feature_name| @feature_keys << feature_name }
154
156
 
155
157
  self
156
158
  end
@@ -267,7 +269,7 @@ module Musa
267
269
 
268
270
  # Checks if chord fits within a scale.
269
271
  #
270
- # @param scale [Scale] scale to check against
272
+ # @param scale [Scales::Scale] scale to check against
271
273
  # @param chord_root_pitch [Integer] chord root pitch
272
274
  # @return [Boolean] true if all chord notes are in scale
273
275
  #
@@ -1,5 +1,42 @@
1
1
  require_relative 'chord-definition'
2
2
 
3
+ # Standard chord definitions for Western harmony.
4
+ #
5
+ # This file registers the common chord types used in Western music theory,
6
+ # organized by size (number of notes) and quality (major, minor, etc.).
7
+ #
8
+ # ## Chord Categories
9
+ #
10
+ # ### Triads (3 notes)
11
+ # - **Major** (:maj) - Root, major 3rd, perfect 5th (0-4-7)
12
+ # - **Minor** (:min) - Root, minor 3rd, perfect 5th (0-3-7)
13
+ # - **Diminished** (:dim) - Root, minor 3rd, diminished 5th (0-3-6)
14
+ # - **Augmented** (:aug) - Root, major 3rd, augmented 5th (0-4-8)
15
+ #
16
+ # ### Seventh Chords (4 notes)
17
+ # - **Major 7th** (:maj7) - Major triad + major 7th (0-4-7-11)
18
+ # - **Minor 7th** (:min7) - Minor triad + minor 7th (0-3-7-10)
19
+ # - **Dominant 7th** (:dom7) - Major triad + minor 7th (0-4-7-10)
20
+ #
21
+ # ### Extended Chords (5+ notes)
22
+ # - **9th chords**: :maj9, :min9, :dom9
23
+ # - **11th chords**: :maj11, :min11
24
+ # - **13th chords**: :maj13, :min13
25
+ #
26
+ # ## Usage
27
+ #
28
+ # Chords are accessed via scale notes using the {Musa::Scales::NoteInScale#chord} method:
29
+ #
30
+ # scale = Scales.et12[440.0].major[60]
31
+ # scale.tonic.chord # Major triad (default)
32
+ # scale.tonic.chord :seventh # Major 7th
33
+ # scale.dominant.chord :seventh # Dominant 7th
34
+ # scale.tonic.chord quality: :minor # Minor triad
35
+ #
36
+ # @see Musa::Chords::ChordDefinition.register How chords are registered
37
+ # @see Musa::Chords::Chord How to build and use chords
38
+ # @see Musa::Scales::NoteInScale#chord Building chords from scale notes
39
+
3
40
  # TODO trasladar los acordes de https://en.wikipedia.org/wiki/Chord_notation
4
41
 
5
42
  # TRIADS