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,98 @@
1
+ # Sequencer - Temporal Engine
2
+
3
+ The Sequencer manages time-based event scheduling with microsecond precision, supporting complex polyrhythmic and polytemporal structures.
4
+
5
+ ```ruby
6
+ require 'musa-dsl'
7
+ include Musa::All
8
+
9
+ # Setup: Create clock and transport (for real-time execution)
10
+ clock = TimerClock.new(bpm: 120, ticks_per_beat: 24)
11
+ transport = Transport.new(clock, 4, 24) # 4 beats per bar, 24 ticks per beat
12
+
13
+ # Define series outside DSL block (Series constructors not available in DSL context)
14
+ melody = S({ note: 60, duration: 1/2r }, { note: 62, duration: 1/2r },
15
+ { note: 64, duration: 1/2r }, { note: 65, duration: 1/2r },
16
+ { note: 67, duration: 1/2r }, { note: 65, duration: 1/2r },
17
+ { note: 64, duration: 1/2r }, { note: 62, duration: 1/2r })
18
+
19
+ # Program sequencer using DSL
20
+ transport.sequencer.with do
21
+ # Custom event handlers (on/launch)
22
+ on :section_change do |name|
23
+ puts "Section: #{name}"
24
+ end
25
+
26
+ # Immediate event (now)
27
+ now do
28
+ launch :section_change, "Start"
29
+ end
30
+
31
+ # Absolute positioning (at): event at bar 1
32
+ at 1 do
33
+ puts "Bar 1: position #{position}"
34
+ end
35
+
36
+ # Relative positioning (wait): event 2 bars later
37
+ wait 2 do
38
+ puts "Bar 3: position #{position}"
39
+ end
40
+
41
+ # Play series (play): reproduces series with automatic timing
42
+ at 5 do
43
+ play melody do |note:, duration:, control:|
44
+ puts "Playing note: #{note}, duration: #{duration}"
45
+ end
46
+ end
47
+
48
+ # Recurring event (every) with stop control
49
+ beat_loop = nil
50
+ at 10 do
51
+ # Store control object to stop it later
52
+ beat_loop = every 2, duration: 10 do
53
+ puts "Beat at position #{position}"
54
+ end
55
+ end
56
+
57
+ # Stop the beat loop at bar 18
58
+ at 18 do
59
+ beat_loop.stop if beat_loop
60
+ puts "Beat loop stopped"
61
+ end
62
+
63
+ # Animated value (move) from 0 to 10 over 4 bars
64
+ at 20 do
65
+ move from: 0, to: 10, duration: 4, every: 1/2r do |value|
66
+ puts "Value: #{value.round(2)}"
67
+ end
68
+ end
69
+
70
+ # Multi-parameter animation (move with hash)
71
+ at 25 do
72
+ move from: { pitch: 60, vel: 60 },
73
+ to: { pitch: 72, vel: 100 },
74
+ duration: 2,
75
+ every: 1/4r do |values|
76
+ puts "Pitch: #{values[:pitch].round}, Velocity: #{values[:vel].round}"
77
+ end
78
+ end
79
+
80
+ # Final event
81
+ at 30 do
82
+ launch :section_change, "End"
83
+ puts "Finished at position #{position}"
84
+ end
85
+ end
86
+
87
+ # Start real-time playback
88
+ transport.start
89
+ ```
90
+
91
+ ## API Reference
92
+
93
+ **Complete API documentation:**
94
+ - [Musa::Sequencer](https://rubydoc.info/gems/musa-dsl/Musa/Sequencer) - Main sequencer class and DSL
95
+
96
+ **Source code:** `lib/sequencer/`
97
+
98
+
@@ -0,0 +1,302 @@
1
+ # Series - Sequence Generators
2
+
3
+ Series are the fundamental building blocks for generating musical sequences. They provide functional operations for transforming pitches, rhythms, dynamics, and any musical parameter.
4
+
5
+ ## Basic Series Operations
6
+
7
+ ```ruby
8
+ require 'musa-dsl'
9
+ include Musa::Series
10
+
11
+ # S constructor: Create series from values
12
+ melody = S(0, 2, 4, 5, 7).repeat(2)
13
+ melody.i.to_a # => [0, 2, 4, 5, 7, 0, 2, 4, 5, 7]
14
+
15
+ # Transform with map
16
+ transposed = S(60, 64, 67).map { |n| n + 12 }
17
+ transposed.i.to_a # => [72, 76, 79]
18
+
19
+ # Filter with select
20
+ evens = S(1, 2, 3, 4, 5, 6).select { |n| n.even? }
21
+ evens.i.to_a # => [2, 4, 6]
22
+ ```
23
+
24
+ ## Combining Multiple Parameters
25
+
26
+ Use `.with` to combine pitches, durations, and velocities:
27
+
28
+ ```ruby
29
+ # Combine pitch, duration, and velocity
30
+ pitches = S(60, 64, 67, 72)
31
+ durations = S(1r, 1/2r, 1/2r, 1r)
32
+ velocities = S(96, 80, 90, 100)
33
+
34
+ notes = pitches.with(dur: durations, vel: velocities) do |p, dur:, vel:|
35
+ { pitch: p, duration: dur, velocity: vel }
36
+ end
37
+
38
+ notes.i.to_a
39
+ # => [{pitch: 60, duration: 1, velocity: 96},
40
+ # {pitch: 64, duration: 1/2, velocity: 80},
41
+ # {pitch: 67, duration: 1/2, velocity: 90},
42
+ # {pitch: 72, duration: 1, velocity: 100}]
43
+ ```
44
+
45
+ **Creating PDV with `H()` and `HC()`:**
46
+
47
+ When series have different lengths, use `H` (stops at shortest) or `HC` (cycles all series):
48
+
49
+ ```ruby
50
+ # Create PDV from series of different sizes
51
+ pitches = S(60, 62, 64, 65, 67) # 5 notes
52
+ durations = S(1r, 1/2r, 1/4r) # 3 durations
53
+ velocities = S(96, 80, 90, 100) # 4 velocities
54
+
55
+ # H: Stop when shortest series exhausts (3 notes - limited by durations)
56
+ notes = H(pitch: pitches, duration: durations, velocity: velocities)
57
+
58
+ notes.i.to_a
59
+ # => [{pitch: 60, duration: 1, velocity: 96},
60
+ # {pitch: 62, duration: 1/2, velocity: 80},
61
+ # {pitch: 64, duration: 1/4, velocity: 90}]
62
+
63
+ # HC: Continue cycling all series (cycles until common multiple)
64
+ notes_cycling = HC(pitch: pitches, duration: durations, velocity: velocities)
65
+ .max_size(7) # Limit output for readability
66
+
67
+ notes_cycling.i.to_a
68
+ # => [{pitch: 60, duration: 1, velocity: 96},
69
+ # {pitch: 62, duration: 1/2, velocity: 80},
70
+ # {pitch: 64, duration: 1/4, velocity: 90},
71
+ # {pitch: 65, duration: 1, velocity: 100},
72
+ # {pitch: 67, duration: 1/2, velocity: 96},
73
+ # {pitch: 60, duration: 1/4, velocity: 80},
74
+ # {pitch: 62, duration: 1, velocity: 90}]
75
+ ```
76
+
77
+ ## Merging Melodic Phrases
78
+
79
+ Use `MERGE` to concatenate multiple series:
80
+
81
+ ```ruby
82
+ # Build melody from phrases
83
+ phrase1 = S(60, 64, 67) # C major triad ascending
84
+ phrase2 = S(72, 69, 65) # Descending from octave
85
+ phrase3 = S(60, 62, 64) # Scale fragment
86
+
87
+ melody = MERGE(phrase1, phrase2, phrase3)
88
+ melody.i.to_a # => [60, 64, 67, 72, 69, 65, 60, 62, 64]
89
+
90
+ # Repeat merged structure
91
+ section = MERGE(S(1, 2, 3), S(4, 5, 6)).repeat(2)
92
+ section.i.to_a # => [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
93
+ ```
94
+
95
+ ## Numeric Generators
96
+
97
+ ```ruby
98
+ # FOR: Numeric ranges
99
+ ascending = FOR(from: 0, to: 7, step: 1)
100
+ ascending.i.to_a # => [0, 1, 2, 3, 4, 5, 6, 7]
101
+
102
+ descending = FOR(from: 10, to: 0, step: 2)
103
+ descending.i.to_a # => [10, 8, 6, 4, 2, 0]
104
+
105
+ # FIBO: Fibonacci rhythmic proportions
106
+ rhythm = FIBO().max_size(8).map { |n| Rational(n, 16) }
107
+ rhythm.i.to_a
108
+ # => [1/16, 1/16, 1/8, 3/16, 5/16, 1/2, 13/16, 21/16]
109
+
110
+ # RND: Random melody with constraints
111
+ melody = RND(60, 62, 64, 65, 67, 69, 71, 72)
112
+ .max_size(16)
113
+ .remove { |note, history| note == history.last } # No consecutive repeats
114
+
115
+ # HARMO: Harmonic series (overtones)
116
+ harmonics = HARMO(error: 0.5).max_size(10)
117
+ harmonics.i.to_a # => [0, 12, 19, 24, 28, 31, 34, 36, 38, 40]
118
+ ```
119
+
120
+ ## Structural Transformations
121
+
122
+ ```ruby
123
+ # Reverse: Retrograde motion
124
+ melody = S(60, 64, 67, 72)
125
+ retrograde = melody.reverse
126
+ retrograde.i.to_a # => [72, 67, 64, 60]
127
+
128
+ # merge operation: Flatten serie of series
129
+ chunks = S(1, 2, 3, 4, 5, 6).cut(2) # Split into pairs (serie of series)
130
+
131
+ # Each chunk is a serie, use .merge to flatten
132
+ reconstructed = chunks.merge
133
+ reconstructed.i.to_a # => [1, 2, 3, 4, 5, 6]
134
+
135
+ # Chaining operations
136
+ result = S(60, 62, 64, 65, 67, 69, 71, 72)
137
+ .select { |n| n.even? } # Keep even pitches: [60, 62, 64, 72]
138
+ .map { |n| n + 12 } # Transpose up octave: [72, 74, 76, 84]
139
+ .reverse # Retrograde: [84, 76, 74, 72]
140
+ .repeat(2) # Repeat twice
141
+
142
+ result.i.to_a # => [84, 76, 74, 72, 84, 76, 74, 72]
143
+ ```
144
+
145
+ **Serie Constructors:**
146
+ - `S(...)` - Array serie
147
+ - `E(&block)` - Serie from evaluation block
148
+ - `H(k1: s1, k2: s2, ...)` - Hash serie from series (stops at shortest)
149
+ - `HC(k1: s1, k2: s2, ...)` - Hash combined (cycles all series)
150
+ - `A(s1, s2, ...)` - Array of series (stops at shortest)
151
+ - `AC(s1, s2, ...)` - Array combined (cycles all series)
152
+ - `FOR(from:, to:, step:)` - Numeric range generator
153
+ - `MERGE(s1, s2, ...)` - Concatenate series sequentially
154
+ - `RND(...)` - Random values (infinite)
155
+ - `RND1(...)` - Random single value (exhausts after one)
156
+ - `SIN(steps:, amplitude:, center:)` - Sinusoidal waveform
157
+ - `FIBO()` - Fibonacci sequence
158
+ - `HARMO(error:, extended:)` - Harmonic series (overtones)
159
+
160
+ **Serie Operations:**
161
+ - `.map(&block)` - Transform each value
162
+ - `.select(&block)`, `.remove(&block)` - Filter values
163
+ - `.with(*series, &block)` - Combine multiple series
164
+ - `.hashify(*keys)` - Convert array values to hash
165
+ - `.repeat(times)`, `.autorestart` - Repetition control
166
+ - `.reverse` - Reverse order
167
+ - `.randomize(random:)` - Randomize order
168
+ - `.merge`, `.flatten` - Flatten nested series
169
+ - `.cut(length)` - Split into chunks
170
+ - `.max_size(n)`, `.skip(n)` - Limit/offset control
171
+ - `.shift(n)` - Circular rotation (negative: rotate left, positive: rotate right)
172
+ - `.after(*series)` - Concatenate series
173
+ - `.switch(*series)`, `.multiplex(*series)` - Switch between series
174
+ - `.lock` - Lock/freeze values
175
+ - `.anticipate(&block)`, `.lazy(&block)` - Advanced evaluation
176
+
177
+ ## API Reference
178
+
179
+ **Complete API documentation:**
180
+ - [Musa::Series](https://rubydoc.info/gems/musa-dsl/Musa/Series) - Sequence generators and operations
181
+
182
+ **Source code:** `lib/musa-dsl/series/`
183
+
184
+ ## Specialized Series Types
185
+
186
+ Beyond basic operations, Series provides specialized types for advanced transformations and musical applications.
187
+
188
+ **BufferSerie** - Multiple Independent Readers:
189
+
190
+ Enables multiple "readers" to independently iterate over the same series source without interfering with each other. Essential for canonic structures (rounds, fugues), polyphonic playback from a single source, and multi-voice compositions.
191
+
192
+ ```ruby
193
+ require 'musa-dsl'
194
+ include Musa::Series
195
+
196
+ # Create buffered melody for canon
197
+ melody = S(60, 64, 67, 72, 76).buffered
198
+
199
+ # Create independent readers (voices)
200
+ voice1 = melody.buffer.i
201
+ voice2 = melody.buffer.i
202
+ voice3 = melody.buffer.i
203
+
204
+ # Each voice progresses independently
205
+ voice1.next_value # => 60
206
+ voice1.next_value # => 64
207
+
208
+ voice2.next_value # => 60 (independent of voice1)
209
+ voice3.next_value # => 60 (independent of others)
210
+
211
+ voice1.next_value # => 67
212
+ voice2.next_value # => 64
213
+
214
+ # Use in canon: play voice2 delayed by 2 beats, voice3 delayed by 4 beats
215
+ # Each voice reads the same melodic material at its own pace
216
+ ```
217
+
218
+ **QuantizerSerie** - Value Quantization:
219
+
220
+ Quantizes continuous time-value pairs to discrete steps. Useful for converting MIDI controller data to discrete values, snapping pitch bends to semitones, or generating stepped automation curves.
221
+
222
+ Two quantization modes:
223
+ - **Raw mode**: Rounds values to nearest step with configurable boundary inclusion
224
+ - **Predictive mode**: Predicts crossings of quantization boundaries for smooth transitions
225
+
226
+ ```ruby
227
+ require 'musa-dsl'
228
+ include Musa::Series
229
+
230
+ # Example 1: Quantize continuous pitch bend to semitones
231
+ pitch_bend = S({ time: 0r, value: 60.3 },
232
+ { time: 1r, value: 61.8 },
233
+ { time: 2r, value: 63.1 })
234
+
235
+ quantized = pitch_bend.quantize(step: 1) # Quantize to integer semitones
236
+
237
+ quantized.i.to_a
238
+ # => [{ time: 0r, value: 60, duration: 1r },
239
+ # { time: 1r, value: 62, duration: 1r }]
240
+
241
+ # Example 2: Predictive quantization for smooth crossings
242
+ continuous = S({ time: 0r, value: 0 }, { time: 4r, value: 10 })
243
+
244
+ predicted = continuous.quantize(step: 2, predictive: true)
245
+
246
+ predicted.i.to_a
247
+ # Generates crossing points at values 0, 2, 4, 6, 8, 10
248
+ # with precise timing for each boundary crossing
249
+ ```
250
+
251
+ **TimedSerie Operations** - Time-Based Merging:
252
+
253
+ Operations for series with explicit `:time` attributes. Enables multi-track MIDI sequencing, polyphonic event streams, and synchronized parameter automation.
254
+
255
+ ```ruby
256
+ require 'musa-dsl'
257
+ include Musa::Series
258
+
259
+ # Create independent melodic lines with timing
260
+ melody = S({ time: 0r, value: 60 },
261
+ { time: 1r, value: 64 },
262
+ { time: 2r, value: 67 })
263
+
264
+ bass = S({ time: 0r, value: 36 },
265
+ { time: 2r, value: 38 },
266
+ { time: 4r, value: 41 })
267
+
268
+ harmony = S({ time: 0r, value: 64 },
269
+ { time: 2r, value: 67 })
270
+
271
+ # Merge by time using TIMED_UNION (hash mode)
272
+ combined = TIMED_UNION(melody: melody, bass: bass, harmony: harmony)
273
+
274
+ combined.i.to_a
275
+ # => [{ time: 0r, value: { melody: 60, bass: 36, harmony: 64 } },
276
+ # { time: 1r, value: { melody: 64, bass: nil, harmony: nil } },
277
+ # { time: 2r, value: { melody: 67, bass: 38, harmony: 67 } },
278
+ # { time: 4r, value: { melody: nil, bass: 41, harmony: nil } }]
279
+
280
+ # Array mode for unnamed voices
281
+ voice1 = S({ time: 0r, value: 60 }, { time: 1r, value: 64 })
282
+ voice2 = S({ time: 0r, value: 48 }, { time: 1r, value: 52 })
283
+
284
+ merged = TIMED_UNION(voice1, voice2)
285
+
286
+ merged.i.to_a
287
+ # => [{ time: 0r, value: [60, 48] },
288
+ # { time: 1r, value: [64, 52] }]
289
+
290
+ # Flatten timed values
291
+ multi = S({ time: 0r, value: { soprano: 60, alto: 64 } })
292
+ flat = multi.flatten_timed.i.next_value
293
+ # => { soprano: { time: 0r, value: 60 },
294
+ # alto: { time: 0r, value: 64 } }
295
+
296
+ # Compact removes nil values
297
+ sparse = S({ time: 0r, value: [60, nil, 67] })
298
+ compact = sparse.compact_timed.i.to_a
299
+ # Removes entries where all values are nil
300
+ ```
301
+
302
+
@@ -0,0 +1,152 @@
1
+ # Transcription - MIDI & MusicXML Output
2
+
3
+ The Transcription system converts GDV events to MIDI (with ornament expansion) or MusicXML format (preserving ornaments as symbols).
4
+
5
+ ## MIDI with Ornament Expansion
6
+
7
+ ```ruby
8
+ require 'musa-dsl'
9
+
10
+ using Musa::Extension::Neumas
11
+
12
+ # Neuma notation with ornaments: trill (.tr) and mordent (.mor)
13
+ neumas = "(0 1 mf) (+2 1 tr) (+4 1 mor) (+5 1)"
14
+
15
+ # Create scale and decoder
16
+ scale = Musa::Scales::Scales.et12[440.0].major[60]
17
+ decoder = Musa::Neumas::Decoders::NeumaDecoder.new(scale, base_duration: 1/4r)
18
+
19
+ # Create MIDI transcriptor with ornament expansion
20
+ transcriptor = Musa::Transcription::Transcriptor.new(
21
+ Musa::Transcriptors::FromGDV::ToMIDI.transcription_set(duration_factor: 1/6r),
22
+ base_duration: 1/4r,
23
+ tick_duration: 1/96r
24
+ )
25
+
26
+ # Parse and expand ornaments to PDV
27
+ result = Musa::Neumalang::Neumalang.parse(neumas, decode_with: decoder)
28
+ .process_with { |gdv| transcriptor.transcript(gdv) }
29
+ .map { |gdv| gdv.to_pdv(scale) }
30
+ .to_a(recursive: true)
31
+
32
+ # View expanded notes (ornaments rendered as note sequences)
33
+ result.each do |pdv|
34
+ puts "Pitch: #{pdv[:pitch]}, Duration: #{pdv[:duration]}, Velocity: #{pdv[:velocity]}"
35
+ end
36
+ # => Pitch: 60, Duration: 1/4, Velocity: 80 # C4 (no ornament)
37
+ # Pitch: 65, Duration: 1/24, Velocity: 80 # Trill: F4 (upper neighbor)
38
+ # Pitch: 64, Duration: 1/24, Velocity: 80 # Trill: E4 (original)
39
+ # Pitch: 65, Duration: 1/24, Velocity: 80 # Trill: F4
40
+ # Pitch: 64, Duration: 1/24, Velocity: 80 # Trill: E4
41
+ # ... (trill alternation continues)
42
+ # Pitch: 71, Duration: 1/24, Velocity: 80 # Mordent: B4 (original)
43
+ # Pitch: 72, Duration: 1/24, Velocity: 80 # Mordent: C5 (neighbor)
44
+ # Pitch: 71, Duration: 1/6, Velocity: 80 # Mordent: B4 (return)
45
+ # Pitch: 79, Duration: 1/4, Velocity: 80 # G5 (no ornament)
46
+ ```
47
+
48
+ **Supported ornaments:**
49
+ - `.tr` - Trill (rapid alternation with upper note)
50
+ - `.mor` - Mordent (quick alternation with adjacent note)
51
+ - `.turn` - Turn (four-note figure)
52
+ - `.st` - Staccato (shortened duration)
53
+
54
+ ## MusicXML with Ornament Symbols
55
+
56
+ ```ruby
57
+ require 'musa-dsl'
58
+
59
+ using Musa::Extension::Neumas
60
+
61
+ # Same phrase as MIDI example (ornaments preserved as symbols)
62
+ neumas = "(0 1 mf) (+2 1 tr) (+4 1 mor) (+5 1)"
63
+
64
+ # Create scale and decoder
65
+ scale = Musa::Scales::Scales.et12[440.0].major[60]
66
+ decoder = Musa::Neumas::Decoders::NeumaDecoder.new(scale, base_duration: 1/4r)
67
+
68
+ # Create MusicXML transcriptor (preserves ornaments as symbols)
69
+ transcriptor = Musa::Transcription::Transcriptor.new(
70
+ Musa::Transcriptors::FromGDV::ToMusicXML.transcription_set,
71
+ base_duration: 1/4r,
72
+ tick_duration: 1/96r
73
+ )
74
+
75
+ # Parse and convert to GDV with preserved ornament markers
76
+ serie = Musa::Neumalang::Neumalang.parse(neumas, decode_with: decoder)
77
+ .process_with { |gdv| transcriptor.transcript(gdv) }
78
+
79
+ # Create Score and use sequencer to fill it
80
+ score = Musa::Datasets::Score.new
81
+ sequencer = Musa::Sequencer::Sequencer.new(4, 24)
82
+
83
+ sequencer.at 1 do
84
+ play serie, decoder: decoder, mode: :neumalang do |gdv|
85
+ pdv = gdv.to_pdv(scale)
86
+ score.at(position, add: pdv) # position is automatically tracked by sequencer
87
+ end
88
+ end
89
+
90
+ sequencer.run
91
+
92
+ # Convert to MusicXML
93
+ mxml = score.to_mxml(
94
+ 4, 24, # 4 beats per bar, 24 ticks per beat
95
+ bpm: 120,
96
+ title: 'Ornaments Example',
97
+ creators: { composer: 'MusaDSL' },
98
+ parts: { piano: { name: 'Piano', clefs: { g: 2 } } }
99
+ )
100
+
101
+ # Generated MusicXML (excerpt showing notes with ornaments):
102
+ puts mxml.to_xml.string
103
+ ```
104
+
105
+ **Generated MusicXML output (excerpt):**
106
+ ```xml
107
+ <note>
108
+ <pitch>
109
+ <step>C</step>
110
+ <octave>4</octave>
111
+ </pitch>
112
+ <duration>24</duration>
113
+ <type>quarter</type>
114
+ </note>
115
+ <note>
116
+ <pitch>
117
+ <step>E</step>
118
+ <octave>4</octave>
119
+ </pitch>
120
+ <duration>24</duration>
121
+ <type>quarter</type>
122
+ <notations>
123
+ <ornaments>
124
+ <trill-mark /> <!-- Trill preserved as notation symbol -->
125
+ </ornaments>
126
+ </notations>
127
+ </note>
128
+ <note>
129
+ <pitch>
130
+ <step>B</step>
131
+ <octave>4</octave>
132
+ </pitch>
133
+ <duration>24</duration>
134
+ <type>quarter</type>
135
+ <notations>
136
+ <ornaments>
137
+ <inverted-mordent /> <!-- Mordent preserved as notation symbol -->
138
+ </ornaments>
139
+ </notations>
140
+ </note>
141
+ ```
142
+
143
+ **Note:** Only 4 notes (vs 11 in MIDI) - ornaments preserved as notation symbols, not expanded
144
+
145
+ ## API Reference
146
+
147
+ **Complete API documentation:**
148
+ - [Musa::Transcription](https://rubydoc.info/gems/musa-dsl/Musa/Transcription) - Musical event transformation and ornament expansion
149
+
150
+ **Source code:** `lib/transcription/` and `lib/musicxml/`
151
+
152
+