musa-dsl 0.49.3 → 0.49.4
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/docs/subsystems/generative.md +20 -0
- data/docs/subsystems/midi.md +16 -0
- data/docs/subsystems/sequencer.md +48 -0
- data/docs/subsystems/transcription.md +7 -6
- data/docs/vocabulary.md +2 -2
- data/lib/musa-dsl/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cdf7189dfdc5b4f566f8727a90eae99f2025c46de642bd9e29af0d7c2ab53673
|
|
4
|
+
data.tar.gz: 4f3ecff2f2178e393a446c7ab4ac13c6f8aff89cbe6b178cf0ef333d84b376d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a472d7b75153cdeac08f67bfc81668dd713491587e6b185a72a4abea6acdd1a87c156af964acb435876f423fd34a2b8e077df41695b4693327d53675d1da900
|
|
7
|
+
data.tar.gz: fe000da956e7dd4b1a86ddfc3c3bb72604be4da365c4e6f5cd0d96bea25169ab406d4ee6cf5f350ce8526ac76f910bb650c1e33949d513545e512bf7604bb0c7
|
|
@@ -82,6 +82,26 @@ markov = Musa::Markov::Markov.new(
|
|
|
82
82
|
melody_pitches = markov.to_a
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
### Replacing the table while the chain is running
|
|
86
|
+
|
|
87
|
+
`transitions=` swaps the table on a live chain.
|
|
88
|
+
**The new table has to cover every state the
|
|
89
|
+
chain can currently be in, not only the states it wants to produce.** The chain
|
|
90
|
+
keeps its current state across the swap, and if the new table has no entry for it:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
RuntimeError: No transition defined for <STATE>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
That raise happens on every call from then on, and under a sequencer it is easy
|
|
97
|
+
to miss entirely: the scheduled block's exception is recorded and the sequencer
|
|
98
|
+
carries on, so the voice simply goes quiet while everything else keeps playing.
|
|
99
|
+
Nothing crashes and nothing warns.
|
|
100
|
+
|
|
101
|
+
The way out is to build the tables over the **union** of the states any of them
|
|
102
|
+
can reach, giving each one an entry for every state even if some of those entries
|
|
103
|
+
only lead back out of it.
|
|
104
|
+
|
|
85
105
|
## Variatio
|
|
86
106
|
|
|
87
107
|
Generates all combinations of parameter variations using Cartesian product. Useful for creating comprehensive parameter sweeps, exploring all possibilities of a musical motif, or generating exhaustive harmonic permutations.
|
data/docs/subsystems/midi.md
CHANGED
|
@@ -82,6 +82,22 @@ voices.fast_forward = true
|
|
|
82
82
|
voices.fast_forward = false # Resume audible output
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
+
## A pitch on a channel is a boolean, not a counter
|
|
86
|
+
|
|
87
|
+
Two overlapping notes of the same pitch on the same voice are **one pitch
|
|
88
|
+
sounding**. `MIDIVoice` reference-counts per pitch: it emits a NoteOn for each,
|
|
89
|
+
and a single NoteOff when the last `NoteControl` is released. That is correct —
|
|
90
|
+
MIDI has no way to express "two of the same note on one channel" — but it breaks
|
|
91
|
+
two things people write.
|
|
92
|
+
|
|
93
|
+
**Counting NoteOn's will not find hanging notes.** `NoteOn - NoteOff > 0` is the normal
|
|
94
|
+
state of any piece with overlaps, not a leak. What answers the question is
|
|
95
|
+
whether any pitch still holds controls:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
hanging = voice.active_pitches.select { |_pitch, state| !state[:note_controls].empty? }
|
|
99
|
+
```
|
|
100
|
+
|
|
85
101
|
## MIDIRecorder - MIDI Event Recording
|
|
86
102
|
|
|
87
103
|
**MIDIRecorder** captures raw MIDI bytes alongside sequencer position timestamps and converts them into structured note events. Useful for recording phrases from external MIDI controllers synchronized with the sequencer timeline.
|
|
@@ -231,6 +231,30 @@ All scheduling methods (`every`, `play`, `move`, `play_timed`) pass parameters t
|
|
|
231
231
|
|
|
232
232
|
**Important**: keyword parameters (like `control:`) must be declared as **keyword arguments** in the block signature (`|control:|`), not as positional arguments (`|control|`).
|
|
233
233
|
|
|
234
|
+
**And the one that bites: a parameter with no value arrives as `nil`, which
|
|
235
|
+
overrides the Ruby default you wrote.** `SmartProcBinder` supplies every
|
|
236
|
+
declared parameter, so `nil` is passed rather than the parameter being left out
|
|
237
|
+
— and a default only fires when an argument is *absent*, never when it is `nil`.
|
|
238
|
+
This matters most in `launch`, where the recursion looks like it will seed
|
|
239
|
+
itself:
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
# WRONG — rep is nil on the first call, not 0
|
|
243
|
+
control.after { launch :section }
|
|
244
|
+
on :section do |rep = 0|
|
|
245
|
+
launch :section, rep + 1 # NoMethodError: undefined method '+' for nil
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# RIGHT — pass the starting value explicitly
|
|
249
|
+
control.after { launch :section, 0 }
|
|
250
|
+
on :section do |rep = 0| # the default now only documents the intent
|
|
251
|
+
launch :section, rep + 1
|
|
252
|
+
end
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The rule is the same wherever a block declares a parameter the caller may not
|
|
256
|
+
supply: give it a value, and treat the Ruby default as documentation.
|
|
257
|
+
|
|
234
258
|
### Parameters available per method
|
|
235
259
|
|
|
236
260
|
| Method | Positional params | Keyword params |
|
|
@@ -292,6 +316,30 @@ Three things the table cannot say and the result does:
|
|
|
292
316
|
- `started_ago:` is an **array**, not a number: one entry per value that was
|
|
293
317
|
already sounding when this one arrived, empty when nothing was.
|
|
294
318
|
|
|
319
|
+
## A block that raises does not stop the piece
|
|
320
|
+
|
|
321
|
+
Every scheduled block runs inside a rescue: an exception is written to the
|
|
322
|
+
sequencer's logger and the sequencer carries on with the next tick. Nothing
|
|
323
|
+
propagates to whoever called `run`, and nothing appears on stdout unless the
|
|
324
|
+
logger is being watched.
|
|
325
|
+
|
|
326
|
+
That is the right behaviour for a piece playing live — one broken voice should
|
|
327
|
+
not take the other five with it — but it has a consequence worth knowing before
|
|
328
|
+
it happens to you: **a voice can fall silent for the rest of the piece while
|
|
329
|
+
everything reports success.** The piece runs to completion, the verification
|
|
330
|
+
passes, and the only symptom is silence where there should be a line.
|
|
331
|
+
|
|
332
|
+
If a voice goes quiet with no error, this is the first thing to check. An
|
|
333
|
+
offline verification can catch it by intercepting `logger.error` on the
|
|
334
|
+
transport's logger and failing when anything arrives:
|
|
335
|
+
|
|
336
|
+
```text
|
|
337
|
+
errors = []
|
|
338
|
+
transport.logger.define_singleton_method(:error) { |*args, &b| errors << (b ? b.call : args.first) }
|
|
339
|
+
# ... run the piece ...
|
|
340
|
+
raise "a scheduled block failed: #{errors.first}" unless errors.empty?
|
|
341
|
+
```
|
|
342
|
+
|
|
295
343
|
## Play Modes
|
|
296
344
|
|
|
297
345
|
`play` supports three modes that determine how series elements are scheduled. The default mode is `:wait`.
|
|
@@ -31,7 +31,7 @@ require 'musa-dsl'
|
|
|
31
31
|
|
|
32
32
|
using Musa::Extension::Neumas
|
|
33
33
|
|
|
34
|
-
# Neuma notation with ornaments: trill (
|
|
34
|
+
# Neuma notation with ornaments: trill (tr) and mordent (mor)
|
|
35
35
|
neumas = "(0 1 mf) (+2 1 tr) (+4 1 mor) (+5 1)"
|
|
36
36
|
|
|
37
37
|
# Create scale and decoder
|
|
@@ -67,11 +67,12 @@ end
|
|
|
67
67
|
# Pitch: 79, Duration: 1/4, Velocity: 80 # G5 (no ornament)
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
**Supported ornaments
|
|
71
|
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
70
|
+
**Supported ornaments**:
|
|
71
|
+
|
|
72
|
+
- `tr` - Trill (rapid alternation with upper note)
|
|
73
|
+
- `mor` - Mordent (quick alternation with adjacent note)
|
|
74
|
+
- `turn` - Turn (four-note figure)
|
|
75
|
+
- `st` - Staccato (shortened duration)
|
|
75
76
|
|
|
76
77
|
## MusicXML with Ornament Symbols
|
|
77
78
|
|
data/docs/vocabulary.md
CHANGED
|
@@ -27,7 +27,7 @@ missing from the documents, and that is where to add it.
|
|
|
27
27
|
|
|
28
28
|
## midi
|
|
29
29
|
|
|
30
|
-
`MIDIRecorder` · `MIDIVoices` · `channel` · `duration` · `note` · `note_off` · `pitch` · `position` · `raw` · `record` · `transcription` · `velocity` · `velocity_off` · `voice` · `voices`
|
|
30
|
+
`MIDIRecorder` · `MIDIVoice` · `MIDIVoices` · `channel` · `duration` · `note` · `note_off` · `pitch` · `position` · `raw` · `record` · `transcription` · `velocity` · `velocity_off` · `voice` · `voices`
|
|
31
31
|
|
|
32
32
|
## music
|
|
33
33
|
|
|
@@ -47,7 +47,7 @@ missing from the documents, and that is where to add it.
|
|
|
47
47
|
|
|
48
48
|
## sequencer
|
|
49
49
|
|
|
50
|
-
`Sequencer` · `after` · `at` · `duration` · `every` · `move` · `next_value` · `note_duration` · `now` · `on_stop` · `play` · `play_timed` · `sequencer` · `stop` · `time` · `wait` · `with`
|
|
50
|
+
`Sequencer` · `SmartProcBinder` · `after` · `at` · `duration` · `error` · `every` · `launch` · `logger` · `move` · `next_value` · `note_duration` · `now` · `on_stop` · `play` · `play_timed` · `run` · `sequencer` · `stop` · `time` · `wait` · `with`
|
|
51
51
|
|
|
52
52
|
## series
|
|
53
53
|
|
data/lib/musa-dsl/version.rb
CHANGED