musa-dsl 0.30.2 → 0.40.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 (123) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -1
  3. data/.version +6 -0
  4. data/.yardopts +7 -0
  5. data/README.md +227 -6
  6. data/docs/README.md +83 -0
  7. data/docs/api-reference.md +86 -0
  8. data/docs/getting-started/quick-start.md +93 -0
  9. data/docs/getting-started/tutorial.md +58 -0
  10. data/docs/subsystems/core-extensions.md +316 -0
  11. data/docs/subsystems/datasets.md +465 -0
  12. data/docs/subsystems/generative.md +290 -0
  13. data/docs/subsystems/matrix.md +63 -0
  14. data/docs/subsystems/midi.md +123 -0
  15. data/docs/subsystems/music.md +233 -0
  16. data/docs/subsystems/musicxml-builder.md +264 -0
  17. data/docs/subsystems/neumas.md +71 -0
  18. data/docs/subsystems/repl.md +135 -0
  19. data/docs/subsystems/sequencer.md +98 -0
  20. data/docs/subsystems/series.md +302 -0
  21. data/docs/subsystems/transcription.md +152 -0
  22. data/docs/subsystems/transport.md +177 -0
  23. data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
  24. data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
  25. data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
  26. data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
  27. data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
  28. data/lib/musa-dsl/core-ext/extension.rb +53 -0
  29. data/lib/musa-dsl/core-ext/hashify.rb +162 -1
  30. data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
  31. data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
  32. data/lib/musa-dsl/core-ext/with.rb +114 -0
  33. data/lib/musa-dsl/datasets/dataset.rb +109 -0
  34. data/lib/musa-dsl/datasets/delta-d.rb +78 -0
  35. data/lib/musa-dsl/datasets/e.rb +186 -2
  36. data/lib/musa-dsl/datasets/gdv.rb +279 -2
  37. data/lib/musa-dsl/datasets/gdvd.rb +201 -0
  38. data/lib/musa-dsl/datasets/helper.rb +75 -0
  39. data/lib/musa-dsl/datasets/p.rb +177 -2
  40. data/lib/musa-dsl/datasets/packed-v.rb +91 -0
  41. data/lib/musa-dsl/datasets/pdv.rb +136 -1
  42. data/lib/musa-dsl/datasets/ps.rb +134 -4
  43. data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
  44. data/lib/musa-dsl/datasets/score/render.rb +105 -1
  45. data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
  46. data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
  47. data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
  48. data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
  49. data/lib/musa-dsl/datasets/score.rb +279 -0
  50. data/lib/musa-dsl/datasets/v.rb +88 -0
  51. data/lib/musa-dsl/generative/darwin.rb +180 -1
  52. data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
  53. data/lib/musa-dsl/generative/markov.rb +133 -3
  54. data/lib/musa-dsl/generative/rules.rb +258 -4
  55. data/lib/musa-dsl/generative/variatio.rb +217 -2
  56. data/lib/musa-dsl/logger/logger.rb +267 -2
  57. data/lib/musa-dsl/matrix/matrix.rb +256 -10
  58. data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
  59. data/lib/musa-dsl/midi/midi-voices.rb +265 -4
  60. data/lib/musa-dsl/music/chord-definition.rb +233 -1
  61. data/lib/musa-dsl/music/chord-definitions.rb +33 -6
  62. data/lib/musa-dsl/music/chords.rb +308 -2
  63. data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
  64. data/lib/musa-dsl/music/scales.rb +957 -40
  65. data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
  66. data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
  67. data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
  68. data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
  69. data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
  70. data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
  71. data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
  72. data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
  73. data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
  74. data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
  75. data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
  76. data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
  77. data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
  78. data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
  79. data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
  80. data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
  81. data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
  82. data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
  83. data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
  84. data/lib/musa-dsl/neumas/neumas.rb +67 -0
  85. data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
  86. data/lib/musa-dsl/repl/repl.rb +550 -0
  87. data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
  88. data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
  89. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
  90. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
  91. data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
  92. data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
  93. data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
  94. data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
  95. data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
  96. data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
  97. data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
  98. data/lib/musa-dsl/series/array-to-serie.rb +37 -1
  99. data/lib/musa-dsl/series/base-series.rb +843 -5
  100. data/lib/musa-dsl/series/buffer-serie.rb +48 -0
  101. data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
  102. data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
  103. data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
  104. data/lib/musa-dsl/series/proxy-serie.rb +67 -0
  105. data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
  106. data/lib/musa-dsl/series/queue-serie.rb +65 -0
  107. data/lib/musa-dsl/series/series-composer.rb +701 -0
  108. data/lib/musa-dsl/series/timed-serie.rb +473 -28
  109. data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
  110. data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
  111. data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
  112. data/lib/musa-dsl/transcription/transcription.rb +265 -0
  113. data/lib/musa-dsl/transport/clock.rb +125 -0
  114. data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
  115. data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
  116. data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
  117. data/lib/musa-dsl/transport/timer-clock.rb +183 -1
  118. data/lib/musa-dsl/transport/timer.rb +83 -0
  119. data/lib/musa-dsl/transport/transport.rb +318 -0
  120. data/lib/musa-dsl/version.rb +1 -1
  121. data/lib/musa-dsl.rb +132 -25
  122. data/musa-dsl.gemspec +12 -10
  123. metadata +87 -8
@@ -0,0 +1,290 @@
1
+ # Generative - Algorithmic Composition
2
+
3
+ Tools for generative and algorithmic music composition.
4
+
5
+ ## Markov Chains
6
+
7
+ Probabilistic sequence generation using transition matrices. Markov chains generate sequences where each value depends only on the current state and transition probabilities.
8
+
9
+ Parameters:
10
+ - `start:` - Initial state value
11
+ - `finish:` - End state symbol (transitions to this value terminate the sequence)
12
+ - `transitions:` - Hash mapping each state to possible next states with probabilities
13
+ - Format: `state => { next_state => probability, ... }`
14
+ - Probabilities for each state should sum to 1.0
15
+
16
+ ```ruby
17
+ require 'musa-dsl'
18
+
19
+ markov = Musa::Markov::Markov.new(
20
+ start: 0,
21
+ finish: :end,
22
+ transitions: {
23
+ 0 => { 2 => 0.5, 4 => 0.3, 7 => 0.2 },
24
+ 2 => { 0 => 0.3, 4 => 0.5, 5 => 0.2 },
25
+ 4 => { 2 => 0.4, 5 => 0.4, 7 => 0.2 },
26
+ 5 => { 0 => 0.5, :end => 0.5 },
27
+ 7 => { 0 => 0.6, :end => 0.4 }
28
+ }
29
+ ).i
30
+
31
+ # Generate melody pitches (Markov is a Serie, so we can use .to_a)
32
+ melody_pitches = markov.to_a
33
+ ```
34
+
35
+ ## Variatio
36
+
37
+ Generates all combinations of parameter variations using Cartesian product. Useful for creating comprehensive parameter sweeps, exploring all possibilities of a musical motif, or generating exhaustive harmonic permutations.
38
+
39
+ **Constructor parameters:**
40
+ - `instance_name` (Symbol) - Name for the object parameter in blocks (e.g., `:chord`, `:note`, `:synth`)
41
+ - `&block` - DSL block defining fields, constructor, and optional attributes/finalize
42
+
43
+ **DSL methods:**
44
+ - `field(name, options)` - Define a parameter field with possible values (Array or Range)
45
+ - `fieldset(name, options, &block)` - Define nested field group with its own fields
46
+ - `constructor(&block)` - Define how to construct each variation object (required)
47
+ - `with_attributes(&block)` - Modify objects with field/fieldset values (optional)
48
+ - `finalize(&block)` - Post-process completed objects (optional)
49
+
50
+ **Execution methods:**
51
+ - `run` - Generate all variations with default field values
52
+ - `on(**values)` - Generate variations with runtime field value overrides
53
+
54
+ ```ruby
55
+ require 'musa-dsl'
56
+
57
+ variatio = Musa::Variatio::Variatio.new :chord do
58
+ field :root, [60, 64, 67] # C, E, G
59
+ field :type, [:major, :minor]
60
+
61
+ constructor do |root:, type:|
62
+ { root: root, type: type }
63
+ end
64
+ end
65
+
66
+ all_chords = variatio.run
67
+ # => [
68
+ # { root: 60, type: :major }, { root: 60, type: :minor },
69
+ # { root: 64, type: :major }, { root: 64, type: :minor },
70
+ # { root: 67, type: :major }, { root: 67, type: :minor }
71
+ # ]
72
+ # 3 roots × 2 types = 6 variations
73
+
74
+ # Override field values at runtime
75
+ limited_chords = variatio.on(root: [60, 64])
76
+ # => 2 roots × 2 types = 4 variations
77
+ ```
78
+
79
+ ## Rules
80
+
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
+
83
+ **Constructor parameters:**
84
+ - `&block` - DSL block defining grow rules, cut rules, and end condition
85
+
86
+ **DSL methods:**
87
+ - `grow(name, &block)` - Define growth rule that generates new branches
88
+ - Block receives: `|object, history, **params|`
89
+ - Use `branch(new_object)` to create new possibilities
90
+ - `cut(reason, &block)` - Define pruning rule to eliminate invalid paths
91
+ - Block receives: `|object, history, **params|`
92
+ - Use `prune` to reject current branch
93
+ - `ended_when(&block)` - Define end condition to mark complete branches
94
+ - Block receives: `|object, history, **params|`
95
+ - Return `true` to mark branch as complete
96
+
97
+ **Execution methods:**
98
+ - `apply(seed_or_seeds, **params)` - Apply rules to initial object(s), returns tree Node
99
+ - Accepts single object or array of objects
100
+ - Optional parameters passed to all rule blocks
101
+
102
+ **Tree Node methods:**
103
+ - `combinations` - Returns array of all valid complete paths through tree
104
+ - `fish` - Returns array of all valid endpoint objects
105
+
106
+ ```ruby
107
+ require 'musa-dsl'
108
+
109
+ # Build chord voicings by adding notes sequentially
110
+ rules = Musa::Rules::Rules.new do
111
+ # Step 1: Choose root note
112
+ grow 'add root' do |seed|
113
+ [60, 64, 67].each { |root| branch [root] } # C, E, G
114
+ end
115
+
116
+ # Step 2: Add third (major or minor)
117
+ grow 'add third' do |chord|
118
+ branch chord + [chord[0] + 4] # Major third
119
+ branch chord + [chord[0] + 3] # Minor third
120
+ end
121
+
122
+ # Step 3: Add fifth
123
+ grow 'add fifth' do |chord|
124
+ branch chord + [chord[0] + 7]
125
+ end
126
+
127
+ # Pruning rule: avoid wide intervals
128
+ cut 'no wide spacing' do |chord|
129
+ if chord.size >= 2
130
+ prune if (chord[-1] - chord[-2]) > 12 # Max octave between adjacent notes
131
+ end
132
+ end
133
+
134
+ # End after three notes
135
+ ended_when do |chord|
136
+ chord.size == 3
137
+ end
138
+ end
139
+
140
+ tree = rules.apply(0) # seed value (triggers generation)
141
+ combinations = tree.combinations
142
+
143
+ # Extract final voicings from each path
144
+ voicings = combinations.map { |path| path.last }
145
+ # => [[60, 64, 67], [60, 63, 67], [64, 68, 71], [64, 67, 71], [67, 71, 74], [67, 70, 74]]
146
+ # 3 roots × 2 thirds × 1 fifth = 6 voicings
147
+
148
+ # With parameters
149
+ tree_with_params = rules.apply(0, max_interval: 7)
150
+ ```
151
+
152
+ ## Generative Grammar
153
+
154
+ Formal grammars with combinatorial generation using operators. Useful for generating melodic patterns with rhythmic constraints, harmonic progressions, or variations of musical motifs.
155
+
156
+ **Constructors:**
157
+ - `N(content, **attributes)` - Create terminal node with fixed content and attributes
158
+ - `N(**attributes, &block)` - Create block node with dynamic content generation
159
+ - `PN()` - Create proxy node for recursive grammar definitions
160
+
161
+ **Combination operators:**
162
+ - `|` (or) - Alternative/choice between nodes (e.g., `a | b`)
163
+ - `+` (next) - Concatenation/sequence of nodes (e.g., `a + b`)
164
+ - `repeat(exactly:)` or `repeat(min:, max:)` - Repeat node multiple times
165
+ - `limit(&block)` - Filter options by condition
166
+
167
+ **Result methods:**
168
+ - `options(content: :join)` - Generate all combinations as joined strings
169
+ - `options(content: :itself)` - Generate all combinations as arrays (default)
170
+ - `options(raw: true)` - Generate raw OptionElement objects with attributes
171
+ - `options(&condition)` - Generate filtered combinations
172
+
173
+ ```ruby
174
+ require 'musa-dsl'
175
+
176
+ include Musa::GenerativeGrammar
177
+
178
+ a = N('a', size: 1)
179
+ b = N('b', size: 1)
180
+ c = N('c', size: 1)
181
+ d = b | c # d can be either b or c
182
+
183
+ # Grammar: (a or d) repeated 3 times, then c
184
+ grammar = (a | d).repeat(3) + c
185
+
186
+ # Generate all possibilities
187
+ grammar.options(content: :join)
188
+ # => ["aaac", "aabc", "aacc", "abac", "abbc", "abcc", "acac", "acbc", "accc",
189
+ # "baac", "babc", "bacc", "bbac", "bbbc", "bbcc", "bcac", "bcbc", "bccc",
190
+ # "caac", "cabc", "cacc", "cbac", "cbbc", "cbcc", "ccac", "ccbc", "cccc"]
191
+ # 3^3 × 1 = 27 combinations
192
+
193
+ # With constraints - filter by attribute
194
+ grammar_with_limit = (a | d).repeat(min: 1, max: 4).limit { |o|
195
+ o.collect { |e| e.attributes[:size] }.sum <= 3
196
+ }
197
+
198
+ result_limited = grammar_with_limit.options(content: :join)
199
+ # Includes: ["a", "b", "c", "aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc", "aaa", "aab", "aac", ...]
200
+ # Only combinations where total size <= 3
201
+ ```
202
+
203
+ ## Darwin
204
+
205
+ Evolutionary selection algorithm based on fitness evaluation. Darwin doesn't generate populations - it selects and ranks existing candidates using user-defined measures (features and dimensions) and weights. Each object is evaluated, normalized across the population, scored, and sorted by fitness.
206
+
207
+ **How it works:**
208
+ 1. Define measures (features & dimensions) to evaluate each candidate
209
+ 2. Define weights for each measure
210
+ 3. Darwin evaluates all candidates, normalizes dimensions, applies weights
211
+ 4. Returns population sorted by fitness (best first)
212
+
213
+ **Constructor:**
214
+ - `&block` - DSL block defining measures and weights
215
+
216
+ **DSL methods:**
217
+ - `measures(&block)` - Define evaluation block for each object
218
+ - Block receives each object to evaluate
219
+ - Inside block use: `feature(name)`, `dimension(name, value)`, `die`
220
+ - `weight(**weights)` - Assign weights to features/dimensions
221
+ - Positive weights favor the measure
222
+ - Negative weights penalize the measure
223
+
224
+ **Measures methods (inside measures block):**
225
+ - `feature(name)` - Mark object as having a boolean feature
226
+ - `dimension(name, value)` - Record numeric measurement (will be normalized 0-1)
227
+ - `die` - Mark object as non-viable (will be excluded from results)
228
+
229
+ **Execution methods:**
230
+ - `select(population)` - Evaluate and rank population, returns sorted array (best first)
231
+
232
+ ```ruby
233
+ require 'musa-dsl'
234
+
235
+ # Generate candidate melodies using Variatio
236
+ variatio = Musa::Variatio::Variatio.new :melody do
237
+ field :interval, 1..7 # Intervals in semitones
238
+ field :contour, [:up, :down, :repeat]
239
+ field :duration, [1/4r, 1/2r, 1r]
240
+
241
+ constructor do |interval:, contour:, duration:|
242
+ { interval: interval, contour: contour, duration: duration }
243
+ end
244
+ end
245
+
246
+ candidates = variatio.run # Generate all combinations
247
+
248
+ # Create Darwin selector with musical criteria
249
+ darwin = Musa::Darwin::Darwin.new do
250
+ measures do |melody|
251
+ # Eliminate melodies with unwanted characteristics
252
+ die if melody[:interval] > 5 # No large leaps
253
+
254
+ # Binary features (present/absent)
255
+ feature :stepwise if melody[:interval] <= 2 # Stepwise motion
256
+ feature :has_quarter_notes if melody[:duration] == 1/4r
257
+
258
+ # Numeric dimensions (will be normalized across population)
259
+ # Use negative values to prefer lower numbers
260
+ dimension :interval_size, -melody[:interval].to_f
261
+ dimension :duration_value, melody[:duration].to_f
262
+ end
263
+
264
+ # Weight each measure's contribution to fitness
265
+ weight interval_size: 2.0, # Strongly prefer smaller intervals
266
+ stepwise: 1.5, # Prefer stepwise motion
267
+ has_quarter_notes: 1.0, # Slightly prefer quarter notes
268
+ duration_value: -0.5 # Slightly penalize longer durations
269
+ end
270
+
271
+ # Select and rank melodies by fitness
272
+ ranked = darwin.select(candidates)
273
+
274
+ best_melody = ranked.first # Highest fitness
275
+ top_10 = ranked.first(10) # Top 10 melodies
276
+ worst = ranked.last # Lowest fitness (but still viable)
277
+ ```
278
+
279
+ ## API Reference
280
+
281
+ **Complete API documentation:**
282
+ - [Musa::Generative::Markov](https://rubydoc.info/gems/musa-dsl/Musa/Generative/Markov) - Probabilistic sequence generation
283
+ - [Musa::Generative::Variatio](https://rubydoc.info/gems/musa-dsl/Musa/Generative/Variatio) - Cartesian product variations
284
+ - [Musa::Generative::Rules](https://rubydoc.info/gems/musa-dsl/Musa/Generative/Rules) - L-system production rules
285
+ - [Musa::Generative::GenerativeGrammar](https://rubydoc.info/gems/musa-dsl/Musa/Generative/GenerativeGrammar) - Formal grammar generation
286
+ - [Musa::Generative::Darwin](https://rubydoc.info/gems/musa-dsl/Musa/Generative/Darwin) - Genetic algorithms
287
+
288
+ **Source code:** `lib/generative/`
289
+
290
+
@@ -0,0 +1,63 @@
1
+ # Matrix - Sonic Gesture Conversion
2
+
3
+ Musa::Matrix provides refinements to convert matrix representations to P (point sequences) for sequencer playback. Sonic gestures can be represented as matrices where rows are time steps and columns are sonic parameters.
4
+
5
+ This opens a world of compositional possibilities by treating sonic gestures as **geometric objects in multidimensional spaces**. Each dimension of the matrix can represent a different sonic parameter (pitch, velocity, timbre, pan, filter cutoff, etc.), allowing you to:
6
+
7
+ - **Apply mathematical transformations**: Use standard matrix operations (rotation, scaling, translation, shearing) to transform sonic gestures geometrically. A rotation matrix can morph a melodic contour into a completely different shape while maintaining its gestural coherence.
8
+
9
+ - **Compose in geometric space**: Design sonic trajectories as paths through multidimensional parameter spaces. A straight line in pitch-velocity space becomes a linear glissando with proportional dynamic changes.
10
+
11
+ - **Interpolate and morph**: Create smooth transitions between sonic states by interpolating matrix points, generating continuous parameter sweeps that move through complex multidimensional spaces.
12
+
13
+ - **Decompose and recompose**: Extract individual parameter dimensions for independent processing, then recombine them into new sonic configurations.
14
+
15
+ The conversion to P format preserves the temporal relationships (durations calculated from time differences) while making the data suitable for sequencer playback, enabling you to realize complex geometric transformations as actual sonic events.
16
+
17
+ ```ruby
18
+ require 'musa-dsl'
19
+
20
+ using Musa::Extension::Matrix
21
+
22
+ # Matrix representing a melodic gesture: [time, pitch]
23
+ # Time progresses: 0 -> 1 -> 2
24
+ # Pitch changes: 60 -> 62 -> 64
25
+ melody_matrix = Matrix[[0, 60], [1, 62], [2, 64]]
26
+
27
+ # Convert to P format for sequencer playback
28
+ # time_dimension: 0 means first column is time
29
+ # Time dimension removed, used only for duration calculation
30
+ p_sequence = melody_matrix.to_p(time_dimension: 0)
31
+
32
+ # Result: [[[60], 1, [62], 1, [64]]]
33
+ # Format: [[pitch1], duration1, [pitch2], duration2, [pitch3]]
34
+ # Each value [60], [62], [64] is extended with V module
35
+
36
+ # Multi-parameter example: [time, pitch, velocity]
37
+ gesture = Matrix[[0, 60, 100], [0.5, 62, 110], [1, 64, 120]]
38
+ p_with_velocity = gesture.to_p(time_dimension: 0)
39
+ # Result: [[[60, 100], 0.5, [62, 110], 0.5, [64, 120]]]
40
+
41
+ # Condensing connected gestures
42
+ phrase1 = Matrix[[0, 60], [1, 62]]
43
+ phrase2 = Matrix[[1, 62], [2, 64], [3, 65]]
44
+
45
+ # Matrices that share endpoints are automatically merged
46
+ merged = [phrase1, phrase2].to_p(time_dimension: 0)
47
+ # Result: [[[60], 1, [62], 1, [64], 1, [65]]]
48
+ # Both phrases merged into continuous sequence
49
+ ```
50
+
51
+ **Use cases:**
52
+ - Converting recorded MIDI data to playable sequences
53
+ - Transforming algorithmic compositions from matrix form to time-based sequences
54
+ - Merging fragmented sonic gestures
55
+
56
+ ## API Reference
57
+
58
+ **Complete API documentation:**
59
+ - [Musa::Matrix](https://rubydoc.info/gems/musa-dsl/Musa/Matrix) - Matrix to point sequence conversion
60
+
61
+ **Source code:** `lib/matrix/`
62
+
63
+
@@ -0,0 +1,123 @@
1
+ # MIDI - Voice Management & Recording
2
+
3
+ High-level MIDI tools for sequencer-synchronized playback and recording. These utilities integrate MIDI I/O with the sequencer timeline, ensuring correct timing even during fast-forward or quantization.
4
+
5
+ ## MIDIVoices - Polyphonic Voice Management
6
+
7
+ **MIDIVoices** manages MIDI channels as voices synchronized with the sequencer clock. Each voice maintains state (active notes, controllers, sustain pedal) and schedules all events on the musical timeline.
8
+
9
+ **Key features:**
10
+ - Voice abstraction for MIDI channels with automatic note scheduling
11
+ - Duration tracking and note-off scheduling
12
+ - Sustain pedal management
13
+ - Fast-forward support for silent timeline catch-up
14
+ - Polyphonic playback with chord support
15
+
16
+ ```ruby
17
+ require 'musa-dsl'
18
+ require 'midi-communications'
19
+
20
+ # Setup sequencer and MIDI output
21
+ output = MIDICommunications::Output.gets # Select MIDI output interactively
22
+ sequencer = Musa::Sequencer::Sequencer.new(4, 24)
23
+
24
+ # Create voice manager
25
+ voices = Musa::MIDIVoices::MIDIVoices.new(
26
+ sequencer: sequencer,
27
+ output: output,
28
+ channels: [0, 1, 2] # Use MIDI channels 0, 1, and 2
29
+ )
30
+
31
+ # Get a voice and play notes
32
+ voice = voices.voices.first
33
+
34
+ # Play single notes with automatic note-off
35
+ voice.note pitch: 60, velocity: 100, duration: 1/4r # Quarter note
36
+
37
+ # Play chords
38
+ voice.note pitch: [60, 64, 67], velocity: 90, duration: 1r # C major chord, whole note
39
+
40
+ # Control notes manually
41
+ note_ctrl = voice.note pitch: 64, velocity: 80, duration: nil # Indefinite duration
42
+ note_ctrl.on_stop { puts "Note ended!" }
43
+ # ... later:
44
+ note_ctrl.note_off # Manually stop the note
45
+
46
+ # Use fast-forward for silent catch-up (useful for seeking)
47
+ voices.fast_forward = true
48
+ # ... replay past events silently ...
49
+ voices.fast_forward = false # Resume audible output
50
+ ```
51
+
52
+ ## MIDIRecorder - MIDI Event Recording
53
+
54
+ **MIDIRecorder** captures raw MIDI bytes alongside sequencer position timestamps and converts them into structured note events. Useful for recording phrases from external MIDI controllers synchronized with the sequencer timeline.
55
+
56
+ **Key features:**
57
+ - Records MIDI events with sequencer position timestamps
58
+ - Transcribes raw MIDI into structured note hashes
59
+ - Pairs note-on/note-off events automatically
60
+ - Calculates durations and detects silences
61
+ - Output format compatible with Musa transcription pipelines
62
+
63
+ ```ruby
64
+ require 'musa-dsl'
65
+ require 'midi-communications'
66
+
67
+ # Setup sequencer and MIDI input
68
+ input = MIDICommunications::Input.gets # Select MIDI input interactively
69
+ sequencer = Musa::Sequencer::Sequencer.new(4, 24)
70
+
71
+ # Create recorder
72
+ recorder = Musa::MIDIRecorder::MIDIRecorder.new(sequencer)
73
+
74
+ # Capture MIDI from controller during playback
75
+ input.on_message { |bytes| recorder.record(bytes) }
76
+
77
+ # Start sequencer and play/record...
78
+ # (MIDI events from controller are captured with timing)
79
+
80
+ # After recording, get structured notes
81
+ notes = recorder.transcription
82
+
83
+ # The transcription returns an array of note hashes:
84
+ # [
85
+ # { position: 1r, channel: 0, pitch: 60, velocity: 100, duration: 1/4r, velocity_off: 64 },
86
+ # { position: 5/4r, channel: 0, pitch: :silence, duration: 1/8r },
87
+ # { position: 11/8r, channel: 0, pitch: 62, velocity: 90, duration: 1/4r, velocity_off: 64 }
88
+ # ]
89
+
90
+ notes.each do |note|
91
+ if note[:pitch] == :silence
92
+ puts "Silence at #{note[:position]} for #{note[:duration]} bars"
93
+ else
94
+ puts "Note #{note[:pitch]} at #{note[:position]} for #{note[:duration]} bars (vel: #{note[:velocity]})"
95
+ end
96
+ end
97
+
98
+ # Access raw recorded messages if needed
99
+ raw_messages = recorder.raw # Array of timestamped MIDI events
100
+
101
+ # Clear for next recording
102
+ recorder.clear
103
+ ```
104
+
105
+ **Transcription output format:**
106
+
107
+ Each note hash contains:
108
+ - `:position` - Sequencer position (Rational) when note started
109
+ - `:channel` - MIDI channel (0-15)
110
+ - `:pitch` - MIDI note number (0-127) or `:silence` for gaps
111
+ - `:velocity` - Note-on velocity (0-127)
112
+ - `:duration` - Note duration in bars (Rational)
113
+ - `:velocity_off` - Note-off velocity (0-127)
114
+
115
+ ## API Reference
116
+
117
+ **Complete API documentation:**
118
+ - [Musa::MIDIVoices](https://rubydoc.info/gems/musa-dsl/Musa/MIDIVoices) - Voice management and polyphonic playback
119
+ - [Musa::MIDIRecorder](https://rubydoc.info/gems/musa-dsl/Musa/MIDIRecorder) - MIDI input recording and transcription
120
+
121
+ **Source code:** `lib/midi/`
122
+
123
+