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
@@ -2,7 +2,96 @@ require_relative 'from-gdv'
2
2
 
3
3
  module Musa::Transcriptors
4
4
  module FromGDV
5
+ # MIDI-specific GDV transcriptors for playback output.
6
+ #
7
+ # Transcribes GDV events to MIDI playback format by expanding ornaments and
8
+ # articulations into explicit note sequences. Unlike MusicXML transcription,
9
+ # MIDI transcription generates the actual notes to be played.
10
+ #
11
+ # ## MIDI vs MusicXML Approach
12
+ #
13
+ # MIDI transcription expands ornaments for playback:
14
+ #
15
+ # - **MIDI**: Ornaments become explicit note sequences with calculated durations
16
+ # - **MusicXML**: Ornaments preserved as notation symbols
17
+ #
18
+ # ## Supported Ornaments & Articulations
19
+ #
20
+ # - **Appogiatura** (`:appogiatura`): Grace note before main note
21
+ # - **Mordent** (`.mor`): Quick alternation with adjacent note
22
+ # - **Turn** (`.turn`): Four-note figure circling main note
23
+ # - **Trill** (`.tr`): Rapid alternation with upper note
24
+ # - **Staccato** (`.st`): Shortened note duration
25
+ #
26
+ # ## Duration Factor
27
+ #
28
+ # Many ornaments use a configurable `duration_factor` (default 1/4) to determine
29
+ # ornament note durations relative to `base_duration`:
30
+ # ```ruby
31
+ # ornament_duration = base_duration * duration_factor
32
+ # ```
33
+ #
34
+ # ## Usage
35
+ #
36
+ # ```ruby
37
+ # transcriptor = Musa::Transcription::Transcriptor.new(
38
+ # Musa::Transcriptors::FromGDV::ToMIDI.transcription_set(duration_factor: 1/8r),
39
+ # base_duration: 1/4r,
40
+ # tick_duration: 1/96r
41
+ # )
42
+ # result = transcriptor.transcript(gdv_event)
43
+ # ```
44
+ #
45
+ # ## Transcription Set
46
+ #
47
+ # The `transcription_set` returns transcriptors applied in order:
48
+ #
49
+ # 1. `Appogiatura` - Expand appogiatura grace notes
50
+ # 2. `Mordent` - Expand mordent ornaments
51
+ # 3. `Turn` - Expand turn ornaments
52
+ # 4. `Trill` - Expand trill ornaments
53
+ # 5. `Staccato` - Apply staccato articulation
54
+ # 6. `Base` - Process base/rest markers
55
+ #
56
+ # @example MIDI trill expansion
57
+ # gdv = { grade: 0, duration: 1r, tr: true }
58
+ # transcriptor = Musa::Transcriptors::FromGDV::ToMIDI::Trill.new
59
+ # result = transcriptor.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
60
+ # # => [
61
+ # # { grade: 1, duration: 1/16r }, # Upper neighbor
62
+ # # { grade: 0, duration: 1/16r }, # Main note
63
+ # # { grade: 1, duration: 1/16r }, # Upper neighbor
64
+ # # ...
65
+ # # ]
66
+ #
67
+ # @see ToMusicXML Notation-preserving transcription
68
+ # @see Musa::MIDIVoices MIDI output system
69
+ #
70
+ # @api public
5
71
  module ToMIDI
72
+ # Returns standard transcription set for MIDI output.
73
+ #
74
+ # Creates array of transcriptors for processing GDV to MIDI playback format,
75
+ # expanding all ornaments to explicit note sequences.
76
+ #
77
+ # @param duration_factor [Rational] factor for ornament note durations
78
+ # relative to base_duration (default: 1/4)
79
+ #
80
+ # @return [Array<FeatureTranscriptor>] transcriptor chain
81
+ #
82
+ # @example Create MIDI transcription chain with default factor
83
+ # transcriptors = Musa::Transcriptors::FromGDV::ToMIDI.transcription_set
84
+ # transcriptor = Musa::Transcription::Transcriptor.new(
85
+ # transcriptors,
86
+ # base_duration: 1/4r
87
+ # )
88
+ #
89
+ # @example Custom duration factor for faster ornaments
90
+ # transcriptors = Musa::Transcriptors::FromGDV::ToMIDI.transcription_set(
91
+ # duration_factor: 1/8r
92
+ # )
93
+ #
94
+ # @api public
6
95
  def self.transcription_set(duration_factor: nil)
7
96
  [ Appogiatura.new,
8
97
  Mordent.new(duration_factor: duration_factor),
@@ -12,8 +101,60 @@ module Musa::Transcriptors
12
101
  Base.new ]
13
102
  end
14
103
 
104
+ # Appogiatura transcriptor for MIDI playback.
105
+ #
106
+ # Expands appogiatura ornaments into two sequential notes for MIDI playback.
107
+ # The grace note (appogiatura) is played first, followed by the main note
108
+ # with reduced duration.
109
+ #
110
+ # ## Processing
111
+ #
112
+ # Given an appogiatura marking:
113
+ # ```ruby
114
+ # {
115
+ # grade: 0,
116
+ # duration: 1r,
117
+ # appogiatura: { grade: -1, duration: 1/8r }
118
+ # }
119
+ # ```
120
+ #
121
+ # Expands to two notes:
122
+ # ```ruby
123
+ # [
124
+ # { grade: -1, duration: 1/8r }, # Grace note
125
+ # { grade: 0, duration: 7/8r } # Main note (reduced)
126
+ # ]
127
+ # ```
128
+ #
129
+ # The main note's duration is reduced by the appogiatura duration to maintain
130
+ # total duration.
131
+ #
132
+ # @example Appogiatura expansion
133
+ # app = Appogiatura.new
134
+ # gdv = {
135
+ # grade: 0,
136
+ # duration: 1r,
137
+ # appogiatura: { grade: -1, duration: 1/8r }
138
+ # }
139
+ # result = app.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
140
+ # # => [
141
+ # # { grade: -1, duration: 1/8r },
142
+ # # { grade: 0, duration: 7/8r }
143
+ # # ]
144
+ #
145
+ # @api public
15
146
  # Process: appogiatura (neuma)neuma
16
147
  class Appogiatura < Musa::Transcription::FeatureTranscriptor
148
+ # Transcribes appogiatura to two-note sequence.
149
+ #
150
+ # @param gdv [Hash] GDV event possibly containing `:appogiatura`
151
+ # @param base_duration [Rational] base duration unit
152
+ # @param tick_duration [Rational] minimum tick duration
153
+ #
154
+ # @return [Array<Hash>, Hash] array with grace note and main note, or
155
+ # unchanged event if no appogiatura
156
+ #
157
+ # @api public
17
158
  def transcript(gdv, base_duration:, tick_duration:)
18
159
  gdv_appogiatura = gdv.delete :appogiatura
19
160
 
@@ -31,12 +172,74 @@ module Musa::Transcriptors
31
172
  end
32
173
  end
33
174
 
175
+ # Mordent transcriptor for MIDI playback.
176
+ #
177
+ # Expands mordent ornaments into a three-note sequence: main note, adjacent
178
+ # note (upper or lower), then main note. The mordent is a quick ornament
179
+ # typically used to emphasize a note.
180
+ #
181
+ # ## Mordent Types
182
+ #
183
+ # - `.mor` or `.mor(:up)` - Upper mordent (main, upper neighbor, main)
184
+ # - `.mor(:down)` or `.mor(:low)` - Lower mordent (main, lower neighbor, main)
185
+ #
186
+ # ## Processing
187
+ #
188
+ # Given `.mor` on a note:
189
+ # ```ruby
190
+ # { grade: 0, duration: 1r, mor: true }
191
+ # ```
192
+ #
193
+ # Expands to three notes:
194
+ # ```ruby
195
+ # [
196
+ # { grade: 0, duration: 1/16r }, # Main note (short)
197
+ # { grade: 1, duration: 1/16r }, # Upper neighbor (short)
198
+ # { grade: 0, duration: 7/8r } # Main note (remaining duration)
199
+ # ]
200
+ # ```
201
+ #
202
+ # ## Duration Calculation
203
+ #
204
+ # Short notes duration: `base_duration * duration_factor` (default 1/4)
205
+ # Final note gets remaining duration: `original - 2 * short_duration`
206
+ #
207
+ # @example Upper mordent
208
+ # mor = Mordent.new(duration_factor: 1/4r)
209
+ # gdv = { grade: 0, duration: 1r, mor: true }
210
+ # result = mor.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
211
+ # # => [
212
+ # # { grade: 0, duration: 1/16r },
213
+ # # { grade: 1, duration: 1/16r },
214
+ # # { grade: 0, duration: 7/8r }
215
+ # # ]
216
+ #
217
+ # @example Lower mordent
218
+ # gdv = { grade: 0, duration: 1r, mor: :down }
219
+ # # Uses lower neighbor (grade: -1)
220
+ #
221
+ # @api public
34
222
  # Process: .mor
35
223
  class Mordent < Musa::Transcription::FeatureTranscriptor
224
+ # Creates mordent transcriptor.
225
+ #
226
+ # @param duration_factor [Rational] factor for ornament note duration
227
+ # relative to base_duration (default: 1/4)
228
+ #
229
+ # @api public
36
230
  def initialize(duration_factor: nil)
37
231
  @duration_factor = duration_factor || 1/4r
38
232
  end
39
233
 
234
+ # Transcribes mordent to three-note sequence.
235
+ #
236
+ # @param gdv [Hash] GDV event possibly containing `:mor`
237
+ # @param base_duration [Rational] base duration unit
238
+ # @param tick_duration [Rational] minimum tick duration
239
+ #
240
+ # @return [Array<Hash>, Hash] array with mordent notes, or unchanged event
241
+ #
242
+ # @api public
40
243
  def transcript(gdv, base_duration:, tick_duration:)
41
244
  mor = gdv.delete :mor
42
245
 
@@ -74,8 +277,68 @@ module Musa::Transcriptors
74
277
  end
75
278
  end
76
279
 
280
+ # Turn transcriptor for MIDI playback.
281
+ #
282
+ # Expands turn ornaments into a four-note figure that circles around the
283
+ # main note. A turn is a melodic embellishment consisting of the note above,
284
+ # the principal note, the note below, and the principal note again.
285
+ #
286
+ # ## Turn Types
287
+ #
288
+ # - `.turn` or `.turn(:up)` - Start with upper neighbor (upper, main, lower, main)
289
+ # - `.turn(:down)` or `.turn(:low)` - Start with lower neighbor (lower, main, upper, main)
290
+ #
291
+ # ## Processing
292
+ #
293
+ # Given `.turn` on a note:
294
+ # ```ruby
295
+ # { grade: 0, duration: 1r, turn: true }
296
+ # ```
297
+ #
298
+ # Expands to four equal notes:
299
+ # ```ruby
300
+ # [
301
+ # { grade: 1, duration: 1/4r }, # Upper neighbor
302
+ # { grade: 0, duration: 1/4r }, # Main note
303
+ # { grade: -1, duration: 1/4r }, # Lower neighbor
304
+ # { grade: 0, duration: 1/4r } # Main note
305
+ # ]
306
+ # ```
307
+ #
308
+ # Each note gets 1/4 of the original duration.
309
+ #
310
+ # @example Upper turn
311
+ # turn = Turn.new
312
+ # gdv = { grade: 0, duration: 1r, turn: true }
313
+ # result = turn.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
314
+ # # => [
315
+ # # { grade: 1, duration: 1/4r }, # +1
316
+ # # { grade: 0, duration: 1/4r }, # 0
317
+ # # { grade: -1, duration: 1/4r }, # -1
318
+ # # { grade: 0, duration: 1/4r } # 0
319
+ # # ]
320
+ #
321
+ # @example Lower turn
322
+ # gdv = { grade: 0, duration: 1r, turn: :down }
323
+ # # => [
324
+ # # { grade: -1, duration: 1/4r }, # -1
325
+ # # { grade: 0, duration: 1/4r }, # 0
326
+ # # { grade: 1, duration: 1/4r }, # +1
327
+ # # { grade: 0, duration: 1/4r } # 0
328
+ # # ]
329
+ #
330
+ # @api public
77
331
  # Process: .turn
78
332
  class Turn < Musa::Transcription::FeatureTranscriptor
333
+ # Transcribes turn to four-note sequence.
334
+ #
335
+ # @param gdv [Hash] GDV event possibly containing `:turn`
336
+ # @param base_duration [Rational] base duration unit
337
+ # @param tick_duration [Rational] minimum tick duration
338
+ #
339
+ # @return [Array<Hash>, Hash] array with turn notes, or unchanged event
340
+ #
341
+ # @api public
79
342
  def transcript(gdv, base_duration:, tick_duration:)
80
343
  turn = gdv.delete :turn
81
344
 
@@ -84,7 +347,7 @@ module Musa::Transcriptors
84
347
 
85
348
  check(turn) do |turn|
86
349
  case turn
87
- when :true, :up
350
+ when true, :up
88
351
  start = :up
89
352
  when :down, :low
90
353
  start = :down
@@ -115,12 +378,85 @@ module Musa::Transcriptors
115
378
  end
116
379
  end
117
380
 
381
+ # Trill transcriptor for MIDI playback.
382
+ #
383
+ # Expands trill ornaments into a rapid alternation between the main note and
384
+ # its upper neighbor. The trill fills the entire note duration with alternating
385
+ # notes, with sophisticated duration management including acceleration.
386
+ #
387
+ # ## Trill Options
388
+ #
389
+ # - `.tr` or `.tr(true)` - Standard trill starting with upper neighbor
390
+ # - `.tr(:low)` - Start with lower neighbor first (2 notes)
391
+ # - `.tr(:low2)` - Start with upper but include lower neighbor (4 notes)
392
+ # - `.tr(:same)` - Start with main note
393
+ # - `.tr(factor)` - Custom duration factor (e.g., `.tr(1/8r)`)
394
+ #
395
+ # ## Duration Algorithm
396
+ #
397
+ # The trill uses a sophisticated multi-phase duration algorithm:
398
+ #
399
+ # 1. **Initial pattern**: Based on trill options (:low, :low2, :same)
400
+ # 2. **Regular pattern**: Two cycles at full `note_duration`
401
+ # 3. **Accelerando**: Cycles at 2/3 `note_duration` (faster)
402
+ # 4. **Final notes**: Distribute remaining duration
403
+ #
404
+ # ## Processing
405
+ #
406
+ # Given `.tr` on a note:
407
+ # ```ruby
408
+ # { grade: 0, duration: 1r, tr: true }
409
+ # ```
410
+ #
411
+ # Expands to alternating sequence:
412
+ # ```ruby
413
+ # [
414
+ # { grade: 1, duration: 1/16r }, # Upper (initial)
415
+ # { grade: 0, duration: 1/16r }, # Main
416
+ # { grade: 1, duration: 1/16r }, # Upper (regular)
417
+ # { grade: 0, duration: 1/16r }, # Main
418
+ # { grade: 1, duration: ~1/24r }, # Upper (accelerando)
419
+ # { grade: 0, duration: ~1/24r }, # Main
420
+ # ...
421
+ # ]
422
+ # ```
423
+ #
424
+ # @example Standard trill
425
+ # trill = Trill.new(duration_factor: 1/4r)
426
+ # gdv = { grade: 0, duration: 1r, tr: true }
427
+ # result = trill.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
428
+ # # Generates alternating upper/main notes filling duration
429
+ #
430
+ # @example Trill starting low
431
+ # gdv = { grade: 0, duration: 1r, tr: :low }
432
+ # # Starts with lower neighbor, then alternates upper/main
433
+ #
434
+ # @example Custom duration factor
435
+ # gdv = { grade: 0, duration: 1r, tr: 1/8r }
436
+ # # Faster trill with shorter note durations
437
+ #
438
+ # @api public
118
439
  # Process: .tr
119
440
  class Trill < Musa::Transcription::FeatureTranscriptor
441
+ # Creates trill transcriptor.
442
+ #
443
+ # @param duration_factor [Rational] factor for trill note duration
444
+ # relative to base_duration (default: 1/4)
445
+ #
446
+ # @api public
120
447
  def initialize(duration_factor: nil)
121
448
  @duration_factor = duration_factor || 1/4r
122
449
  end
123
450
 
451
+ # Transcribes trill to alternating note sequence.
452
+ #
453
+ # @param gdv [Hash] GDV event possibly containing `:tr`
454
+ # @param base_duration [Rational] base duration unit
455
+ # @param tick_duration [Rational] minimum tick duration
456
+ #
457
+ # @return [Array<Hash>, Hash] array with trill notes, or unchanged event
458
+ #
459
+ # @api public
124
460
  def transcript(gdv, base_duration:, tick_duration:)
125
461
  tr = gdv.delete :tr
126
462
 
@@ -192,12 +528,79 @@ module Musa::Transcriptors
192
528
  end
193
529
  end
194
530
 
531
+ # Staccato transcriptor for MIDI playback.
532
+ #
533
+ # Applies staccato articulation by shortening note duration. Instead of
534
+ # creating multiple notes, staccato modifies the `:note_duration` attribute
535
+ # to create a gap between note-off and the next note-on.
536
+ #
537
+ # ## Staccato Levels
538
+ #
539
+ # - `.st` or `.st(true)` - Half duration (1/2)
540
+ # - `.st(1)` - Half duration (1/2)
541
+ # - `.st(2)` - Quarter duration (1/4)
542
+ # - `.st(3)` - Eighth duration (1/8)
543
+ # - `.st(n)` - Duration divided by 2^n
544
+ #
545
+ # ## Processing
546
+ #
547
+ # Staccato sets `:note_duration` attribute (actual sounding duration)
548
+ # while preserving `:duration` (rhythmic position duration). The gap
549
+ # between these creates the staccato articulation.
550
+ #
551
+ # Given `.st` on a note:
552
+ # ```ruby
553
+ # { grade: 0, duration: 1r, st: true }
554
+ # ```
555
+ #
556
+ # Results in:
557
+ # ```ruby
558
+ # { grade: 0, duration: 1r, note_duration: 1/2r }
559
+ # ```
560
+ # - `duration: 1r` - Next note starts after 1 beat
561
+ # - `note_duration: 1/2r` - Note sounds for 1/2 beat (staccato gap)
562
+ #
563
+ # ## Minimum Duration
564
+ #
565
+ # A minimum duration (`base_duration * min_duration_factor`, default 1/8)
566
+ # prevents extremely short notes that might sound like clicks.
567
+ #
568
+ # @example Basic staccato
569
+ # staccato = Staccato.new
570
+ # gdv = { grade: 0, duration: 1r, st: true }
571
+ # result = staccato.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
572
+ # # => { grade: 0, duration: 1r, note_duration: 1/2r }
573
+ #
574
+ # @example Staccato level 2
575
+ # gdv = { grade: 0, duration: 1r, st: 2 }
576
+ # # => { grade: 0, duration: 1r, note_duration: 1/4r }
577
+ #
578
+ # @example Very short note (minimum enforced)
579
+ # gdv = { grade: 0, duration: 1/16r, st: true }
580
+ # # note_duration clamped to base_duration * 1/8 (minimum)
581
+ #
582
+ # @api public
195
583
  # Process: .st .st(1) .st(2) .st(3): staccato level 1 2 3
196
584
  class Staccato < Musa::Transcription::FeatureTranscriptor
585
+ # Creates staccato transcriptor.
586
+ #
587
+ # @param min_duration_factor [Rational] minimum duration factor relative
588
+ # to base_duration to prevent click-like short notes (default: 1/8)
589
+ #
590
+ # @api public
197
591
  def initialize(min_duration_factor: nil)
198
592
  @min_duration_factor = min_duration_factor || 1/8r
199
593
  end
200
594
 
595
+ # Transcribes staccato by setting note_duration.
596
+ #
597
+ # @param gdv [Hash] GDV event possibly containing `:st`
598
+ # @param base_duration [Rational] base duration unit
599
+ # @param tick_duration [Rational] minimum tick duration
600
+ #
601
+ # @return [Hash] event with `:note_duration` set if staccato, or unchanged
602
+ #
603
+ # @api public
201
604
  def transcript(gdv, base_duration:, tick_duration:)
202
605
  st = gdv.delete :st
203
606
 
@@ -2,14 +2,132 @@ require_relative 'from-gdv'
2
2
 
3
3
  module Musa::Transcriptors
4
4
  module FromGDV
5
+ # MusicXML-specific GDV transcriptors for music notation output.
6
+ #
7
+ # Transcribes GDV events to MusicXML format, handling ornaments and articulations
8
+ # as notation metadata rather than expanded note sequences. MusicXML is an XML-based
9
+ # standard for representing Western music notation.
10
+ #
11
+ # ## MusicXML vs MIDI Approach
12
+ #
13
+ # MusicXML transcription differs from MIDI transcription:
14
+ #
15
+ # - **MusicXML**: Preserves ornaments as notation symbols (grace notes, trills, etc.)
16
+ # - **MIDI**: Expands ornaments to explicit note sequences for playback
17
+ #
18
+ # ## Supported Features
19
+ #
20
+ # - **Appogiatura**: Grace note ornaments marked with `:grace` attribute
21
+ #
22
+ # ## Usage
23
+ #
24
+ # ```ruby
25
+ # transcriptor = Musa::Transcription::Transcriptor.new(
26
+ # Musa::Transcriptors::FromGDV::ToMusicXML.transcription_set,
27
+ # base_duration: 1/4r,
28
+ # tick_duration: 1/96r
29
+ # )
30
+ # result = transcriptor.transcript(gdv_event)
31
+ # ```
32
+ #
33
+ # ## Transcription Set
34
+ #
35
+ # The `transcription_set` method returns an array of transcriptors applied
36
+ # in order:
37
+ #
38
+ # 1. `Appogiatura` - Process appogiatura ornaments
39
+ # 2. `Base` - Process base/rest markers
40
+ #
41
+ # @example MusicXML appogiatura
42
+ # gdv = {
43
+ # grade: 0,
44
+ # duration: 1r,
45
+ # appogiatura: { grade: -1, duration: 1/8r }
46
+ # }
47
+ # transcriptor = Musa::Transcriptors::FromGDV::ToMusicXML::Appogiatura.new
48
+ # result = transcriptor.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
49
+ # # => [
50
+ # # { grade: -1, duration: 1/8r, grace: true },
51
+ # # { grade: 0, duration: 1r, graced: true, graced_by: {...} }
52
+ # # ]
53
+ #
54
+ # @see Musa::MusicXML MusicXML output system
55
+ # @see ToMIDI Playback-oriented transcription
5
56
  module ToMusicXML
57
+ # Returns standard transcription set for MusicXML output.
58
+ #
59
+ # Creates array of transcriptors for processing GDV to MusicXML format.
60
+ #
61
+ # @return [Array<FeatureTranscriptor>] transcriptor chain
62
+ #
63
+ # @example Create MusicXML transcription chain
64
+ # transcriptors = Musa::Transcriptors::FromGDV::ToMusicXML.transcription_set
65
+ # transcriptor = Musa::Transcription::Transcriptor.new(
66
+ # transcriptors,
67
+ # base_duration: 1/4r
68
+ # )
69
+ #
70
+ # @api public
6
71
  def self.transcription_set
7
72
  [ Appogiatura.new,
8
73
  Base.new ]
9
74
  end
10
75
 
76
+ # Appogiatura transcriptor for MusicXML notation.
77
+ #
78
+ # Processes appogiatura ornaments, marking them as grace notes for MusicXML
79
+ # output rather than expanding to explicit note sequences. The grace note
80
+ # relationship is preserved through `:grace`, `:graced`, and `:graced_by`
81
+ # attributes.
82
+ #
83
+ # ## Appogiatura Format
84
+ #
85
+ # Input GDV with `:appogiatura` key:
86
+ # ```ruby
87
+ # {
88
+ # grade: 0,
89
+ # duration: 1r,
90
+ # appogiatura: { grade: -1, duration: 1/8r }
91
+ # }
92
+ # ```
93
+ #
94
+ # Output (array of two events):
95
+ # ```ruby
96
+ # [
97
+ # { grade: -1, duration: 1/8r, grace: true }, # Grace note
98
+ # { grade: 0, duration: 1r, graced: true, graced_by: ... } # Main note
99
+ # ]
100
+ # ```
101
+ #
102
+ # ## MusicXML Representation
103
+ #
104
+ # The `:grace` attribute indicates the note should be rendered as a grace
105
+ # note in the score. The `:graced_by` reference allows the notation engine
106
+ # to properly link the grace note to its principal note.
107
+ #
108
+ # @example Process appogiatura
109
+ # app = Appogiatura.new
110
+ # gdv = {
111
+ # grade: 0,
112
+ # duration: 1r,
113
+ # appogiatura: { grade: -1, duration: 1/8r }
114
+ # }
115
+ # result = app.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
116
+ # # => [grace_note, main_note]
117
+ #
118
+ # @api public
11
119
  # Process: appogiatura (neuma)neuma
12
120
  class Appogiatura < Musa::Transcription::FeatureTranscriptor
121
+ # Transcribes GDV appogiatura to grace note representation.
122
+ #
123
+ # @param gdv [Hash] GDV event possibly containing `:appogiatura`
124
+ # @param base_duration [Rational] base duration unit
125
+ # @param tick_duration [Rational] minimum tick duration
126
+ #
127
+ # @return [Array<Hash>, Hash] array with grace note and main note, or
128
+ # unchanged event if no appogiatura
129
+ #
130
+ # @api public
13
131
  def transcript(gdv, base_duration:, tick_duration:)
14
132
  if gdv_appogiatura = gdv[:appogiatura]
15
133
  gdv.delete :appogiatura
@@ -1,9 +1,92 @@
1
+ #
2
+ #
3
+ # @api public
1
4
  require_relative 'transcription'
2
5
 
3
6
  module Musa::Transcriptors
7
+ # GDV (Grade-Duration-Velocity) base transcriptor for processing fundamental features.
8
+ #
9
+ # Provides the foundational transcriptor for handling base/rest events in GDV notation.
10
+ # GDV is Musa-DSL's internal representation for musical events with grade (pitch),
11
+ # duration, and velocity information.
12
+ #
13
+ # ## GDV Format
14
+ #
15
+ # GDV events are hashes with musical attributes:
16
+ # ```ruby
17
+ # {
18
+ # grade: 0, # Scale degree
19
+ # duration: 1r, # Rational duration
20
+ # velocity: 0.8, # Note velocity (0.0-1.0)
21
+ # base: true # Mark as base/rest (zero duration)
22
+ # }
23
+ # ```
24
+ #
25
+ # This module contains the base transcriptor (`Base`) for handling base/rest
26
+ # markers in GDV events, converting them to zero-duration structural markers.
27
+ #
28
+ # Format-specific transcriptors are in submodules:
29
+ # - {ToMIDI} - MIDI playback transcription (expands ornaments)
30
+ # - {ToMusicXML} - MusicXML notation transcription (preserves ornaments)
31
+ #
32
+ # ## Base/Rest Processing
33
+ #
34
+ # The `.base` (or `.b`) attribute marks an event as a base or rest, which is
35
+ # converted to a zero-duration event. This is useful for representing rests
36
+ # or structural markers in the musical timeline.
37
+ #
38
+ # ## Transcriptor Pattern
39
+ #
40
+ # All transcriptors follow the same pattern:
41
+ #
42
+ # 1. Extract specific features from GDV hash
43
+ # 2. Process/transform the event based on those features
44
+ # 3. Return modified event(s) or array of events
45
+ #
46
+ # @example Basic base event
47
+ # gdv = { grade: 0, duration: 1r, base: true }
48
+ # transcriptor = Musa::Transcriptors::FromGDV::Base.new
49
+ # result = transcriptor.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
50
+ # # => { duration: 0 } (marked as AbsD)
51
+ #
52
+ # @see Musa::Transcription::Transcriptor Main transcription orchestrator
53
+ # @see Musa::Transcription::FeatureTranscriptor
54
+ # @see ToMIDI MIDI-specific transcriptors
55
+ # @see ToMusicXML MusicXML-specific transcriptors
4
56
  module FromGDV
5
- # Process: .base .b
57
+ # Base transcriptor for processing `.base` or `.b` attributes.
58
+ #
59
+ # Converts GDV events marked with `:base` or `:b` to zero-duration events,
60
+ # useful for representing rests or structural markers.
61
+ #
62
+ # ## Processing
63
+ #
64
+ # - Checks for `:base` or `:b` attribute
65
+ # - If found, replaces event with `{duration: 0}` marked as `AbsD`
66
+ # - If not found, passes through unchanged
67
+ #
68
+ # @example Process base event
69
+ # base = Base.new
70
+ # gdv = { grade: 0, duration: 1r, base: true }
71
+ # result = base.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
72
+ # # => { duration: 0 }
73
+ #
74
+ # @example Normal event (unchanged)
75
+ # gdv = { grade: 0, duration: 1r }
76
+ # result = base.transcript(gdv, base_duration: 1/4r, tick_duration: 1/96r)
77
+ # # => { grade: 0, duration: 1r }
78
+ #
79
+ # @api public
6
80
  class Base < Musa::Transcription::FeatureTranscriptor
81
+ # Transcribes GDV event, converting base markers to zero-duration events.
82
+ #
83
+ # @param gdv [Hash] GDV event with musical attributes
84
+ # @param base_duration [Rational] base duration unit (e.g., quarter note)
85
+ # @param tick_duration [Rational] minimum tick duration (e.g., 1/96)
86
+ #
87
+ # @return [Hash] transcribed event (zero-duration if base, unchanged otherwise)
88
+ #
89
+ # @api public
7
90
  def transcript(gdv, base_duration:, tick_duration:)
8
91
  base = gdv.delete :base
9
92
  base ||= gdv.delete :b