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.
- checksums.yaml +4 -4
- data/.gitignore +3 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/README.md +227 -6
- data/docs/README.md +83 -0
- data/docs/api-reference.md +86 -0
- data/docs/getting-started/quick-start.md +93 -0
- data/docs/getting-started/tutorial.md +58 -0
- data/docs/subsystems/core-extensions.md +316 -0
- data/docs/subsystems/datasets.md +465 -0
- data/docs/subsystems/generative.md +290 -0
- data/docs/subsystems/matrix.md +63 -0
- data/docs/subsystems/midi.md +123 -0
- data/docs/subsystems/music.md +233 -0
- data/docs/subsystems/musicxml-builder.md +264 -0
- data/docs/subsystems/neumas.md +71 -0
- data/docs/subsystems/repl.md +135 -0
- data/docs/subsystems/sequencer.md +98 -0
- data/docs/subsystems/series.md +302 -0
- data/docs/subsystems/transcription.md +152 -0
- data/docs/subsystems/transport.md +177 -0
- data/lib/musa-dsl/core-ext/array-explode-ranges.rb +68 -0
- data/lib/musa-dsl/core-ext/arrayfy.rb +110 -0
- data/lib/musa-dsl/core-ext/attribute-builder.rb +91 -30
- data/lib/musa-dsl/core-ext/deep-copy.rb +125 -2
- data/lib/musa-dsl/core-ext/dynamic-proxy.rb +78 -0
- data/lib/musa-dsl/core-ext/extension.rb +53 -0
- data/lib/musa-dsl/core-ext/hashify.rb +162 -1
- data/lib/musa-dsl/core-ext/inspect-nice.rb +154 -0
- data/lib/musa-dsl/core-ext/smart-proc-binder.rb +117 -0
- data/lib/musa-dsl/core-ext/with.rb +114 -0
- data/lib/musa-dsl/datasets/dataset.rb +109 -0
- data/lib/musa-dsl/datasets/delta-d.rb +78 -0
- data/lib/musa-dsl/datasets/e.rb +186 -2
- data/lib/musa-dsl/datasets/gdv.rb +279 -2
- data/lib/musa-dsl/datasets/gdvd.rb +201 -0
- data/lib/musa-dsl/datasets/helper.rb +75 -0
- data/lib/musa-dsl/datasets/p.rb +177 -2
- data/lib/musa-dsl/datasets/packed-v.rb +91 -0
- data/lib/musa-dsl/datasets/pdv.rb +136 -1
- data/lib/musa-dsl/datasets/ps.rb +134 -4
- data/lib/musa-dsl/datasets/score/queriable.rb +143 -1
- data/lib/musa-dsl/datasets/score/render.rb +105 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-pdv.rb +138 -1
- data/lib/musa-dsl/datasets/score/to-mxml/process-ps.rb +111 -0
- data/lib/musa-dsl/datasets/score/to-mxml/process-time.rb +200 -1
- data/lib/musa-dsl/datasets/score/to-mxml/to-mxml.rb +145 -1
- data/lib/musa-dsl/datasets/score.rb +279 -0
- data/lib/musa-dsl/datasets/v.rb +88 -0
- data/lib/musa-dsl/generative/darwin.rb +180 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +359 -0
- data/lib/musa-dsl/generative/markov.rb +133 -3
- data/lib/musa-dsl/generative/rules.rb +258 -4
- data/lib/musa-dsl/generative/variatio.rb +217 -2
- data/lib/musa-dsl/logger/logger.rb +267 -2
- data/lib/musa-dsl/matrix/matrix.rb +256 -10
- data/lib/musa-dsl/midi/midi-recorder.rb +108 -1
- data/lib/musa-dsl/midi/midi-voices.rb +265 -4
- data/lib/musa-dsl/music/chord-definition.rb +233 -1
- data/lib/musa-dsl/music/chord-definitions.rb +33 -6
- data/lib/musa-dsl/music/chords.rb +308 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +315 -0
- data/lib/musa-dsl/music/scales.rb +957 -40
- data/lib/musa-dsl/musicxml/builder/attributes.rb +483 -3
- data/lib/musa-dsl/musicxml/builder/backup-forward.rb +166 -1
- data/lib/musa-dsl/musicxml/builder/direction.rb +243 -0
- data/lib/musa-dsl/musicxml/builder/helper.rb +240 -0
- data/lib/musa-dsl/musicxml/builder/measure.rb +284 -0
- data/lib/musa-dsl/musicxml/builder/note-complexities.rb +324 -8
- data/lib/musa-dsl/musicxml/builder/note.rb +285 -0
- data/lib/musa-dsl/musicxml/builder/part-group.rb +108 -1
- data/lib/musa-dsl/musicxml/builder/part.rb +139 -0
- data/lib/musa-dsl/musicxml/builder/pitched-note.rb +124 -0
- data/lib/musa-dsl/musicxml/builder/rest.rb +93 -0
- data/lib/musa-dsl/musicxml/builder/score-partwise.rb +276 -0
- data/lib/musa-dsl/musicxml/builder/typed-text.rb +62 -1
- data/lib/musa-dsl/musicxml/builder/unpitched-note.rb +83 -0
- data/lib/musa-dsl/neumalang/neumalang.rb +675 -0
- data/lib/musa-dsl/neumas/array-to-neumas.rb +149 -0
- data/lib/musa-dsl/neumas/neuma-decoder.rb +253 -0
- data/lib/musa-dsl/neumas/neuma-gdv-decoder.rb +142 -2
- data/lib/musa-dsl/neumas/neuma-gdvd-decoder.rb +82 -0
- data/lib/musa-dsl/neumas/neumas.rb +67 -0
- data/lib/musa-dsl/neumas/string-to-neumas.rb +233 -1
- data/lib/musa-dsl/repl/repl.rb +550 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-every.rb +118 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-move.rb +149 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-helper.rb +296 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play-timed.rb +88 -2
- data/lib/musa-dsl/sequencer/base-sequencer-implementation-play.rb +161 -0
- data/lib/musa-dsl/sequencer/base-sequencer-implementation.rb +263 -0
- data/lib/musa-dsl/sequencer/base-sequencer-tick-based.rb +173 -1
- data/lib/musa-dsl/sequencer/base-sequencer-tickless-based.rb +177 -0
- data/lib/musa-dsl/sequencer/base-sequencer.rb +710 -10
- data/lib/musa-dsl/sequencer/sequencer-dsl.rb +210 -0
- data/lib/musa-dsl/sequencer/timeslots.rb +79 -0
- data/lib/musa-dsl/series/array-to-serie.rb +37 -1
- data/lib/musa-dsl/series/base-series.rb +843 -5
- data/lib/musa-dsl/series/buffer-serie.rb +48 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +41 -0
- data/lib/musa-dsl/series/main-serie-constructors.rb +398 -2
- data/lib/musa-dsl/series/main-serie-operations.rb +538 -16
- data/lib/musa-dsl/series/proxy-serie.rb +67 -0
- data/lib/musa-dsl/series/quantizer-serie.rb +45 -7
- data/lib/musa-dsl/series/queue-serie.rb +65 -0
- data/lib/musa-dsl/series/series-composer.rb +701 -0
- data/lib/musa-dsl/series/timed-serie.rb +473 -28
- data/lib/musa-dsl/transcription/from-gdv-to-midi.rb +404 -1
- data/lib/musa-dsl/transcription/from-gdv-to-musicxml.rb +118 -0
- data/lib/musa-dsl/transcription/from-gdv.rb +84 -1
- data/lib/musa-dsl/transcription/transcription.rb +265 -0
- data/lib/musa-dsl/transport/clock.rb +125 -0
- data/lib/musa-dsl/transport/dummy-clock.rb +89 -2
- data/lib/musa-dsl/transport/external-tick-clock.rb +91 -0
- data/lib/musa-dsl/transport/input-midi-clock.rb +133 -1
- data/lib/musa-dsl/transport/timer-clock.rb +183 -1
- data/lib/musa-dsl/transport/timer.rb +83 -0
- data/lib/musa-dsl/transport/transport.rb +318 -0
- data/lib/musa-dsl/version.rb +1 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +12 -10
- 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
|
|
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
|
-
#
|
|
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
|