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
|
@@ -5,18 +5,229 @@ require_relative '../neumas'
|
|
|
5
5
|
require_relative '../datasets'
|
|
6
6
|
|
|
7
7
|
module Musa
|
|
8
|
+
# Neumalang namespace for parser and related functionality.
|
|
9
|
+
#
|
|
10
|
+
# @api public
|
|
8
11
|
module Neumalang
|
|
12
|
+
# Neumalang parser for parsing neuma text notation to structured objects.
|
|
13
|
+
#
|
|
14
|
+
# Neumalang is a domain-specific language (DSL) for expressing musical notation
|
|
15
|
+
# in a compact, text-based format. The parser uses Citrus (PEG parser framework)
|
|
16
|
+
# to parse neuma notation strings into structured Ruby objects.
|
|
17
|
+
#
|
|
18
|
+
# ## Architecture Overview
|
|
19
|
+
#
|
|
20
|
+
# ### Parser Framework
|
|
21
|
+
#
|
|
22
|
+
# The parser is built on **Citrus**, a Parsing Expression Grammar (PEG) framework:
|
|
23
|
+
#
|
|
24
|
+
# - Grammar defined in `.citrus` files (terminals, datatypes, neuma, vectors, process, neumalang)
|
|
25
|
+
# - Each grammar rule has a corresponding Ruby module for semantic actions
|
|
26
|
+
# - Modules transform parse tree into structured neuma objects
|
|
27
|
+
#
|
|
28
|
+
# ### Grammar Files
|
|
29
|
+
#
|
|
30
|
+
# 1. **terminals.citrus** - Basic tokens (numbers, names, symbols, whitespace)
|
|
31
|
+
# 2. **datatypes.citrus** - Data types (strings, numbers, symbols, vectors)
|
|
32
|
+
# 3. **neuma.citrus** - Neuma notation (grade, duration, velocity, modifiers)
|
|
33
|
+
# 4. **vectors.citrus** - Vector notation (V, PackedV for musical data)
|
|
34
|
+
# 5. **process.citrus** - Process notation (P for rhythmic processes)
|
|
35
|
+
# 6. **neumalang.citrus** - Top-level grammar combining all elements
|
|
36
|
+
#
|
|
37
|
+
# ## Parsing Pipeline
|
|
38
|
+
#
|
|
39
|
+
# ```ruby
|
|
40
|
+
# Text → Citrus Parser → Parse Tree → Semantic Modules → Neuma Objects → Series
|
|
41
|
+
# "0 +2 +2" ↓ ↓ ↓ ↓
|
|
42
|
+
# Grammar AST nodes Module.value() Structured hashes
|
|
43
|
+
# ```
|
|
44
|
+
#
|
|
45
|
+
# ## Neuma Object Structure
|
|
46
|
+
#
|
|
47
|
+
# Parsed neumas are hashes with `:kind` key indicating type:
|
|
48
|
+
#
|
|
49
|
+
# ### GDVD (Musical Event)
|
|
50
|
+
# ```ruby
|
|
51
|
+
# {
|
|
52
|
+
# kind: :gdvd,
|
|
53
|
+
# gdvd: {
|
|
54
|
+
# delta_grade: +2,
|
|
55
|
+
# factor_duration: 2,
|
|
56
|
+
# modifiers: { tr: true }
|
|
57
|
+
# }.extend(Musa::Datasets::GDVd)
|
|
58
|
+
# }.extend(Musa::Neumas::Neuma)
|
|
59
|
+
# ```
|
|
60
|
+
#
|
|
61
|
+
# ### Serie (Sequential)
|
|
62
|
+
# ```ruby
|
|
63
|
+
# {
|
|
64
|
+
# kind: :serie,
|
|
65
|
+
# serie: [neuma1, neuma2, ...]
|
|
66
|
+
# }.extend(Musa::Neumas::Neuma::Serie)
|
|
67
|
+
# ```
|
|
68
|
+
#
|
|
69
|
+
# ### Parallel (Polyphonic)
|
|
70
|
+
# ```ruby
|
|
71
|
+
# {
|
|
72
|
+
# kind: :parallel,
|
|
73
|
+
# parallel: [
|
|
74
|
+
# { kind: :serie, serie: [...] },
|
|
75
|
+
# { kind: :serie, serie: [...] }
|
|
76
|
+
# ]
|
|
77
|
+
# }.extend(Musa::Neumas::Neuma::Parallel)
|
|
78
|
+
# ```
|
|
79
|
+
#
|
|
80
|
+
# ### Commands & Variables
|
|
81
|
+
# ```ruby
|
|
82
|
+
# { kind: :command, command: proc { ... } }
|
|
83
|
+
# { kind: :use_variable, use_variable: :@variable_name }
|
|
84
|
+
# { kind: :assign_to, assign_to: [:@var1], assign_value: ... }
|
|
85
|
+
# ```
|
|
86
|
+
#
|
|
87
|
+
# ### Values
|
|
88
|
+
# ```ruby
|
|
89
|
+
# { kind: :value, value: 42 }
|
|
90
|
+
# { kind: :value, value: :symbol }
|
|
91
|
+
# { kind: :value, value: "string" }
|
|
92
|
+
# ```
|
|
93
|
+
#
|
|
94
|
+
# ### Vectors & Processes
|
|
95
|
+
# ```ruby
|
|
96
|
+
# { kind: :v, v: [1, 2, 3].extend(Musa::Datasets::V) }
|
|
97
|
+
# { kind: :packed_v, packed_v: {a: 1, b: 2}.extend(Musa::Datasets::PackedV) }
|
|
98
|
+
# { kind: :p, p: [values...].extend(Musa::Datasets::P) }
|
|
99
|
+
# ```
|
|
100
|
+
#
|
|
101
|
+
# ## Neumalang Syntax Features
|
|
102
|
+
#
|
|
103
|
+
# - **Grade notation**: `0`, `+2`, `-1`, `^2` (octave up), `v1` (octave down)
|
|
104
|
+
# - **Duration notation**: `_`, `_2`, `_/2`, `_3/2` (dotted), `_.` (dots)
|
|
105
|
+
# - **Velocity notation**: `p`, `pp`, `mp`, `mf`, `f`, `ff`, `fff`
|
|
106
|
+
# - **Modifiers**: `.tr`, `.mor`, `.turn`, `.st`, `.b` (ornaments/articulations)
|
|
107
|
+
# - **Appogiatura**: `(+1_/4)+2_` (grace note before main note)
|
|
108
|
+
# - **Parallel**: `[0 +2 +4 | +7 +5 +7]` (multiple voices)
|
|
109
|
+
# - **Vectors**: `<1 2 3>` (V), `<a: 1 b: 2>` (PackedV)
|
|
110
|
+
# - **Process**: `<< 1 _ _ 2 _ >>` (rhythmic process)
|
|
111
|
+
# - **Commands**: `{ ruby code }` (embedded Ruby)
|
|
112
|
+
# - **Variables**: `@variable`, `@var = value`
|
|
113
|
+
# - **Events**: `event_name(params)`, `event_name(key: value)`
|
|
114
|
+
#
|
|
115
|
+
# ## Usage
|
|
116
|
+
#
|
|
117
|
+
# ```ruby
|
|
118
|
+
# # Parse string
|
|
119
|
+
# neumas = Musa::Neumalang::Neumalang.parse("0 +2 +2 -1 0")
|
|
120
|
+
#
|
|
121
|
+
# # Parse with decoder (converts GDVD to GDV)
|
|
122
|
+
# decoder = NeumaDecoder.new(scale)
|
|
123
|
+
# gdvs = Musa::Neumalang::Neumalang.parse(
|
|
124
|
+
# "0 +2 +2 -1 0",
|
|
125
|
+
# decode_with: decoder
|
|
126
|
+
# )
|
|
127
|
+
#
|
|
128
|
+
# # Parse file
|
|
129
|
+
# neumas = Musa::Neumalang::Neumalang.parse_file("melody.neuma")
|
|
130
|
+
#
|
|
131
|
+
# # Debug parsing
|
|
132
|
+
# Musa::Neumalang::Neumalang.parse(
|
|
133
|
+
# "0 +2 +2 -1 0",
|
|
134
|
+
# debug: true # Dumps parse tree
|
|
135
|
+
# )
|
|
136
|
+
# ```
|
|
137
|
+
#
|
|
138
|
+
# ## Integration
|
|
139
|
+
#
|
|
140
|
+
# - **Series**: Parsed neumas generate Series for sequential playback
|
|
141
|
+
# - **Neumas**: Output extends Neuma modules for structural operations
|
|
142
|
+
# - **Datasets**: GDVd, V, PackedV, P extensions for musical data
|
|
143
|
+
# - **Decoders**: Optional decode_with parameter for immediate GDV conversion
|
|
144
|
+
#
|
|
145
|
+
# @example Basic parsing (simple melody)
|
|
146
|
+
# neumas = Musa::Neumalang::Neumalang.parse("(0) (+2) (+2) (-1) (0)")
|
|
147
|
+
# # Returns serie of GDVD neuma objects
|
|
148
|
+
#
|
|
149
|
+
# # Access the series
|
|
150
|
+
# neumas.i.to_a.size # => 5
|
|
151
|
+
# neumas.i.to_a[0][:gdvd][:abs_grade] # => 0
|
|
152
|
+
# neumas.i.to_a[1][:gdvd][:delta_grade] # => 2
|
|
153
|
+
#
|
|
154
|
+
# @example With decoder
|
|
155
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
156
|
+
# decoder = Musa::Neumas::Decoders::NeumaDecoder.new(
|
|
157
|
+
# scale,
|
|
158
|
+
# base_duration: 1/4r
|
|
159
|
+
# )
|
|
160
|
+
#
|
|
161
|
+
# gdvs = Musa::Neumalang::Neumalang.parse(
|
|
162
|
+
# "(0) (+2) (+2) (-1) (0)",
|
|
163
|
+
# decode_with: decoder
|
|
164
|
+
# )
|
|
165
|
+
# # Returns serie of GDV events
|
|
166
|
+
#
|
|
167
|
+
# @example Complex notation
|
|
168
|
+
# neumas = Musa::Neumalang::Neumalang.parse(
|
|
169
|
+
# "(0) (+2 .tr) (+4 _) (+5 _2) ((^1 _/4) +7 _) (+5) (+4) (+2) (0)"
|
|
170
|
+
# )
|
|
171
|
+
#
|
|
172
|
+
# @example Parallel voices
|
|
173
|
+
# neumas = Musa::Neumalang::Neumalang.parse(
|
|
174
|
+
# "[(0) (+2) (+4) | (+7) (+5) (+7)]"
|
|
175
|
+
# )
|
|
176
|
+
#
|
|
177
|
+
# @example With variables and commands
|
|
178
|
+
# neumas = Musa::Neumalang::Neumalang.parse(
|
|
179
|
+
# "@melody = (0) (+2) (+2) (-1) (0)
|
|
180
|
+
# @melody { |gdv| gdv[:duration] *= 2 }"
|
|
181
|
+
# )
|
|
182
|
+
#
|
|
183
|
+
# @see Musa::Neumas
|
|
184
|
+
# @see Musa::Neumas::Decoders::NeumaDecoder
|
|
185
|
+
# @see Musa::Datasets
|
|
186
|
+
# @see https://github.com/mjackson/citrus Citrus parser framework
|
|
187
|
+
#
|
|
188
|
+
# @api public
|
|
9
189
|
module Neumalang
|
|
190
|
+
# Parser namespace containing grammar and semantic modules.
|
|
191
|
+
#
|
|
192
|
+
# @api public
|
|
10
193
|
module Parser
|
|
194
|
+
# Grammar namespace where Citrus loads grammar rules.
|
|
195
|
+
#
|
|
196
|
+
# Populated by Citrus when loading .citrus files.
|
|
197
|
+
# Contains Grammar class with parse methods.
|
|
198
|
+
#
|
|
199
|
+
# @api private
|
|
11
200
|
module Grammar; end
|
|
12
201
|
|
|
202
|
+
# Semantic action for top-level sentences (sequence of expressions).
|
|
203
|
+
#
|
|
204
|
+
# Transforms parsed expressions into Serie structure.
|
|
205
|
+
# Used for main neuma sequences like `"0 +2 +4"`.
|
|
206
|
+
#
|
|
207
|
+
# @api private
|
|
13
208
|
module Sentences
|
|
209
|
+
# Builds serie from expressions.
|
|
210
|
+
#
|
|
211
|
+
# @return [Hash] serie structure with kind :serie
|
|
212
|
+
#
|
|
213
|
+
# @api private
|
|
14
214
|
def value
|
|
15
215
|
Musa::Series::Constructors.S(*captures(:expression).collect(&:value)).extend(Musa::Neumas::Neuma::Serie)
|
|
16
216
|
end
|
|
17
217
|
end
|
|
18
218
|
|
|
219
|
+
# Semantic action for parallel structure (voices separated by `|`).
|
|
220
|
+
#
|
|
221
|
+
# Transforms bracketed parallel notation into Parallel structure.
|
|
222
|
+
# Used for polyphonic notation like `"[0 +2 | +7 +5]"`.
|
|
223
|
+
#
|
|
224
|
+
# @api private
|
|
19
225
|
module BracketedBarSentences
|
|
226
|
+
# Builds parallel structure from bar-separated series.
|
|
227
|
+
#
|
|
228
|
+
# @return [Hash] parallel structure with kind :parallel
|
|
229
|
+
#
|
|
230
|
+
# @api private
|
|
20
231
|
def value
|
|
21
232
|
{ kind: :parallel,
|
|
22
233
|
parallel: [{ kind: :serie,
|
|
@@ -26,21 +237,54 @@ module Musa
|
|
|
26
237
|
end
|
|
27
238
|
end
|
|
28
239
|
|
|
240
|
+
# Semantic action for bracketed sentences (grouping).
|
|
241
|
+
#
|
|
242
|
+
# Transforms bracketed expressions into serie structure.
|
|
243
|
+
# Used for grouping like `"[0 +2 +4]"` (without bars).
|
|
244
|
+
#
|
|
245
|
+
# @api private
|
|
29
246
|
module BracketedSentences
|
|
247
|
+
# Builds serie from bracketed sentences.
|
|
248
|
+
#
|
|
249
|
+
# @return [Hash] serie structure with kind :serie
|
|
250
|
+
#
|
|
251
|
+
# @api private
|
|
30
252
|
def value
|
|
31
253
|
{ kind: :serie,
|
|
32
254
|
serie: Musa::Series::Constructors.S(*capture(:sentences).value) }.extend Musa::Neumas::Neuma
|
|
33
255
|
end
|
|
34
256
|
end
|
|
35
257
|
|
|
258
|
+
# Semantic action for command references.
|
|
259
|
+
#
|
|
260
|
+
# Transforms referenced braced commands (Ruby code blocks).
|
|
261
|
+
# Used when command is referenced elsewhere.
|
|
262
|
+
#
|
|
263
|
+
# @api private
|
|
36
264
|
module ReferencedBracedCommand
|
|
265
|
+
# Builds command reference structure.
|
|
266
|
+
#
|
|
267
|
+
# @return [Hash] command reference with kind :command_reference
|
|
268
|
+
#
|
|
269
|
+
# @api private
|
|
37
270
|
def value
|
|
38
271
|
{ kind: :command_reference,
|
|
39
272
|
command: capture(:braced_command).value }.extend Musa::Neumas::Neuma
|
|
40
273
|
end
|
|
41
274
|
end
|
|
42
275
|
|
|
276
|
+
# Semantic action for variable assignment.
|
|
277
|
+
#
|
|
278
|
+
# Transforms variable assignment notation like `"@melody = 0 +2 +4"`.
|
|
279
|
+
# Supports multiple variables: `"@var1 @var2 = expression"`.
|
|
280
|
+
#
|
|
281
|
+
# @api private
|
|
43
282
|
module VariableAssign
|
|
283
|
+
# Builds variable assignment structure.
|
|
284
|
+
#
|
|
285
|
+
# @return [Hash] assignment with kind :assign_to, variable names, and value
|
|
286
|
+
#
|
|
287
|
+
# @api private
|
|
44
288
|
def value
|
|
45
289
|
{ kind: :assign_to,
|
|
46
290
|
assign_to: captures(:use_variable).collect { |c| c.value[:use_variable] },
|
|
@@ -49,7 +293,18 @@ module Musa
|
|
|
49
293
|
end
|
|
50
294
|
end
|
|
51
295
|
|
|
296
|
+
# Semantic action for event notation.
|
|
297
|
+
#
|
|
298
|
+
# Transforms event calls like `"event_name(params)"` or `"event_name(key: value)"`.
|
|
299
|
+
# Events can trigger external actions during playback.
|
|
300
|
+
#
|
|
301
|
+
# @api private
|
|
52
302
|
module Event
|
|
303
|
+
# Builds event structure with name and parameters.
|
|
304
|
+
#
|
|
305
|
+
# @return [Hash] event with kind :event, name, and optional parameters
|
|
306
|
+
#
|
|
307
|
+
# @api private
|
|
53
308
|
def value
|
|
54
309
|
{ kind: :event,
|
|
55
310
|
event: capture(:name).value.to_sym
|
|
@@ -57,7 +312,18 @@ module Musa
|
|
|
57
312
|
end
|
|
58
313
|
end
|
|
59
314
|
|
|
315
|
+
# Semantic action for function/event parameters.
|
|
316
|
+
#
|
|
317
|
+
# Parses parameter lists for events and commands.
|
|
318
|
+
# Separates positional parameters, keyword parameters, and proc parameters.
|
|
319
|
+
#
|
|
320
|
+
# @api private
|
|
60
321
|
module Parameters
|
|
322
|
+
# Builds parameters structure.
|
|
323
|
+
#
|
|
324
|
+
# @return [Hash] parameters with :value_parameters, :key_parameters, :proc_parameter
|
|
325
|
+
#
|
|
326
|
+
# @api private
|
|
61
327
|
def value
|
|
62
328
|
value_parameters = []
|
|
63
329
|
key_value_parameters = {}
|
|
@@ -80,7 +346,17 @@ module Musa
|
|
|
80
346
|
end
|
|
81
347
|
end
|
|
82
348
|
|
|
349
|
+
# Semantic action for code blocks (proc parameters).
|
|
350
|
+
#
|
|
351
|
+
# Transforms braced commands or variable references into proc parameters.
|
|
352
|
+
#
|
|
353
|
+
# @api private
|
|
83
354
|
module Codeblock
|
|
355
|
+
# Builds codeblock structure.
|
|
356
|
+
#
|
|
357
|
+
# @return [Hash] codeblock with command or variable reference
|
|
358
|
+
#
|
|
359
|
+
# @api private
|
|
84
360
|
def value
|
|
85
361
|
{ codeblock:
|
|
86
362
|
capture(:braced_command)&.value ||
|
|
@@ -89,7 +365,17 @@ module Musa
|
|
|
89
365
|
end
|
|
90
366
|
end
|
|
91
367
|
|
|
368
|
+
# Semantic action for individual parameter.
|
|
369
|
+
#
|
|
370
|
+
# Transforms either key-value parameter or positional parameter.
|
|
371
|
+
#
|
|
372
|
+
# @api private
|
|
92
373
|
module Parameter
|
|
374
|
+
# Builds parameter structure.
|
|
375
|
+
#
|
|
376
|
+
# @return [Hash] parameter as :key_value or :value
|
|
377
|
+
#
|
|
378
|
+
# @api private
|
|
93
379
|
def value
|
|
94
380
|
if capture(:key_value_parameter)
|
|
95
381
|
{ key_value: capture(:key_value_parameter).value }
|
|
@@ -99,7 +385,18 @@ module Musa
|
|
|
99
385
|
end
|
|
100
386
|
end
|
|
101
387
|
|
|
388
|
+
# Semantic action for braced Ruby commands.
|
|
389
|
+
#
|
|
390
|
+
# Transforms `{ ruby code }` into executable proc.
|
|
391
|
+
# Evaluates Ruby code string to create proc object.
|
|
392
|
+
#
|
|
393
|
+
# @api private
|
|
102
394
|
module BracedCommand
|
|
395
|
+
# Builds command structure with evaluated proc.
|
|
396
|
+
#
|
|
397
|
+
# @return [Hash] command with kind :command and executable proc
|
|
398
|
+
#
|
|
399
|
+
# @api private
|
|
103
400
|
def value
|
|
104
401
|
{ kind: :command,
|
|
105
402
|
command: eval("proc { #{capture(:complex_command).value.strip} }")
|
|
@@ -107,7 +404,18 @@ module Musa
|
|
|
107
404
|
end
|
|
108
405
|
end
|
|
109
406
|
|
|
407
|
+
# Semantic action for method call chains.
|
|
408
|
+
#
|
|
409
|
+
# Transforms method call chains like `"object.method1.method2"`.
|
|
410
|
+
# Enables fluent interface notation in Neumalang.
|
|
411
|
+
#
|
|
412
|
+
# @api private
|
|
110
413
|
module CallMethodsExpression
|
|
414
|
+
# Builds method call chain structure.
|
|
415
|
+
#
|
|
416
|
+
# @return [Hash] call_methods with kind :call_methods, methods array, and target object
|
|
417
|
+
#
|
|
418
|
+
# @api private
|
|
111
419
|
def value
|
|
112
420
|
{ kind: :call_methods,
|
|
113
421
|
call_methods: captures(:method_call).collect(&:value),
|
|
@@ -115,14 +423,58 @@ module Musa
|
|
|
115
423
|
end
|
|
116
424
|
end
|
|
117
425
|
|
|
426
|
+
# Semantic action for variable usage.
|
|
427
|
+
#
|
|
428
|
+
# Transforms variable references like `"@variable"`.
|
|
429
|
+
# Converts name to symbol with @ prefix.
|
|
430
|
+
#
|
|
431
|
+
# @api private
|
|
118
432
|
module UseVariable
|
|
433
|
+
# Builds variable reference structure.
|
|
434
|
+
#
|
|
435
|
+
# @return [Hash] variable reference with kind :use_variable and symbol name
|
|
436
|
+
#
|
|
437
|
+
# @api private
|
|
119
438
|
def value
|
|
120
439
|
{ kind: :use_variable,
|
|
121
440
|
use_variable: "@#{capture(:name).value}".to_sym }.extend Musa::Neumas::Neuma
|
|
122
441
|
end
|
|
123
442
|
end
|
|
124
443
|
|
|
444
|
+
# Semantic action for neuma notation (core musical event).
|
|
445
|
+
#
|
|
446
|
+
# Transforms neuma notation like `"+2_2.tr"` into GDVD structure.
|
|
447
|
+
# Combines grade, octave, duration, velocity, and modifiers.
|
|
448
|
+
#
|
|
449
|
+
# This is the most important module - it builds the musical events
|
|
450
|
+
# that form the core of Neumalang notation.
|
|
451
|
+
#
|
|
452
|
+
# @api private
|
|
125
453
|
module NeumaAsAttributes
|
|
454
|
+
# Builds GDVD structure from neuma attributes.
|
|
455
|
+
#
|
|
456
|
+
# Merges:
|
|
457
|
+
#
|
|
458
|
+
# - Grade attributes (delta_grade, abs_grade, etc.)
|
|
459
|
+
# - Octave attributes (delta_octave, abs_octave)
|
|
460
|
+
# - Duration attributes (delta_duration, abs_duration, factor_duration)
|
|
461
|
+
# - Velocity attributes (delta_velocity, abs_velocity)
|
|
462
|
+
# - Modifiers (ornaments, articulations)
|
|
463
|
+
#
|
|
464
|
+
# @return [Hash] GDVD neuma with kind :gdvd
|
|
465
|
+
#
|
|
466
|
+
# @example Parse result
|
|
467
|
+
# # "+2_2.tr" becomes:
|
|
468
|
+
# {
|
|
469
|
+
# kind: :gdvd,
|
|
470
|
+
# gdvd: {
|
|
471
|
+
# delta_grade: 2,
|
|
472
|
+
# factor_duration: 2,
|
|
473
|
+
# modifiers: { tr: true }
|
|
474
|
+
# }.extend(Musa::Datasets::GDVd)
|
|
475
|
+
# }
|
|
476
|
+
#
|
|
477
|
+
# @api private
|
|
126
478
|
def value
|
|
127
479
|
h = {}.extend Musa::Datasets::GDVd
|
|
128
480
|
|
|
@@ -138,31 +490,86 @@ module Musa
|
|
|
138
490
|
end
|
|
139
491
|
end
|
|
140
492
|
|
|
493
|
+
# Semantic action for packed vector notation.
|
|
494
|
+
#
|
|
495
|
+
# Transforms packed vector notation `"<a: 1 b: 2>"` into PackedV structure.
|
|
496
|
+
# PackedV is hash-based vector with named components.
|
|
497
|
+
#
|
|
498
|
+
# @api private
|
|
141
499
|
module PackedVector
|
|
500
|
+
# Builds packed vector structure.
|
|
501
|
+
#
|
|
502
|
+
# @return [Hash] packed_v with kind :packed_v
|
|
503
|
+
#
|
|
504
|
+
# @api private
|
|
142
505
|
def value
|
|
143
506
|
{ kind: :packed_v, packed_v: capture(:raw_packed_vector).value }.extend(Musa::Neumas::Neuma)
|
|
144
507
|
end
|
|
145
508
|
end
|
|
146
509
|
|
|
510
|
+
# Semantic action for raw packed vector data.
|
|
511
|
+
#
|
|
512
|
+
# Extracts key-value pairs from packed vector notation.
|
|
513
|
+
#
|
|
514
|
+
# @api private
|
|
147
515
|
module RawPackedVector
|
|
516
|
+
# Builds PackedV hash.
|
|
517
|
+
#
|
|
518
|
+
# @return [Hash] PackedV extended hash
|
|
519
|
+
#
|
|
520
|
+
# @api private
|
|
148
521
|
def value
|
|
149
522
|
captures(:key_value).collect(&:value).to_h.extend(Musa::Datasets::PackedV)
|
|
150
523
|
end
|
|
151
524
|
end
|
|
152
525
|
|
|
526
|
+
# Semantic action for vector notation.
|
|
527
|
+
#
|
|
528
|
+
# Transforms vector notation `"<1 2 3>"` into V structure.
|
|
529
|
+
# V is array-based vector for musical data.
|
|
530
|
+
#
|
|
531
|
+
# @api private
|
|
153
532
|
module Vector
|
|
533
|
+
# Builds vector structure.
|
|
534
|
+
#
|
|
535
|
+
# @return [Hash] v with kind :v
|
|
536
|
+
#
|
|
537
|
+
# @api private
|
|
154
538
|
def value
|
|
155
539
|
{ kind: :v, v: capture(:raw_vector).value }.extend(Musa::Neumas::Neuma)
|
|
156
540
|
end
|
|
157
541
|
end
|
|
158
542
|
|
|
543
|
+
# Semantic action for raw vector data.
|
|
544
|
+
#
|
|
545
|
+
# Extracts numeric values from vector notation.
|
|
546
|
+
#
|
|
547
|
+
# @api private
|
|
159
548
|
module RawVector
|
|
549
|
+
# Builds V array.
|
|
550
|
+
#
|
|
551
|
+
# @return [Array] V extended array
|
|
552
|
+
#
|
|
553
|
+
# @api private
|
|
160
554
|
def value
|
|
161
555
|
captures(:raw_number).collect(&:value).extend(Musa::Datasets::V)
|
|
162
556
|
end
|
|
163
557
|
end
|
|
164
558
|
|
|
559
|
+
# Semantic action for process notation (rhythmic process).
|
|
560
|
+
#
|
|
561
|
+
# Transforms process notation `"<< 1 _ _ 2 _ >>"` into P structure.
|
|
562
|
+
# P represents rhythmic/temporal processes with values and rests.
|
|
563
|
+
#
|
|
564
|
+
# @api private
|
|
165
565
|
module ProcessOfVectors
|
|
566
|
+
# Builds process structure.
|
|
567
|
+
#
|
|
568
|
+
# Interleaves durations and rests: [first, duration1, rest1, duration2, rest2, ...]
|
|
569
|
+
#
|
|
570
|
+
# @return [Hash] p with kind :p
|
|
571
|
+
#
|
|
572
|
+
# @api private
|
|
166
573
|
def value
|
|
167
574
|
durations_rest = []
|
|
168
575
|
i = 0
|
|
@@ -179,7 +586,18 @@ module Musa
|
|
|
179
586
|
end
|
|
180
587
|
end
|
|
181
588
|
|
|
589
|
+
# Semantic action for differential grade (pitch) attribute.
|
|
590
|
+
#
|
|
591
|
+
# Parses relative pitch changes: `"+2"`, `"-1"`, `"+2#"` (with sharp), `"+I"` (interval).
|
|
592
|
+
# Used for scale-degree-based melodic motion.
|
|
593
|
+
#
|
|
594
|
+
# @api private
|
|
182
595
|
module DeltaGradeAttribute
|
|
596
|
+
# Builds delta grade structure.
|
|
597
|
+
#
|
|
598
|
+
# @return [Hash] delta_grade, delta_sharps, delta_interval attributes
|
|
599
|
+
#
|
|
600
|
+
# @api private
|
|
183
601
|
def value
|
|
184
602
|
|
|
185
603
|
value = {}
|
|
@@ -196,7 +614,18 @@ module Musa
|
|
|
196
614
|
end
|
|
197
615
|
end
|
|
198
616
|
|
|
617
|
+
# Semantic action for absolute grade (pitch) attribute.
|
|
618
|
+
#
|
|
619
|
+
# Parses absolute pitch: `"0"` (tonic), `"2#"` (2nd degree sharp), `"I"` (interval name).
|
|
620
|
+
# Used for establishing absolute pitch positions.
|
|
621
|
+
#
|
|
622
|
+
# @api private
|
|
199
623
|
module AbsGradeAttribute
|
|
624
|
+
# Builds absolute grade structure.
|
|
625
|
+
#
|
|
626
|
+
# @return [Hash] abs_grade, abs_sharps attributes
|
|
627
|
+
#
|
|
628
|
+
# @api private
|
|
200
629
|
def value
|
|
201
630
|
value = {}
|
|
202
631
|
|
|
@@ -209,19 +638,50 @@ module Musa
|
|
|
209
638
|
end
|
|
210
639
|
end
|
|
211
640
|
|
|
641
|
+
# Semantic action for differential octave attribute.
|
|
642
|
+
#
|
|
643
|
+
# Parses relative octave changes: `"^2"` (up 2 octaves), `"v1"` (down 1 octave).
|
|
644
|
+
#
|
|
645
|
+
# @api private
|
|
212
646
|
module DeltaOctaveAttribute
|
|
647
|
+
# Builds delta octave structure.
|
|
648
|
+
#
|
|
649
|
+
# @return [Hash] delta_octave attribute
|
|
650
|
+
#
|
|
651
|
+
# @api private
|
|
213
652
|
def value
|
|
214
653
|
{ delta_octave: capture(:sign).value * capture(:number).value }
|
|
215
654
|
end
|
|
216
655
|
end
|
|
217
656
|
|
|
657
|
+
# Semantic action for absolute octave attribute.
|
|
658
|
+
#
|
|
659
|
+
# Parses absolute octave: `"^2"` (octave 2), used for setting specific octave.
|
|
660
|
+
#
|
|
661
|
+
# @api private
|
|
218
662
|
module AbsOctaveAttribute
|
|
663
|
+
# Builds absolute octave structure.
|
|
664
|
+
#
|
|
665
|
+
# @return [Hash] abs_octave attribute
|
|
666
|
+
#
|
|
667
|
+
# @api private
|
|
219
668
|
def value
|
|
220
669
|
{ abs_octave: capture(:number).value }
|
|
221
670
|
end
|
|
222
671
|
end
|
|
223
672
|
|
|
673
|
+
# Helper module for duration calculations.
|
|
674
|
+
#
|
|
675
|
+
# Calculates duration from notation including dots and slashes.
|
|
676
|
+
# Supports: `_2` (double), `_/2` (half), `_.` (dotted), `_..` (double dotted).
|
|
677
|
+
#
|
|
678
|
+
# @api private
|
|
224
679
|
module DurationCalculation
|
|
680
|
+
# Calculates duration value.
|
|
681
|
+
#
|
|
682
|
+
# @return [Rational] calculated duration
|
|
683
|
+
#
|
|
684
|
+
# @api private
|
|
225
685
|
def duration
|
|
226
686
|
base = capture(:number)&.value
|
|
227
687
|
|
|
@@ -237,9 +697,19 @@ module Musa
|
|
|
237
697
|
end
|
|
238
698
|
end
|
|
239
699
|
|
|
700
|
+
# Semantic action for differential duration attribute (absolute delta).
|
|
701
|
+
#
|
|
702
|
+
# Parses absolute duration changes: `"+1/4"` (add quarter), `"-1/2"` (subtract half).
|
|
703
|
+
#
|
|
704
|
+
# @api private
|
|
240
705
|
module DeltaDurationAttribute
|
|
241
706
|
include DurationCalculation
|
|
242
707
|
|
|
708
|
+
# Builds delta duration structure.
|
|
709
|
+
#
|
|
710
|
+
# @return [Hash] delta_duration attribute
|
|
711
|
+
#
|
|
712
|
+
# @api private
|
|
243
713
|
def value
|
|
244
714
|
sign = capture(:sign).value
|
|
245
715
|
|
|
@@ -247,21 +717,53 @@ module Musa
|
|
|
247
717
|
end
|
|
248
718
|
end
|
|
249
719
|
|
|
720
|
+
# Semantic action for absolute duration attribute.
|
|
721
|
+
#
|
|
722
|
+
# Parses absolute duration: `"1/4"`, `"1"`, `"1/2.."` (double dotted).
|
|
723
|
+
#
|
|
724
|
+
# @api private
|
|
250
725
|
module AbsDurationAttribute
|
|
251
726
|
include DurationCalculation
|
|
252
727
|
|
|
728
|
+
# Builds absolute duration structure.
|
|
729
|
+
#
|
|
730
|
+
# @return [Hash] abs_duration attribute
|
|
731
|
+
#
|
|
732
|
+
# @api private
|
|
253
733
|
def value
|
|
254
734
|
{ abs_duration: duration }
|
|
255
735
|
end
|
|
256
736
|
end
|
|
257
737
|
|
|
738
|
+
# Semantic action for factor duration attribute (multiplicative).
|
|
739
|
+
#
|
|
740
|
+
# Parses duration factors: `"_2"` (double), `"_/2"` (half), `"_*3"` (triple).
|
|
741
|
+
# Most common duration notation in neumas.
|
|
742
|
+
#
|
|
743
|
+
# @api private
|
|
258
744
|
module FactorDurationAttribute
|
|
745
|
+
# Builds factor duration structure.
|
|
746
|
+
#
|
|
747
|
+
# @return [Hash] factor_duration attribute
|
|
748
|
+
#
|
|
749
|
+
# @api private
|
|
259
750
|
def value
|
|
260
751
|
{ factor_duration: capture(:number).value ** (capture(:factor).value == '/' ? -1 : 1) }
|
|
261
752
|
end
|
|
262
753
|
end
|
|
263
754
|
|
|
755
|
+
# Semantic action for absolute velocity attribute.
|
|
756
|
+
#
|
|
757
|
+
# Parses dynamics: `"pp"`, `"p"`, `"mp"`, `"mf"`, `"f"`, `"ff"`, `"fff"`.
|
|
758
|
+
# Converts to numeric velocity levels.
|
|
759
|
+
#
|
|
760
|
+
# @api private
|
|
264
761
|
module AbsVelocityAttribute
|
|
762
|
+
# Builds absolute velocity structure.
|
|
763
|
+
#
|
|
764
|
+
# @return [Hash] abs_velocity attribute
|
|
765
|
+
#
|
|
766
|
+
# @api private
|
|
265
767
|
def value
|
|
266
768
|
if capture(:p)
|
|
267
769
|
v = -capture(:p).length
|
|
@@ -277,7 +779,17 @@ module Musa
|
|
|
277
779
|
end
|
|
278
780
|
end
|
|
279
781
|
|
|
782
|
+
# Semantic action for differential velocity attribute.
|
|
783
|
+
#
|
|
784
|
+
# Parses relative dynamics: `"+f"` (louder), `"-p"` (softer).
|
|
785
|
+
#
|
|
786
|
+
# @api private
|
|
280
787
|
module DeltaVelocityAttribute
|
|
788
|
+
# Builds delta velocity structure.
|
|
789
|
+
#
|
|
790
|
+
# @return [Hash] delta_velocity attribute
|
|
791
|
+
#
|
|
792
|
+
# @api private
|
|
281
793
|
def value
|
|
282
794
|
d = capture(:delta).value
|
|
283
795
|
s = capture(:sign).value
|
|
@@ -292,7 +804,18 @@ module Musa
|
|
|
292
804
|
end
|
|
293
805
|
end
|
|
294
806
|
|
|
807
|
+
# Semantic action for appogiatura (grace note).
|
|
808
|
+
#
|
|
809
|
+
# Parses grace note notation: `"(+1_/4)+2_"` (grace +1, then main +2).
|
|
810
|
+
# Attaches grace note as modifier to main note.
|
|
811
|
+
#
|
|
812
|
+
# @api private
|
|
295
813
|
module AppogiaturaNeuma
|
|
814
|
+
# Builds neuma with appogiatura modifier.
|
|
815
|
+
#
|
|
816
|
+
# @return [Hash] neuma with appogiatura in modifiers
|
|
817
|
+
#
|
|
818
|
+
# @api private
|
|
296
819
|
def value
|
|
297
820
|
capture(:base).value.tap do |_|
|
|
298
821
|
_[:gdvd][:modifiers] ||= {}
|
|
@@ -301,28 +824,68 @@ module Musa
|
|
|
301
824
|
end
|
|
302
825
|
end
|
|
303
826
|
|
|
827
|
+
# Semantic action for symbol values.
|
|
828
|
+
#
|
|
829
|
+
# Parses symbol notation: `":symbol_name"`.
|
|
830
|
+
#
|
|
831
|
+
# @api private
|
|
304
832
|
module Symbol
|
|
833
|
+
# Builds symbol value structure.
|
|
834
|
+
#
|
|
835
|
+
# @return [Hash] symbol value with kind :value
|
|
836
|
+
#
|
|
837
|
+
# @api private
|
|
305
838
|
def value
|
|
306
839
|
{ kind: :value,
|
|
307
840
|
value: capture(:name).value.to_sym }.extend Musa::Neumas::Neuma
|
|
308
841
|
end
|
|
309
842
|
end
|
|
310
843
|
|
|
844
|
+
# Semantic action for string values.
|
|
845
|
+
#
|
|
846
|
+
# Parses string notation: `"\"text\""`.
|
|
847
|
+
#
|
|
848
|
+
# @api private
|
|
311
849
|
module String
|
|
850
|
+
# Builds string value structure.
|
|
851
|
+
#
|
|
852
|
+
# @return [Hash] string value with kind :value
|
|
853
|
+
#
|
|
854
|
+
# @api private
|
|
312
855
|
def value
|
|
313
856
|
{ kind: :value,
|
|
314
857
|
value: capture(:everything_except_double_quote).value }.extend Musa::Neumas::Neuma
|
|
315
858
|
end
|
|
316
859
|
end
|
|
317
860
|
|
|
861
|
+
# Semantic action for numeric values.
|
|
862
|
+
#
|
|
863
|
+
# Parses numbers: `42`, `3.14`, `1/2`.
|
|
864
|
+
#
|
|
865
|
+
# @api private
|
|
318
866
|
module Number
|
|
867
|
+
# Builds number value structure.
|
|
868
|
+
#
|
|
869
|
+
# @return [Hash] number value with kind :value
|
|
870
|
+
#
|
|
871
|
+
# @api private
|
|
319
872
|
def value
|
|
320
873
|
{ kind: :value,
|
|
321
874
|
value: capture(:raw_number).value }.extend Musa::Neumas::Neuma
|
|
322
875
|
end
|
|
323
876
|
end
|
|
324
877
|
|
|
878
|
+
# Semantic action for special values.
|
|
879
|
+
#
|
|
880
|
+
# Parses special keywords: `nil`, `true`, `false`.
|
|
881
|
+
#
|
|
882
|
+
# @api private
|
|
325
883
|
module Special
|
|
884
|
+
# Builds special value structure.
|
|
885
|
+
#
|
|
886
|
+
# @return [Hash] special value with kind :value
|
|
887
|
+
#
|
|
888
|
+
# @api private
|
|
326
889
|
def value
|
|
327
890
|
v = captures(0)
|
|
328
891
|
{ kind: :value,
|
|
@@ -333,6 +896,79 @@ module Musa
|
|
|
333
896
|
|
|
334
897
|
extend self
|
|
335
898
|
|
|
899
|
+
# Parses Neumalang notation string or file to structured neuma objects.
|
|
900
|
+
#
|
|
901
|
+
# Main parsing method. Uses Citrus parser to transform text notation
|
|
902
|
+
# into structured neuma series. Optionally decodes GDVD to GDV using
|
|
903
|
+
# provided decoder.
|
|
904
|
+
#
|
|
905
|
+
# ## Parsing Process
|
|
906
|
+
#
|
|
907
|
+
# 1. Parse text with Citrus grammar
|
|
908
|
+
# 2. Apply semantic action modules to build neuma structures
|
|
909
|
+
# 3. Optionally decode GDVD events to GDV with decoder
|
|
910
|
+
# 4. Return serie of neuma objects
|
|
911
|
+
#
|
|
912
|
+
# ## Decoder Integration
|
|
913
|
+
#
|
|
914
|
+
# If `decode_with` parameter provided:
|
|
915
|
+
#
|
|
916
|
+
# - GDVD events decoded to GDV (absolute format)
|
|
917
|
+
# - Requires NeumaDecoder or compatible decoder
|
|
918
|
+
# - Useful for immediate conversion to playable events
|
|
919
|
+
#
|
|
920
|
+
# ## Debug Mode
|
|
921
|
+
#
|
|
922
|
+
# If `debug: true`:
|
|
923
|
+
#
|
|
924
|
+
# - Dumps parse tree to stdout
|
|
925
|
+
# - Shows grammar rule matches
|
|
926
|
+
# - Useful for understanding parsing or debugging grammar
|
|
927
|
+
#
|
|
928
|
+
# @param string_or_file [String, File] neuma notation to parse
|
|
929
|
+
# @param decode_with [Decoder, nil] optional decoder for GDVD→GDV conversion
|
|
930
|
+
# @param debug [Boolean, nil] enable parse tree debugging output
|
|
931
|
+
#
|
|
932
|
+
# @return [Serie, Array] parsed neuma serie or array of neumas
|
|
933
|
+
#
|
|
934
|
+
# @raise [ArgumentError] if string_or_file is not String or File
|
|
935
|
+
# @raise [Citrus::ParseError] if notation has syntax errors
|
|
936
|
+
#
|
|
937
|
+
# @example Parse simple notation
|
|
938
|
+
# neumas = Musa::Neumalang::Neumalang.parse("(0) (+2) (+2) (-1) (0)")
|
|
939
|
+
# # => Serie of GDVD neuma objects
|
|
940
|
+
#
|
|
941
|
+
# @example Parse with decoder (immediate GDV conversion)
|
|
942
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
943
|
+
# decoder = Musa::Neumas::Decoders::NeumaDecoder.new(
|
|
944
|
+
# scale,
|
|
945
|
+
# base_duration: 1/4r
|
|
946
|
+
# )
|
|
947
|
+
#
|
|
948
|
+
# gdvs = Musa::Neumalang::Neumalang.parse(
|
|
949
|
+
# "(0) (+2) (+2) (-1) (0)",
|
|
950
|
+
# decode_with: decoder
|
|
951
|
+
# )
|
|
952
|
+
# # => Serie of GDV events ready for playback
|
|
953
|
+
#
|
|
954
|
+
# @example Parse file
|
|
955
|
+
# file = File.open("melody.neuma")
|
|
956
|
+
# neumas = Musa::Neumalang::Neumalang.parse(file)
|
|
957
|
+
#
|
|
958
|
+
# @example Debug parsing
|
|
959
|
+
# neumas = Musa::Neumalang::Neumalang.parse(
|
|
960
|
+
# "(0) (+2) (+2)",
|
|
961
|
+
# debug: true
|
|
962
|
+
# )
|
|
963
|
+
# # Prints parse tree to stdout
|
|
964
|
+
#
|
|
965
|
+
# @example Complex notation
|
|
966
|
+
# neumas = Musa::Neumalang::Neumalang.parse(
|
|
967
|
+
# "[(0) (+2 .tr) (+4 _) | (+7) (+5 .mor) (+7 _)] (+9 _2)"
|
|
968
|
+
# )
|
|
969
|
+
# # Parallel voices followed by longer note
|
|
970
|
+
#
|
|
971
|
+
# @api public
|
|
336
972
|
def parse(string_or_file, decode_with: nil, debug: nil)
|
|
337
973
|
case string_or_file
|
|
338
974
|
when String
|
|
@@ -362,12 +998,51 @@ module Musa
|
|
|
362
998
|
end
|
|
363
999
|
end
|
|
364
1000
|
|
|
1001
|
+
# Parses Neumalang notation file to structured neuma objects.
|
|
1002
|
+
#
|
|
1003
|
+
# Convenience method for parsing files. Opens file and calls `parse`.
|
|
1004
|
+
#
|
|
1005
|
+
# @param filename [String] path to neuma notation file
|
|
1006
|
+
# @param decode_with [Decoder, nil] optional decoder for GDVD→GDV conversion
|
|
1007
|
+
# @param debug [Boolean, nil] enable parse tree debugging output
|
|
1008
|
+
#
|
|
1009
|
+
# @return [Serie, Array] parsed neuma serie or array
|
|
1010
|
+
#
|
|
1011
|
+
# @raise [Errno::ENOENT] if file not found
|
|
1012
|
+
# @raise [Citrus::ParseError] if notation has syntax errors
|
|
1013
|
+
#
|
|
1014
|
+
# @example Parse neuma file
|
|
1015
|
+
# neumas = Musa::Neumalang::Neumalang.parse_file("melodies/theme.neuma")
|
|
1016
|
+
#
|
|
1017
|
+
# @example Parse with decoder
|
|
1018
|
+
# scale = Musa::Scales::Scales.et12[440.0].major[60]
|
|
1019
|
+
# decoder = Musa::Neumas::Decoders::NeumaDecoder.new(scale)
|
|
1020
|
+
# gdvs = Musa::Neumalang::Neumalang.parse_file(
|
|
1021
|
+
# "melodies/theme.neuma",
|
|
1022
|
+
# decode_with: decoder
|
|
1023
|
+
# )
|
|
1024
|
+
#
|
|
1025
|
+
# @api public
|
|
365
1026
|
def parse_file(filename, decode_with: nil, debug: nil)
|
|
366
1027
|
File.open filename do |file|
|
|
367
1028
|
parse file, decode_with: decode_with, debug: debug
|
|
368
1029
|
end
|
|
369
1030
|
end
|
|
370
1031
|
|
|
1032
|
+
# Load Citrus grammar files.
|
|
1033
|
+
#
|
|
1034
|
+
# Grammars are loaded in dependency order:
|
|
1035
|
+
#
|
|
1036
|
+
# 1. terminals.citrus - Basic tokens
|
|
1037
|
+
# 2. datatypes.citrus - Data types
|
|
1038
|
+
# 3. neuma.citrus - Neuma notation
|
|
1039
|
+
# 4. vectors.citrus - Vector notation
|
|
1040
|
+
# 5. process.citrus - Process notation
|
|
1041
|
+
# 6. neumalang.citrus - Top-level grammar
|
|
1042
|
+
#
|
|
1043
|
+
# Grammar classes are loaded into Parser::Grammar namespace.
|
|
1044
|
+
#
|
|
1045
|
+
# @api private
|
|
371
1046
|
Citrus.load File.join(File.dirname(__FILE__), 'terminals.citrus')
|
|
372
1047
|
Citrus.load File.join(File.dirname(__FILE__), 'datatypes.citrus')
|
|
373
1048
|
Citrus.load File.join(File.dirname(__FILE__), 'neuma.citrus')
|