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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 707fe9806d319afc148d08559e3f57bae65802f8015a220c1cf9c65220c79ed2
4
- data.tar.gz: 2df3f1fcdd21ca6fc5b7fe0f2325d3319033e79a6b09163738b8d18ae2a01850
3
+ metadata.gz: cdf7189dfdc5b4f566f8727a90eae99f2025c46de642bd9e29af0d7c2ab53673
4
+ data.tar.gz: 4f3ecff2f2178e393a446c7ab4ac13c6f8aff89cbe6b178cf0ef333d84b376d7
5
5
  SHA512:
6
- metadata.gz: 9bbf7c979c54e4dc937ca9172aaba7796fb3597afa29592e3e20d52466d947632c8885906d9de42ae35ce4639d8a49db37fdcaa3a150a452e02a3885d12fdbce
7
- data.tar.gz: b3e51e89caa3db5fd86bf4f0f172df8b7845050f8ead625d92acf1e7c7926a5ec4c693d6ae945de6b102c8ed99cdc7c9c2b36ec4ee02881b5587de8e61c70590
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.
@@ -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 (.tr) and mordent (.mor)
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
- - `.tr` - Trill (rapid alternation with upper note)
72
- - `.mor` - Mordent (quick alternation with adjacent note)
73
- - `.turn` - Turn (four-note figure)
74
- - `.st` - Staccato (shortened duration)
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
 
@@ -1,3 +1,3 @@
1
1
  module Musa
2
- VERSION = '0.49.3'.freeze
2
+ VERSION = '0.49.4'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: musa-dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.49.3
4
+ version: 0.49.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Javier Sánchez Yeste