Module: Musa::Neumalang::Neumalang
Overview
Neumalang parser for parsing neuma text notation to structured objects.
Neumalang is a domain-specific language (DSL) for expressing musical notation in a compact, text-based format. The parser uses Citrus (PEG parser framework) to parse neuma notation strings into structured Ruby objects.
Architecture Overview
Parser Framework
The parser is built on Citrus, a Parsing Expression Grammar (PEG) framework:
- Grammar defined in
.citrusfiles (terminals, datatypes, neuma, vectors, process, neumalang) - Each grammar rule has a corresponding Ruby module for semantic actions
- Modules transform parse tree into structured neuma objects
Grammar Files
- terminals.citrus - Basic tokens (numbers, names, symbols, whitespace)
- datatypes.citrus - Data types (strings, numbers, symbols, vectors)
- neuma.citrus - Neuma notation (grade, duration, velocity, modifiers)
- vectors.citrus - Vector notation (V, PackedV for musical data)
- process.citrus - Process notation (P for rhythmic processes)
- neumalang.citrus - Top-level grammar combining all elements
Parsing Pipeline
Text → Citrus Parser → Parse Tree → Semantic Modules → Neuma Objects → Series
"0 +2 +2" ↓ ↓ ↓ ↓
Grammar AST nodes Module.value() Structured hashes
Neuma Object Structure
Parsed neumas are hashes with :kind key indicating type:
GDVD (Musical Event)
{
kind: :gdvd,
gdvd: {
delta_grade: +2,
factor_duration: 2,
modifiers: { tr: true }
}.extend(Musa::Datasets::GDVd)
}.extend(Musa::Neumas::Neuma)
Serie (Sequential)
{
kind: :serie,
serie: [neuma1, neuma2, ...]
}.extend(Musa::Neumas::Neuma::Serie)
Parallel (Polyphonic)
{
kind: :parallel,
parallel: [
{ kind: :serie, serie: [...] },
{ kind: :serie, serie: [...] }
]
}.extend(Musa::Neumas::Neuma::Parallel)
Commands & Variables
{ kind: :command, command: proc { ... } }
{ kind: :use_variable, use_variable: :@variable_name }
{ kind: :assign_to, assign_to: [:@var1], assign_value: ... }
Values
{ kind: :value, value: 42 }
{ kind: :value, value: :symbol }
{ kind: :value, value: "string" }
Vectors & Processes
{ kind: :v, v: [1, 2, 3].extend(Musa::Datasets::V) }
{ kind: :packed_v, packed_v: {a: 1, b: 2}.extend(Musa::Datasets::PackedV) }
{ kind: :p, p: [values...].extend(Musa::Datasets::P) }
Neumalang Syntax Features
- Grade notation:
0,+2,-1,^2(octave up),v1(octave down) - Duration notation:
_,_2,_/2,_3/2(dotted),_.(dots) - Velocity notation:
p,pp,mp,mf,f,ff,fff - Modifiers:
.tr,.mor,.turn,.st,.b(ornaments/articulations) - Appogiatura:
(+1_/4)+2_(grace note before main note) - Parallel:
[0 +2 +4 | +7 +5 +7](multiple voices) - Vectors:
<1 2 3>(V),<a: 1 b: 2>(PackedV) - Process:
<< 1 _ _ 2 _ >>(rhythmic process) - Commands:
{ ruby code }(embedded Ruby) - Variables:
@variable,@var = value - Events:
event_name(params),event_name(key: value)
Usage
# Parse string
neumas = Musa::Neumalang::Neumalang.parse("0 +2 +2 -1 0")
# Parse with decoder (converts GDVD to GDV)
decoder = NeumaDecoder.new(scale)
gdvs = Musa::Neumalang::Neumalang.parse(
"0 +2 +2 -1 0",
decode_with: decoder
)
# Parse file
neumas = Musa::Neumalang::Neumalang.parse_file("melody.neuma")
# Debug parsing
Musa::Neumalang::Neumalang.parse(
"0 +2 +2 -1 0",
debug: true # Dumps parse tree
)
Integration
- Series: Parsed neumas generate Series for sequential playback
- Neumas: Output extends Neuma modules for structural operations
- Datasets: GDVd, V, PackedV, P extensions for musical data
- Decoders: Optional decode_with parameter for immediate GDV conversion
Defined Under Namespace
Modules: Parser
Class Method Summary collapse
-
.parse(string_or_file, decode_with: nil, debug: nil) ⇒ Serie, Array
Parses Neumalang notation string or file to structured neuma objects.
-
.parse_file(filename, decode_with: nil, debug: nil) ⇒ Serie, Array
Parses Neumalang notation file to structured neuma objects.
Instance Method Summary collapse
-
#parse(string_or_file, decode_with: nil, debug: nil) ⇒ Serie, Array
Parses Neumalang notation string or file to structured neuma objects.
-
#parse_file(filename, decode_with: nil, debug: nil) ⇒ Serie, Array
Parses Neumalang notation file to structured neuma objects.
Class Method Details
.parse(string_or_file, decode_with: nil, debug: nil) ⇒ Serie, Array
Parses Neumalang notation string or file to structured neuma objects.
Main parsing method. Uses Citrus parser to transform text notation into structured neuma series. Optionally decodes GDVD to GDV using provided decoder.
Parsing Process
- Parse text with Citrus grammar
- Apply semantic action modules to build neuma structures
- Optionally decode GDVD events to GDV with decoder
- Return serie of neuma objects
Decoder Integration
If decode_with parameter provided:
- GDVD events decoded to GDV (absolute format)
- Requires NeumaDecoder or compatible decoder
- Useful for immediate conversion to playable events
Debug Mode
If debug: true:
- Dumps parse tree to stdout
- Shows grammar rule matches
- Useful for understanding parsing or debugging grammar
972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 |
# File 'lib/musa-dsl/neumalang/neumalang.rb', line 972 def parse(string_or_file, decode_with: nil, debug: nil) case string_or_file when String match = Parser::Grammar::Grammar.parse string_or_file when File match = Parser::Grammar::Grammar.parse string_or_file.read else raise ArgumentError, 'Only String or File allowed to be parsed' end match.dump if debug serie = match.value if decode_with serie.eval do |e| if e[:kind] == :gdvd decode_with.decode(e[:gdvd]) else raise ArgumentError, "Don't know how to convert #{e} to neumas" end end else serie end end |
.parse_file(filename, decode_with: nil, debug: nil) ⇒ Serie, Array
Parses Neumalang notation file to structured neuma objects.
Convenience method for parsing files. Opens file and calls parse.
1026 1027 1028 1029 1030 |
# File 'lib/musa-dsl/neumalang/neumalang.rb', line 1026 def parse_file(filename, decode_with: nil, debug: nil) File.open filename do |file| parse file, decode_with: decode_with, debug: debug end end |
Instance Method Details
#parse(string_or_file, decode_with: nil, debug: nil) ⇒ Serie, Array
Parses Neumalang notation string or file to structured neuma objects.
Main parsing method. Uses Citrus parser to transform text notation into structured neuma series. Optionally decodes GDVD to GDV using provided decoder.
Parsing Process
- Parse text with Citrus grammar
- Apply semantic action modules to build neuma structures
- Optionally decode GDVD events to GDV with decoder
- Return serie of neuma objects
Decoder Integration
If decode_with parameter provided:
- GDVD events decoded to GDV (absolute format)
- Requires NeumaDecoder or compatible decoder
- Useful for immediate conversion to playable events
Debug Mode
If debug: true:
- Dumps parse tree to stdout
- Shows grammar rule matches
- Useful for understanding parsing or debugging grammar
972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 |
# File 'lib/musa-dsl/neumalang/neumalang.rb', line 972 def parse(string_or_file, decode_with: nil, debug: nil) case string_or_file when String match = Parser::Grammar::Grammar.parse string_or_file when File match = Parser::Grammar::Grammar.parse string_or_file.read else raise ArgumentError, 'Only String or File allowed to be parsed' end match.dump if debug serie = match.value if decode_with serie.eval do |e| if e[:kind] == :gdvd decode_with.decode(e[:gdvd]) else raise ArgumentError, "Don't know how to convert #{e} to neumas" end end else serie end end |
#parse_file(filename, decode_with: nil, debug: nil) ⇒ Serie, Array
Parses Neumalang notation file to structured neuma objects.
Convenience method for parsing files. Opens file and calls parse.
1026 1027 1028 1029 1030 |
# File 'lib/musa-dsl/neumalang/neumalang.rb', line 1026 def parse_file(filename, decode_with: nil, debug: nil) File.open filename do |file| parse file, decode_with: decode_with, debug: debug end end |