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
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Music - Scales & Chords
|
|
2
|
+
|
|
3
|
+
Comprehensive framework for working with musical scales, tuning systems, and chord structures. The system resolves two main domains:
|
|
4
|
+
|
|
5
|
+
## Scales System
|
|
6
|
+
|
|
7
|
+
The **Scales** module provides hierarchical access to musical scales with multiple tuning systems and scale types:
|
|
8
|
+
|
|
9
|
+
**Architecture:**
|
|
10
|
+
- **ScaleSystem** (`Musa::Scales::ScaleSystem`): Defines tuning systems (e.g., 12-tone equal temperament)
|
|
11
|
+
- **ScaleSystemTuning** (`Musa::Scales::ScaleSystemTuning`): A scale system with specific reference frequency (e.g., A=440Hz)
|
|
12
|
+
- **ScaleKind** (`Musa::Scales::ScaleKind`): Scale types (major, minor, chromatic, etc.)
|
|
13
|
+
- **Scale** (`Musa::Scales::Scale`): A scale kind rooted on a specific pitch (e.g., C major)
|
|
14
|
+
- **NoteInScale** (`Musa::Scales::NoteInScale`): A specific note within a scale
|
|
15
|
+
|
|
16
|
+
**Registry:**
|
|
17
|
+
- **Scales::Scales** (`Musa::Scales::Scales`): Central registry for accessing scale systems by ID or method name
|
|
18
|
+
|
|
19
|
+
**Available scale systems:**
|
|
20
|
+
- `:et12` (EquallyTempered12ToneScaleSystem): 12-tone equal temperament (default)
|
|
21
|
+
|
|
22
|
+
**Available scale kinds in et12:**
|
|
23
|
+
- `:major` - Major scale (Ionian mode)
|
|
24
|
+
- `:minor` - Natural minor scale (Aeolian mode)
|
|
25
|
+
- `:minor_harmonic` - Harmonic minor scale (raised 7th degree)
|
|
26
|
+
- `:chromatic` - Chromatic scale (all 12 semitones)
|
|
27
|
+
|
|
28
|
+
## Chords System
|
|
29
|
+
|
|
30
|
+
The **Chords** module provides chord structures with scale context:
|
|
31
|
+
|
|
32
|
+
**Architecture:**
|
|
33
|
+
- **Chord** (`Musa::Chords::Chord`): Instantiated chord with root note and scale context
|
|
34
|
+
- **ChordDefinition** (`Musa::Chords::ChordDefinition`): Abstract chord structure definition (quality, size, intervals)
|
|
35
|
+
|
|
36
|
+
**Features:**
|
|
37
|
+
- Access chord tones by name (root, third, fifth, seventh, etc.)
|
|
38
|
+
- Voicing modifications (move, duplicate, octave)
|
|
39
|
+
- Navigate between related chords (change quality, add extensions)
|
|
40
|
+
- Extract pitches and notes
|
|
41
|
+
|
|
42
|
+
**Usage examples:**
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
require 'musa-dsl'
|
|
46
|
+
|
|
47
|
+
include Musa::Scales
|
|
48
|
+
include Musa::Chords
|
|
49
|
+
|
|
50
|
+
# Access default system and tuning
|
|
51
|
+
tuning = Scales.default_system.default_tuning # A=440Hz
|
|
52
|
+
|
|
53
|
+
# Create scales using available scale kinds
|
|
54
|
+
c_major = tuning.major[60] # C major (tonic pitch 60)
|
|
55
|
+
d_minor = tuning.minor[62] # D minor (natural)
|
|
56
|
+
e_harmonic = tuning.minor_harmonic[64] # E harmonic minor
|
|
57
|
+
chromatic = tuning.chromatic[60] # C chromatic
|
|
58
|
+
|
|
59
|
+
# Alternative access methods
|
|
60
|
+
c_major = Scales.et12[440.0].major[60] # Explicit system and frequency
|
|
61
|
+
|
|
62
|
+
# Access notes by grade (0-based) or function
|
|
63
|
+
tonic = c_major[0] # => C (grade 0)
|
|
64
|
+
mediant = c_major[2] # => E (grade 2)
|
|
65
|
+
dominant = c_major[4] # => G (grade 4)
|
|
66
|
+
|
|
67
|
+
# Access by function name
|
|
68
|
+
tonic = c_major.tonic # => C
|
|
69
|
+
supertonic = c_major.supertonic # => D
|
|
70
|
+
mediant = c_major.mediant # => E
|
|
71
|
+
subdominant = c_major.subdominant # => F
|
|
72
|
+
dominant = c_major.dominant # => G
|
|
73
|
+
|
|
74
|
+
# Access by Roman numeral or symbol
|
|
75
|
+
c_major[:I] # => Tonic (C)
|
|
76
|
+
c_major[:V] # => Dominant (G)
|
|
77
|
+
|
|
78
|
+
# Get pitch values from notes
|
|
79
|
+
pitch = c_major.tonic.pitch # => 60
|
|
80
|
+
|
|
81
|
+
# Navigate with octaves
|
|
82
|
+
note = c_major[2].octave(1) # E in octave 1
|
|
83
|
+
pitch_with_octave = note.pitch # => 76
|
|
84
|
+
|
|
85
|
+
# Chromatic operations - sharp and flat
|
|
86
|
+
c_sharp = c_major.tonic.sharp # => C# (chromatic, +1 semitone)
|
|
87
|
+
c_flat = c_major.tonic.flat # => Cb (chromatic, -1 semitone)
|
|
88
|
+
|
|
89
|
+
# Navigate by semitones
|
|
90
|
+
fifth_up = c_major.tonic.sharp(7) # => G (+7 semitones = perfect fifth)
|
|
91
|
+
third_up = c_major.tonic.sharp(4) # => E (+4 semitones = major third)
|
|
92
|
+
|
|
93
|
+
# Frequency calculation
|
|
94
|
+
frequency = c_major.tonic.frequency # => 261.63 Hz (middle C at A=440)
|
|
95
|
+
|
|
96
|
+
# Create chords from scale degrees
|
|
97
|
+
i_chord = c_major.tonic.chord # C major triad [C, E, G]
|
|
98
|
+
ii_chord = c_major.supertonic.chord # D minor triad [D, F, A]
|
|
99
|
+
v_chord = c_major.dominant.chord # G major triad [G, B, D]
|
|
100
|
+
|
|
101
|
+
# Create extended chords
|
|
102
|
+
i_seventh = c_major.tonic.chord :seventh # C major 7th [C, E, G, B]
|
|
103
|
+
v_ninth = c_major.dominant.chord :ninth # G 9th chord
|
|
104
|
+
|
|
105
|
+
# Access chord tones by name
|
|
106
|
+
root = i_chord.root # => C (NoteInScale)
|
|
107
|
+
third = i_chord.third # => E (NoteInScale)
|
|
108
|
+
fifth = i_chord.fifth # => G (NoteInScale)
|
|
109
|
+
|
|
110
|
+
# Get chord pitches
|
|
111
|
+
pitches = i_chord.pitches # => [60, 64, 67] (C, E, G)
|
|
112
|
+
notes = i_chord.notes # Array of ChordGradeNote structs
|
|
113
|
+
|
|
114
|
+
# Chord features
|
|
115
|
+
i_chord.quality # => :major
|
|
116
|
+
i_chord.size # => :triad
|
|
117
|
+
|
|
118
|
+
# Navigate between chord qualities
|
|
119
|
+
minor_chord = i_chord.with_quality(:minor) # C minor [C, Eb, G]
|
|
120
|
+
diminished = i_chord.with_quality(:diminished) # C diminished [C, Eb, Gb]
|
|
121
|
+
|
|
122
|
+
# Change chord extensions
|
|
123
|
+
seventh_chord = i_chord.with_size(:seventh) # C major 7th
|
|
124
|
+
ninth_chord = i_chord.with_size(:ninth) # C major 9th
|
|
125
|
+
|
|
126
|
+
# Voicing modifications - move specific tones to different octaves
|
|
127
|
+
voiced = i_chord.move(root: -1, fifth: 1) # Root down, fifth up
|
|
128
|
+
|
|
129
|
+
# Duplicate tones in other octaves
|
|
130
|
+
doubled = i_chord.duplicate(root: -2, third: [-1, 1]) # Root 2 down, third 1 down and 1 up
|
|
131
|
+
|
|
132
|
+
# Transpose entire chord
|
|
133
|
+
lower = i_chord.octave(-1) # Move chord down one octave
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Defining Custom Scale Systems, Scale Kinds, and Chord Definitions
|
|
137
|
+
|
|
138
|
+
The framework is extensible, allowing users to define custom tuning systems, scale types, and chord structures:
|
|
139
|
+
|
|
140
|
+
**Custom Scale Systems:**
|
|
141
|
+
|
|
142
|
+
Users can create custom tuning systems by subclassing `Musa::Scales::ScaleSystem` and implementing:
|
|
143
|
+
- `.id` - Unique symbol identifier
|
|
144
|
+
- `.notes_in_octave` - Number of notes per octave
|
|
145
|
+
- `.part_of_tone_size` - Size of smallest pitch unit
|
|
146
|
+
- `.intervals` - Hash mapping interval names to semitone offsets
|
|
147
|
+
- `.frequency_of_pitch(pitch, root_pitch, a_frequency)` - Pitch to frequency conversion
|
|
148
|
+
|
|
149
|
+
After defining, register with `Musa::Scales::Scales.register(CustomScaleSystem, default: false)`
|
|
150
|
+
|
|
151
|
+
**Custom Scale Kinds:**
|
|
152
|
+
|
|
153
|
+
Users can define new scale types by subclassing `Musa::Scales::ScaleKind` and implementing:
|
|
154
|
+
- `.id` - Unique symbol identifier (e.g., `:dorian`, `:pentatonic`)
|
|
155
|
+
- `.pitches` - Array defining scale structure with functions and pitch offsets
|
|
156
|
+
- `.chromatic?` - Whether this is the chromatic scale (optional, default: false)
|
|
157
|
+
- `.grades` - Number of grades per octave (optional, default: pitches.length)
|
|
158
|
+
|
|
159
|
+
After defining, register with `YourScaleSystem.register(CustomScaleKind)`
|
|
160
|
+
|
|
161
|
+
**Custom Chord Definitions:**
|
|
162
|
+
|
|
163
|
+
Users can register new chord types using `Musa::Chords::ChordDefinition.register`:
|
|
164
|
+
- `name` - Symbol identifier (e.g., `:sus4`, `:add9`)
|
|
165
|
+
- `offsets:` - Hash defining semitone intervals from root (e.g., `{ root: 0, fourth: 5, fifth: 7 }`)
|
|
166
|
+
- `**features` - Chord characteristics like `quality:` and `size:`
|
|
167
|
+
|
|
168
|
+
**Examples of custom definitions:**
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
require 'musa-dsl'
|
|
172
|
+
|
|
173
|
+
include Musa::Scales
|
|
174
|
+
include Musa::Chords
|
|
175
|
+
|
|
176
|
+
# Example 1: Define a custom pentatonic scale kind for the 12-tone system
|
|
177
|
+
class PentatonicMajorScaleKind < ScaleKind
|
|
178
|
+
class << self
|
|
179
|
+
def id
|
|
180
|
+
:pentatonic_major
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def pitches
|
|
184
|
+
[{ functions: [:I, :_1, :tonic], pitch: 0 },
|
|
185
|
+
{ functions: [:II, :_2], pitch: 2 },
|
|
186
|
+
{ functions: [:III, :_3], pitch: 4 },
|
|
187
|
+
{ functions: [:V, :_5], pitch: 7 },
|
|
188
|
+
{ functions: [:VI, :_6], pitch: 9 }]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def grades
|
|
192
|
+
5 # 5 notes per octave
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Register the new scale kind with the 12-tone system
|
|
198
|
+
Scales.et12.register(PentatonicMajorScaleKind)
|
|
199
|
+
|
|
200
|
+
# Use the new scale kind
|
|
201
|
+
tuning = Scales.default_system.default_tuning
|
|
202
|
+
c_pentatonic = tuning[:pentatonic_major][60] # C pentatonic major
|
|
203
|
+
puts c_pentatonic[0].pitch # => 60 (C)
|
|
204
|
+
puts c_pentatonic[1].pitch # => 62 (D)
|
|
205
|
+
puts c_pentatonic[2].pitch # => 64 (E)
|
|
206
|
+
|
|
207
|
+
# Example 2: Register a custom chord definition (sus4)
|
|
208
|
+
Musa::Chords::ChordDefinition.register :sus4,
|
|
209
|
+
quality: :suspended,
|
|
210
|
+
size: :triad,
|
|
211
|
+
offsets: { root: 0, fourth: 5, fifth: 7 }
|
|
212
|
+
|
|
213
|
+
# Use the custom chord definition
|
|
214
|
+
c_major = tuning.major[60]
|
|
215
|
+
# To use custom chords, access via NoteInScale#chord with the definition name
|
|
216
|
+
# or create manually using the definition
|
|
217
|
+
|
|
218
|
+
# Example 3: Register a custom chord definition (add9)
|
|
219
|
+
Musa::Chords::ChordDefinition.register :add9,
|
|
220
|
+
quality: :major,
|
|
221
|
+
size: :extended,
|
|
222
|
+
offsets: { root: 0, third: 4, fifth: 7, ninth: 14 }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## API Reference
|
|
226
|
+
|
|
227
|
+
**Complete API documentation:**
|
|
228
|
+
- [Musa::Scales](https://rubydoc.info/gems/musa-dsl/Musa/Scales) - Scale systems and tuning
|
|
229
|
+
- [Musa::Chords](https://rubydoc.info/gems/musa-dsl/Musa/Chords) - Chord structures and navigation
|
|
230
|
+
|
|
231
|
+
**Source code:** `lib/music/`
|
|
232
|
+
|
|
233
|
+
|
|
@@ -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
|
+
|