musa-dsl 0.30.2 → 0.41.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 +5 -1
- data/.version +6 -0
- data/.yardopts +7 -0
- data/Gemfile +0 -1
- 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 +544 -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 +215 -1
- data/lib/musa-dsl/generative/generative-grammar.rb +387 -0
- data/lib/musa-dsl/generative/markov.rb +135 -3
- data/lib/musa-dsl/generative/rules.rb +312 -4
- data/lib/musa-dsl/generative/variatio.rb +286 -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 +113 -2
- data/lib/musa-dsl/midi/midi-voices.rb +275 -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 +353 -2
- data/lib/musa-dsl/music/equally-tempered-12-tone-scale-system.rb +70 -206
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_dominant_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/bebop/bebop_minor_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_major_scale_kind.rb +100 -0
- data/lib/musa-dsl/music/scale_kinds/blues/blues_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/chromatic_scale_kind.rb +79 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/double_harmonic_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/hungarian_minor_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_major_scale_kind.rb +102 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/neapolitan_minor_scale_kind.rb +101 -0
- data/lib/musa-dsl/music/scale_kinds/ethnic/phrygian_dominant_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/harmonic_major/harmonic_major_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/major_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/altered_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/dorian_b2_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/locrian_sharp2_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_augmented_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/lydian_dominant_scale_kind.rb +106 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/melodic_minor_scale_kind.rb +104 -0
- data/lib/musa-dsl/music/scale_kinds/melodic_minor/mixolydian_b6_scale_kind.rb +103 -0
- data/lib/musa-dsl/music/scale_kinds/minor_harmonic_scale_kind.rb +125 -0
- data/lib/musa-dsl/music/scale_kinds/minor_natural_scale_kind.rb +123 -0
- data/lib/musa-dsl/music/scale_kinds/modes/dorian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/locrian_scale_kind.rb +114 -0
- data/lib/musa-dsl/music/scale_kinds/modes/lydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/mixolydian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/modes/phrygian_scale_kind.rb +111 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_major_scale_kind.rb +93 -0
- data/lib/musa-dsl/music/scale_kinds/pentatonic/pentatonic_minor_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_hw_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/diminished_wh_scale_kind.rb +110 -0
- data/lib/musa-dsl/music/scale_kinds/symmetric/whole_tone_scale_kind.rb +99 -0
- data/lib/musa-dsl/music/scale_systems/equally_tempered_12_tone_scale_system.rb +80 -0
- data/lib/musa-dsl/music/scale_systems/twelve_semitones_scale_system.rb +60 -0
- data/lib/musa-dsl/music/scales.rb +1384 -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 +54 -0
- data/lib/musa-dsl/series/hash-or-array-serie-splitter.rb +64 -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 +57 -7
- data/lib/musa-dsl/series/queue-serie.rb +78 -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 +2 -1
- data/lib/musa-dsl.rb +132 -25
- data/musa-dsl.gemspec +25 -18
- metadata +158 -16
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# MusicXML Builder - Music Notation Export
|
|
2
|
+
|
|
3
|
+
Comprehensive builder for generating MusicXML 3.0 files compatible with music notation software (Finale, Sibelius, MuseScore, Dorico, etc.). MusicXML is the standard open format for exchanging digital sheet music between applications.
|
|
4
|
+
|
|
5
|
+
## Root Class: ScorePartwise
|
|
6
|
+
|
|
7
|
+
The entry point for creating MusicXML documents is `Musa::MusicXML::Builder::ScorePartwise`, which represents the `<score-partwise>` root element. It organizes music by parts (instruments/voices) and measures.
|
|
8
|
+
|
|
9
|
+
**Structure:**
|
|
10
|
+
- **Metadata**: work info, movement info, creators, rights, encoding date
|
|
11
|
+
- **Part List**: part definitions with names and abbreviations
|
|
12
|
+
- **Parts**: musical content organized by measures
|
|
13
|
+
|
|
14
|
+
## Key Features
|
|
15
|
+
|
|
16
|
+
**Multiple staves:**
|
|
17
|
+
Use `staff:` parameter to specify which staff (1, 2, etc.) for grand staff notation (piano, harp, organ, etc.).
|
|
18
|
+
```ruby
|
|
19
|
+
pitch 'C', octave: 3, staff: 2 # Note in staff 2 (bass clef)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Multiple voices:**
|
|
23
|
+
Use `voice:` parameter for polyphonic notation within a single staff (independent melodic lines).
|
|
24
|
+
```ruby
|
|
25
|
+
pitch 'C', octave: 4, voice: 1 # Voice 1
|
|
26
|
+
pitch 'E', octave: 3, voice: 2 # Voice 2 (simultaneous)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Backup/Forward:**
|
|
30
|
+
Navigate timeline within measures to layer voices. `backup(duration)` returns to an earlier point, `forward(duration)` skips ahead.
|
|
31
|
+
```ruby
|
|
32
|
+
pitch 'C', octave: 4, duration: 4
|
|
33
|
+
backup 4 # Return to beginning
|
|
34
|
+
pitch 'E', octave: 3, duration: 4 # Play simultaneously
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**Divisions:**
|
|
38
|
+
Set rhythmic precision as divisions per quarter note in measure attributes. Higher values allow smaller note values.
|
|
39
|
+
```ruby
|
|
40
|
+
attributes do
|
|
41
|
+
divisions 4 # 4 divisions per quarter (allows 16th notes)
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Alterations:**
|
|
46
|
+
Use `alter:` parameter for accidentals: `-1` for flat, `1` for sharp, `2` for double sharp, etc.
|
|
47
|
+
```ruby
|
|
48
|
+
pitch 'F', octave: 4, alter: 1 # F# (sharp)
|
|
49
|
+
pitch 'B', octave: 4, alter: -1 # Bb (flat)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Articulations:**
|
|
53
|
+
Add slurs, dots, and other articulations via parameters.
|
|
54
|
+
```ruby
|
|
55
|
+
pitch 'C', octave: 4, slur: 'start' # Begin slur
|
|
56
|
+
pitch 'D', octave: 4, slur: 'stop' # End slur
|
|
57
|
+
pitch 'E', octave: 4, dots: 1 # Dotted note
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Dynamics:**
|
|
61
|
+
Add dynamic markings using `direction` blocks with `dynamics` method. Supported: `pp`, `p`, `mp`, `mf`, `f`, `ff`, `fff`, etc.
|
|
62
|
+
```ruby
|
|
63
|
+
direction do
|
|
64
|
+
dynamics 'f' # Forte
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**Wedges:**
|
|
69
|
+
Add crescendo/diminuendo markings with `wedge` in direction blocks.
|
|
70
|
+
```ruby
|
|
71
|
+
direction do
|
|
72
|
+
wedge 'crescendo' # Start crescendo
|
|
73
|
+
end
|
|
74
|
+
# ... notes ...
|
|
75
|
+
direction wedge: 'stop' # End crescendo
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Metronome:**
|
|
79
|
+
Add tempo markings with `metronome` in measures.
|
|
80
|
+
```ruby
|
|
81
|
+
metronome beat_unit: 'quarter', per_minute: 120
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Rests:**
|
|
85
|
+
Use `rest` method instead of `pitch` for rest notation.
|
|
86
|
+
```ruby
|
|
87
|
+
rest duration: 2, type: 'quarter'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Two Usage Modes
|
|
91
|
+
|
|
92
|
+
**Constructor Style (Method Calls):**
|
|
93
|
+
|
|
94
|
+
Use constructor parameters and `add_*` methods for programmatic building:
|
|
95
|
+
|
|
96
|
+
```ruby
|
|
97
|
+
require 'musa-dsl'
|
|
98
|
+
|
|
99
|
+
# Create score with metadata
|
|
100
|
+
score = Musa::MusicXML::Builder::ScorePartwise.new(
|
|
101
|
+
work_title: "Piano Piece",
|
|
102
|
+
creators: { composer: "Your Name" },
|
|
103
|
+
encoding_date: DateTime.new(2024, 1, 1)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Add parts using add_* methods
|
|
107
|
+
part = score.add_part(:p1, name: "Piano", abbreviation: "Pno.")
|
|
108
|
+
|
|
109
|
+
# Add measures and attributes
|
|
110
|
+
measure = part.add_measure(divisions: 4)
|
|
111
|
+
|
|
112
|
+
# Add attributes (key, time, clef, etc.)
|
|
113
|
+
measure.attributes.last.add_key(1, fifths: 0) # C major
|
|
114
|
+
measure.attributes.last.add_time(1, beats: 4, beat_type: 4)
|
|
115
|
+
measure.attributes.last.add_clef(1, sign: 'G', line: 2)
|
|
116
|
+
|
|
117
|
+
# Add notes
|
|
118
|
+
measure.add_pitch(step: 'C', octave: 4, duration: 4, type: 'quarter')
|
|
119
|
+
measure.add_pitch(step: 'E', octave: 4, duration: 4, type: 'quarter')
|
|
120
|
+
measure.add_pitch(step: 'G', octave: 4, duration: 4, type: 'quarter')
|
|
121
|
+
measure.add_pitch(step: 'C', octave: 5, duration: 4, type: 'quarter')
|
|
122
|
+
|
|
123
|
+
# Export to file
|
|
124
|
+
File.write("score.musicxml", score.to_xml.string)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**DSL Style (Blocks):**
|
|
128
|
+
|
|
129
|
+
Use blocks with method names as setters/builders for more readable, declarative code:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
require 'musa-dsl'
|
|
133
|
+
|
|
134
|
+
score = Musa::MusicXML::Builder::ScorePartwise.new do
|
|
135
|
+
work_title "Piano Piece"
|
|
136
|
+
creators composer: "Your Name"
|
|
137
|
+
encoding_date DateTime.new(2024, 1, 1)
|
|
138
|
+
|
|
139
|
+
part :p1, name: "Piano", abbreviation: "Pno." do
|
|
140
|
+
measure do
|
|
141
|
+
attributes do
|
|
142
|
+
divisions 4
|
|
143
|
+
key 1, fifths: 0 # C major
|
|
144
|
+
time 1, beats: 4, beat_type: 4
|
|
145
|
+
clef 1, sign: 'G', line: 2
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
pitch 'C', octave: 4, duration: 4, type: 'quarter'
|
|
149
|
+
pitch 'E', octave: 4, duration: 4, type: 'quarter'
|
|
150
|
+
pitch 'G', octave: 4, duration: 4, type: 'quarter'
|
|
151
|
+
pitch 'C', octave: 5, duration: 4, type: 'quarter'
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
File.write("score.musicxml", score.to_xml.string)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**Sophisticated Example - Piano Score with Multiple Features:**
|
|
160
|
+
|
|
161
|
+
```ruby
|
|
162
|
+
require 'musa-dsl'
|
|
163
|
+
|
|
164
|
+
score = Musa::MusicXML::Builder::ScorePartwise.new do
|
|
165
|
+
work_title "Étude in D Major"
|
|
166
|
+
work_number 1
|
|
167
|
+
creators composer: "Example Composer"
|
|
168
|
+
encoding_date DateTime.now
|
|
169
|
+
|
|
170
|
+
part :p1, name: "Piano" do
|
|
171
|
+
# Measure 1 - Setup and opening with two staves
|
|
172
|
+
measure do
|
|
173
|
+
attributes do
|
|
174
|
+
divisions 2 # 2 divisions per quarter note
|
|
175
|
+
|
|
176
|
+
# Treble clef (staff 1)
|
|
177
|
+
key 1, fifths: 2 # D major (2 sharps)
|
|
178
|
+
clef 1, sign: 'G', line: 2
|
|
179
|
+
time 1, beats: 4, beat_type: 4
|
|
180
|
+
|
|
181
|
+
# Bass clef (staff 2)
|
|
182
|
+
key 2, fifths: 2
|
|
183
|
+
clef 2, sign: 'F', line: 4
|
|
184
|
+
time 2, beats: 4, beat_type: 4
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# Tempo marking
|
|
188
|
+
metronome beat_unit: 'quarter', per_minute: 120
|
|
189
|
+
|
|
190
|
+
# Right hand melody (staff 1)
|
|
191
|
+
pitch 'D', octave: 4, duration: 4, type: 'half', slur: 'start'
|
|
192
|
+
pitch 'E', octave: 4, duration: 4, type: 'half', slur: 'stop'
|
|
193
|
+
|
|
194
|
+
# Return to beginning for left hand (staff 2)
|
|
195
|
+
backup 8
|
|
196
|
+
|
|
197
|
+
# Left hand accompaniment (staff 2)
|
|
198
|
+
pitch 'D', octave: 3, duration: 8, type: 'whole', staff: 2
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Measure 2 - Two voices in treble clef
|
|
202
|
+
measure do
|
|
203
|
+
# Voice 1
|
|
204
|
+
pitch 'F#', octave: 4, duration: 2, type: 'quarter', alter: 1, voice: 1
|
|
205
|
+
pitch 'G', octave: 4, duration: 2, type: 'quarter', voice: 1
|
|
206
|
+
pitch 'A', octave: 4, duration: 2, type: 'quarter', voice: 1
|
|
207
|
+
pitch 'B', octave: 4, duration: 2, type: 'quarter', voice: 1
|
|
208
|
+
|
|
209
|
+
# Return to beginning for voice 2
|
|
210
|
+
backup 8
|
|
211
|
+
|
|
212
|
+
# Voice 2 (inner voice)
|
|
213
|
+
pitch 'A', octave: 3, duration: 3, type: 'quarter', dots: 1, voice: 2
|
|
214
|
+
pitch 'B', octave: 3, duration: 1, type: 'eighth', voice: 2
|
|
215
|
+
pitch 'C#', octave: 4, duration: 3, type: 'quarter', dots: 1, alter: 1, voice: 2
|
|
216
|
+
pitch 'D', octave: 4, duration: 1, type: 'eighth', voice: 2
|
|
217
|
+
|
|
218
|
+
# Return for left hand
|
|
219
|
+
backup 8
|
|
220
|
+
|
|
221
|
+
# Left hand (staff 2)
|
|
222
|
+
pitch 'A', octave: 2, duration: 8, type: 'whole', staff: 2
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Measure 3 - Dynamics and articulations
|
|
226
|
+
measure do
|
|
227
|
+
# Dynamic marking
|
|
228
|
+
direction do
|
|
229
|
+
dynamics 'pp'
|
|
230
|
+
wedge 'crescendo'
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
# Notes with crescendo
|
|
234
|
+
pitch 'C#', octave: 5, duration: 1, type: 'eighth', alter: 1
|
|
235
|
+
pitch 'D', octave: 5, duration: 1, type: 'eighth'
|
|
236
|
+
pitch 'E', octave: 5, duration: 1, type: 'eighth'
|
|
237
|
+
pitch 'F#', octave: 5, duration: 1, type: 'eighth', alter: 1
|
|
238
|
+
|
|
239
|
+
pitch 'G', octave: 5, duration: 1, type: 'eighth'
|
|
240
|
+
pitch 'A', octave: 5, duration: 1, type: 'eighth'
|
|
241
|
+
pitch 'B', octave: 5, duration: 1, type: 'eighth'
|
|
242
|
+
pitch 'C#', octave: 6, duration: 1, type: 'eighth', alter: 1
|
|
243
|
+
|
|
244
|
+
# End of crescendo, forte
|
|
245
|
+
direction wedge: 'stop', dynamics: 'f'
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# Export to file
|
|
251
|
+
File.write("etude.musicxml", score.to_xml.string)
|
|
252
|
+
|
|
253
|
+
# Or write directly to IO
|
|
254
|
+
File.open("etude.musicxml", 'w') { |f| score.to_xml(f) }
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## API Reference
|
|
258
|
+
|
|
259
|
+
**Complete API documentation:**
|
|
260
|
+
- [Musa::MusicXML::Builder](https://rubydoc.info/gems/musa-dsl/Musa/MusicXML/Builder) - MusicXML score generation
|
|
261
|
+
|
|
262
|
+
**Source code:** `lib/musicxml/builder/`
|
|
263
|
+
|
|
264
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Neumas & Neumalang - Musical Notation
|
|
2
|
+
|
|
3
|
+
Neumas provide a compact text-based notation system for musical composition. Neumalang is the parser that converts this notation to structured musical data.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
require 'musa-dsl'
|
|
7
|
+
require 'midi-communications'
|
|
8
|
+
|
|
9
|
+
# To play the song, decode neumas to GDV and convert to PDV
|
|
10
|
+
include Musa::All
|
|
11
|
+
|
|
12
|
+
using Musa::Extension::Neumas
|
|
13
|
+
|
|
14
|
+
# Neuma notation requires parentheses around each neuma element
|
|
15
|
+
# Parsed using Musa::Neumalang::Neumalang.parse()
|
|
16
|
+
|
|
17
|
+
# Complete example with durations and dynamics (parallel voices using |)
|
|
18
|
+
song = "(0 1 mf) (+2 1 mp) (+4 2 p) (+5 1/2 mf) (+7 1 f)" | # Voice 1: melody with varied dynamics
|
|
19
|
+
"(+7 2 p) (+5 1 mp) (+7 1 mf) (+9 1/2 f) (+12 2 ff)" # Voice 2: harmony with crescendo
|
|
20
|
+
|
|
21
|
+
# Wrap parallel structure in serie
|
|
22
|
+
song_serie = S(song)
|
|
23
|
+
|
|
24
|
+
# Create decoder with a scale
|
|
25
|
+
scale = Scales.et12[440.0].major[60]
|
|
26
|
+
decoder = Decoders::NeumaDecoder.new(scale, base_duration: 1r)
|
|
27
|
+
|
|
28
|
+
# Setup sequencer with clock and transport
|
|
29
|
+
output = MIDICommunications::Output.gets
|
|
30
|
+
|
|
31
|
+
clock = TimerClock.new(bpm: 120, ticks_per_beat: 24)
|
|
32
|
+
transport = Transport.new(clock, 4, 24)
|
|
33
|
+
|
|
34
|
+
voices = MIDIVoices.new(sequencer: transport.sequencer, output: output, channels: [0, 1])
|
|
35
|
+
|
|
36
|
+
# Play both voices simultaneously - sequencer handles parallel structure automatically
|
|
37
|
+
transport.sequencer.with do
|
|
38
|
+
at 1 do
|
|
39
|
+
play song_serie, decoder: decoder, mode: :neumalang do |gdv|
|
|
40
|
+
# Convert GDV to PDV for MIDI output
|
|
41
|
+
pdv = gdv.to_pdv(scale)
|
|
42
|
+
|
|
43
|
+
# Use voice based on channel assignment (sequencer maintains voice separation)
|
|
44
|
+
voice_index = gdv[:channel] || 0
|
|
45
|
+
voices.voices[voice_index].note pitch: pdv[:pitch],
|
|
46
|
+
velocity: pdv[:velocity],
|
|
47
|
+
duration: pdv[:duration]
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
transport.start
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Notation syntax:**
|
|
56
|
+
- `(0)`, `(+2)`, `(-1)` - Absolute/relative pitch steps (in parentheses)
|
|
57
|
+
- `o0`, `o1`, `o-1` - Octave specification
|
|
58
|
+
- `1`, `2`, `1/2`, `1/4` - Duration (whole, double, half, quarter)
|
|
59
|
+
- `ppp`, `pp`, `p`, `mp`, `mf`, `f`, `ff`, `fff` - Dynamics (velocity)
|
|
60
|
+
- `+f`, `+ff`, `-p`, `-pp` - Relative dynamics (louder/softer)
|
|
61
|
+
- `|` operator - Parallel voices (polyphonic structure)
|
|
62
|
+
|
|
63
|
+
## API Reference
|
|
64
|
+
|
|
65
|
+
**Complete API documentation:**
|
|
66
|
+
- [Musa::Neumas](https://rubydoc.info/gems/musa-dsl/Musa/Neumas) - Musical notation data structures
|
|
67
|
+
- [Musa::Neumalang](https://rubydoc.info/gems/musa-dsl/Musa/Neumalang) - Notation parser and interpreter
|
|
68
|
+
|
|
69
|
+
**Source code:** `lib/neumas/` and `lib/neumalang/`
|
|
70
|
+
|
|
71
|
+
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# REPL - Live Coding Infrastructure
|
|
2
|
+
|
|
3
|
+
The REPL (Read-Eval-Print Loop) provides a TCP-based server for live coding, enabling real-time code evaluation and interactive composition. It acts as a bridge between code editors (via MusaLCE clients) and the running Musa DSL environment.
|
|
4
|
+
|
|
5
|
+
**Architecture:**
|
|
6
|
+
```
|
|
7
|
+
Editor → MusaLCE Client → TCP (port 1327) → REPL Server → DSL Context
|
|
8
|
+
↓
|
|
9
|
+
Results/Errors
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Available MusaLCE Clients:**
|
|
13
|
+
- **MusaLCEClientForVSCode**: Visual Studio Code extension
|
|
14
|
+
- **MusaLCEClientForAtom**: Atom editor plugin
|
|
15
|
+
- **MusaLCEforBitwig**: Bitwig Studio integration
|
|
16
|
+
- **MusaLCEforLive**: Ableton Live integration
|
|
17
|
+
|
|
18
|
+
## Communication Protocol
|
|
19
|
+
|
|
20
|
+
The REPL uses a line-based protocol over TCP (default port: 1327).
|
|
21
|
+
|
|
22
|
+
**Client to Server:**
|
|
23
|
+
- `#path` - Start path block (optional, to inject file path context)
|
|
24
|
+
- *file path* - Path to the user's file being edited
|
|
25
|
+
- `#begin` - Start code block
|
|
26
|
+
- *code lines* - Ruby code to execute
|
|
27
|
+
- `#end` - Execute accumulated code block
|
|
28
|
+
|
|
29
|
+
**Server to Client:**
|
|
30
|
+
- `//echo` - Start echo block (code about to be executed)
|
|
31
|
+
- `//error` - Start error block
|
|
32
|
+
- `//backtrace` - Start backtrace section within error block
|
|
33
|
+
- `//end` - End current block
|
|
34
|
+
- *regular lines* - Output from code execution (puts, etc.)
|
|
35
|
+
|
|
36
|
+
**Example Session:**
|
|
37
|
+
```
|
|
38
|
+
Client → Server:
|
|
39
|
+
#path
|
|
40
|
+
/Users/me/composition.rb
|
|
41
|
+
#begin
|
|
42
|
+
puts "Starting composition..."
|
|
43
|
+
at 1 do
|
|
44
|
+
note pitch: 60, duration: 1r
|
|
45
|
+
end
|
|
46
|
+
#end
|
|
47
|
+
|
|
48
|
+
Server → Client:
|
|
49
|
+
//echo
|
|
50
|
+
puts "Starting composition..."
|
|
51
|
+
at 1 do
|
|
52
|
+
note pitch: 60, duration: 1r
|
|
53
|
+
end
|
|
54
|
+
//end
|
|
55
|
+
Starting composition...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Server Setup
|
|
59
|
+
|
|
60
|
+
**Basic REPL Server:**
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
require 'musa-dsl'
|
|
64
|
+
include Musa::All
|
|
65
|
+
|
|
66
|
+
# Create sequencer and transport
|
|
67
|
+
clock = TimerClock.new(bpm: 120, ticks_per_beat: 24)
|
|
68
|
+
transport = Transport.new(clock, 4, 24)
|
|
69
|
+
|
|
70
|
+
# Start REPL server bound to sequencer context
|
|
71
|
+
# The REPL will execute code in the sequencer's DSL context
|
|
72
|
+
transport.sequencer.with do
|
|
73
|
+
# DSL methods available in REPL
|
|
74
|
+
def note(pitch:, duration:)
|
|
75
|
+
puts "Playing pitch #{pitch} for #{duration} bars"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Create REPL server (port 1327 by default)
|
|
79
|
+
@repl = Musa::REPL::REPL.new(binding)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Start playback (REPL runs in background thread)
|
|
83
|
+
transport.start
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**File Path Injection:**
|
|
87
|
+
|
|
88
|
+
When a client sends a file path via `#path`, the REPL injects it as `@user_pathname` (Pathname object). This enables relative requires based on the editor's current file location:
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
# In REPL context, clients can use:
|
|
92
|
+
require_relative @user_pathname.dirname / 'my_helpers'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Integration with Sequencer
|
|
96
|
+
|
|
97
|
+
The REPL automatically hooks into sequencer error handling to report async errors during playback:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
require 'musa-dsl'
|
|
101
|
+
include Musa::All
|
|
102
|
+
|
|
103
|
+
clock = TimerClock.new(bpm: 120, ticks_per_beat: 24)
|
|
104
|
+
transport = Transport.new(clock, 4, 24)
|
|
105
|
+
|
|
106
|
+
transport.sequencer.with do
|
|
107
|
+
# If an error occurs during sequencer execution,
|
|
108
|
+
# REPL clients receive formatted error messages
|
|
109
|
+
|
|
110
|
+
at 1 do
|
|
111
|
+
raise "This error will be sent to REPL client"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
@repl = Musa::REPL::REPL.new(binding)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
transport.start
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Use Cases
|
|
121
|
+
|
|
122
|
+
- **Live coding performances**: Real-time code evaluation during performances
|
|
123
|
+
- **Interactive composition**: Develop compositions interactively with immediate feedback
|
|
124
|
+
- **DAW synchronization**: Control Musa DSL from within Bitwig or Ableton Live
|
|
125
|
+
- **Remote composition control**: Send commands to running compositions over network
|
|
126
|
+
- **Educational workshops**: Live demonstrations with instant code execution
|
|
127
|
+
|
|
128
|
+
## API Reference
|
|
129
|
+
|
|
130
|
+
**Complete API documentation:**
|
|
131
|
+
- [Musa::REPL](https://rubydoc.info/gems/musa-dsl/Musa/REPL) - Live coding server and protocol
|
|
132
|
+
|
|
133
|
+
**Source code:** `lib/repl/`
|
|
134
|
+
|
|
135
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Sequencer - Temporal Engine
|
|
2
|
+
|
|
3
|
+
The Sequencer manages time-based event scheduling with microsecond precision, supporting complex polyrhythmic and polytemporal structures.
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
require 'musa-dsl'
|
|
7
|
+
include Musa::All
|
|
8
|
+
|
|
9
|
+
# Setup: Create clock and transport (for real-time execution)
|
|
10
|
+
clock = TimerClock.new(bpm: 120, ticks_per_beat: 24)
|
|
11
|
+
transport = Transport.new(clock, 4, 24) # 4 beats per bar, 24 ticks per beat
|
|
12
|
+
|
|
13
|
+
# Define series outside DSL block (Series constructors not available in DSL context)
|
|
14
|
+
melody = S({ note: 60, duration: 1/2r }, { note: 62, duration: 1/2r },
|
|
15
|
+
{ note: 64, duration: 1/2r }, { note: 65, duration: 1/2r },
|
|
16
|
+
{ note: 67, duration: 1/2r }, { note: 65, duration: 1/2r },
|
|
17
|
+
{ note: 64, duration: 1/2r }, { note: 62, duration: 1/2r })
|
|
18
|
+
|
|
19
|
+
# Program sequencer using DSL
|
|
20
|
+
transport.sequencer.with do
|
|
21
|
+
# Custom event handlers (on/launch)
|
|
22
|
+
on :section_change do |name|
|
|
23
|
+
puts "Section: #{name}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Immediate event (now)
|
|
27
|
+
now do
|
|
28
|
+
launch :section_change, "Start"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Absolute positioning (at): event at bar 1
|
|
32
|
+
at 1 do
|
|
33
|
+
puts "Bar 1: position #{position}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Relative positioning (wait): event 2 bars later
|
|
37
|
+
wait 2 do
|
|
38
|
+
puts "Bar 3: position #{position}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Play series (play): reproduces series with automatic timing
|
|
42
|
+
at 5 do
|
|
43
|
+
play melody do |note:, duration:, control:|
|
|
44
|
+
puts "Playing note: #{note}, duration: #{duration}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Recurring event (every) with stop control
|
|
49
|
+
beat_loop = nil
|
|
50
|
+
at 10 do
|
|
51
|
+
# Store control object to stop it later
|
|
52
|
+
beat_loop = every 2, duration: 10 do
|
|
53
|
+
puts "Beat at position #{position}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Stop the beat loop at bar 18
|
|
58
|
+
at 18 do
|
|
59
|
+
beat_loop.stop if beat_loop
|
|
60
|
+
puts "Beat loop stopped"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Animated value (move) from 0 to 10 over 4 bars
|
|
64
|
+
at 20 do
|
|
65
|
+
move from: 0, to: 10, duration: 4, every: 1/2r do |value|
|
|
66
|
+
puts "Value: #{value.round(2)}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Multi-parameter animation (move with hash)
|
|
71
|
+
at 25 do
|
|
72
|
+
move from: { pitch: 60, vel: 60 },
|
|
73
|
+
to: { pitch: 72, vel: 100 },
|
|
74
|
+
duration: 2,
|
|
75
|
+
every: 1/4r do |values|
|
|
76
|
+
puts "Pitch: #{values[:pitch].round}, Velocity: #{values[:vel].round}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Final event
|
|
81
|
+
at 30 do
|
|
82
|
+
launch :section_change, "End"
|
|
83
|
+
puts "Finished at position #{position}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Start real-time playback
|
|
88
|
+
transport.start
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## API Reference
|
|
92
|
+
|
|
93
|
+
**Complete API documentation:**
|
|
94
|
+
- [Musa::Sequencer](https://rubydoc.info/gems/musa-dsl/Musa/Sequencer) - Main sequencer class and DSL
|
|
95
|
+
|
|
96
|
+
**Source code:** `lib/sequencer/`
|
|
97
|
+
|
|
98
|
+
|