@denaudio/den 0.0.0-stage → 0.2.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.
Files changed (191) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/LICENSE-APACHE +202 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +107 -2
  5. package/dist/biquad-eq.d.ts +11 -0
  6. package/dist/biquad-eq.js +35 -0
  7. package/dist/catalog-filter-math.d.ts +4 -0
  8. package/dist/catalog-filter-math.js +30 -0
  9. package/dist/character-filter.d.ts +13 -0
  10. package/dist/character-filter.js +53 -0
  11. package/dist/convolution-spectrum.d.ts +25 -0
  12. package/dist/convolution-spectrum.js +82 -0
  13. package/dist/convolution.d.ts +17 -0
  14. package/dist/convolution.js +87 -0
  15. package/dist/crossover.d.ts +11 -0
  16. package/dist/crossover.js +17 -0
  17. package/dist/curve-shaper.d.ts +26 -0
  18. package/dist/curve-shaper.js +73 -0
  19. package/dist/delay-fx.d.ts +37 -0
  20. package/dist/delay-fx.js +84 -0
  21. package/dist/delay-readhead.d.ts +19 -0
  22. package/dist/delay-readhead.js +54 -0
  23. package/dist/delay-settings.d.ts +55 -0
  24. package/dist/delay-settings.js +20 -0
  25. package/dist/drive.d.ts +24 -0
  26. package/dist/drive.js +116 -0
  27. package/dist/dual-head-delay.d.ts +28 -0
  28. package/dist/dual-head-delay.js +77 -0
  29. package/dist/dynamics.d.ts +61 -0
  30. package/dist/dynamics.js +130 -0
  31. package/dist/envelope.d.ts +34 -0
  32. package/dist/envelope.js +62 -0
  33. package/dist/filter.d.ts +9 -0
  34. package/dist/filter.js +34 -0
  35. package/dist/filterbank-vocoder.d.ts +35 -0
  36. package/dist/filterbank-vocoder.js +52 -0
  37. package/dist/formant-bank.d.ts +15 -0
  38. package/dist/formant-bank.js +28 -0
  39. package/dist/freeze-reverb.d.ts +35 -0
  40. package/dist/freeze-reverb.js +61 -0
  41. package/dist/frequency-shifter.d.ts +24 -0
  42. package/dist/frequency-shifter.js +57 -0
  43. package/dist/gate.d.ts +2 -0
  44. package/dist/gate.js +12 -0
  45. package/dist/index.d.ts +7 -0
  46. package/dist/index.js +12 -0
  47. package/dist/instrument-example.d.ts +12 -0
  48. package/dist/instrument-example.js +29 -0
  49. package/dist/instrument-settings.d.ts +95 -0
  50. package/dist/instrument-settings.js +36 -0
  51. package/dist/instrument.d.ts +42 -0
  52. package/dist/instrument.js +166 -0
  53. package/dist/lfo.d.ts +19 -0
  54. package/dist/lfo.js +55 -0
  55. package/dist/live-buffer.d.ts +39 -0
  56. package/dist/live-buffer.js +52 -0
  57. package/dist/live-granular.d.ts +41 -0
  58. package/dist/live-granular.js +64 -0
  59. package/dist/lookahead-limiter.d.ts +31 -0
  60. package/dist/lookahead-limiter.js +67 -0
  61. package/dist/loop-crossfade.d.ts +28 -0
  62. package/dist/loop-crossfade.js +72 -0
  63. package/dist/modulation-fx.d.ts +47 -0
  64. package/dist/modulation-fx.js +58 -0
  65. package/dist/modulation.d.ts +132 -0
  66. package/dist/modulation.js +228 -0
  67. package/dist/mpe-expression.d.ts +36 -0
  68. package/dist/mpe-expression.js +73 -0
  69. package/dist/multiband-dynamics.d.ts +50 -0
  70. package/dist/multiband-dynamics.js +60 -0
  71. package/dist/musical-controls.d.ts +76 -0
  72. package/dist/musical-controls.js +125 -0
  73. package/dist/musical-examples.d.ts +21 -0
  74. package/dist/musical-examples.js +142 -0
  75. package/dist/musical-materials.d.ts +110 -0
  76. package/dist/musical-materials.js +56 -0
  77. package/dist/oscillator.d.ts +10 -0
  78. package/dist/oscillator.js +35 -0
  79. package/dist/oversampled-drive.d.ts +19 -0
  80. package/dist/oversampled-drive.js +99 -0
  81. package/dist/performance.d.ts +61 -0
  82. package/dist/performance.js +241 -0
  83. package/dist/pitch-quantizer.d.ts +26 -0
  84. package/dist/pitch-quantizer.js +88 -0
  85. package/dist/prepared-convolution.d.ts +26 -0
  86. package/dist/prepared-convolution.js +104 -0
  87. package/dist/resident-recorder.d.ts +28 -0
  88. package/dist/resident-recorder.js +68 -0
  89. package/dist/resident-time-stretch.d.ts +46 -0
  90. package/dist/resident-time-stretch.js +136 -0
  91. package/dist/resonator.d.ts +32 -0
  92. package/dist/resonator.js +63 -0
  93. package/dist/reverb.d.ts +27 -0
  94. package/dist/reverb.js +52 -0
  95. package/dist/sample.d.ts +113 -0
  96. package/dist/sample.js +212 -0
  97. package/dist/source.d.ts +58 -0
  98. package/dist/source.js +113 -0
  99. package/dist/spatial-chains.d.ts +50 -0
  100. package/dist/spatial-chains.js +76 -0
  101. package/dist/spectral-fft.d.ts +27 -0
  102. package/dist/spectral-fft.js +84 -0
  103. package/dist/spectral-freeze.d.ts +24 -0
  104. package/dist/spectral-freeze.js +74 -0
  105. package/dist/spectral-gate.d.ts +23 -0
  106. package/dist/spectral-gate.js +58 -0
  107. package/dist/spectral-stft.d.ts +14 -0
  108. package/dist/spectral-stft.js +70 -0
  109. package/dist/spectral-texture.d.ts +46 -0
  110. package/dist/spectral-texture.js +141 -0
  111. package/dist/spectral.d.ts +18 -0
  112. package/dist/spectral.js +10 -0
  113. package/dist/state-variable-filter.d.ts +17 -0
  114. package/dist/state-variable-filter.js +25 -0
  115. package/dist/stereo-delay.d.ts +45 -0
  116. package/dist/stereo-delay.js +70 -0
  117. package/dist/virtual-analog.d.ts +26 -0
  118. package/dist/virtual-analog.js +66 -0
  119. package/dist/voice-policy.d.ts +37 -0
  120. package/dist/voice-policy.js +245 -0
  121. package/dist/wavetable-bands.d.ts +37 -0
  122. package/dist/wavetable-bands.js +145 -0
  123. package/dist/wavetable-read.d.ts +6 -0
  124. package/dist/wavetable-read.js +11 -0
  125. package/dist/wavetable.d.ts +29 -0
  126. package/dist/wavetable.js +49 -0
  127. package/dist/windowed-pitch-shift.d.ts +34 -0
  128. package/dist/windowed-pitch-shift.js +51 -0
  129. package/docs/catalog-audition.md +76 -0
  130. package/docs/catalog-filters.md +125 -0
  131. package/docs/catalog-modulation-fx.md +220 -0
  132. package/docs/catalog-status.md +433 -0
  133. package/docs/character-filter.md +139 -0
  134. package/docs/contracts.md +141 -0
  135. package/docs/control-modulation.md +194 -0
  136. package/docs/convolution.md +50 -0
  137. package/docs/curve-shaper.md +117 -0
  138. package/docs/delay-fx-capture-findings.json +134 -0
  139. package/docs/delay-fx.md +202 -0
  140. package/docs/delay-readhead.md +121 -0
  141. package/docs/delay-settings-candidate.json +333 -0
  142. package/docs/delay-settings.md +111 -0
  143. package/docs/dependency-findings.md +58 -0
  144. package/docs/deployment-status.md +37 -0
  145. package/docs/drive.md +152 -0
  146. package/docs/dual-head-delay.md +131 -0
  147. package/docs/dynamics.md +180 -0
  148. package/docs/envelope-integer-staging.md +64 -0
  149. package/docs/filter-materialization-results.json +71 -0
  150. package/docs/filter-materialization.md +135 -0
  151. package/docs/filter-oscillator.md +126 -0
  152. package/docs/filterbank-vocoder-entry.md +58 -0
  153. package/docs/freeze-reverb.md +179 -0
  154. package/docs/frequency-shifter.md +133 -0
  155. package/docs/frontend-audition.md +22 -0
  156. package/docs/gain-schedule-verification.md +53 -0
  157. package/docs/initial-acceptance.md +79 -0
  158. package/docs/instrument-performance.md +191 -0
  159. package/docs/instrument-settings.md +130 -0
  160. package/docs/instrument.md +204 -0
  161. package/docs/integration-candidate.md +114 -0
  162. package/docs/live-buffer.md +163 -0
  163. package/docs/live-granular.md +179 -0
  164. package/docs/lookahead-multiband.md +183 -0
  165. package/docs/loop-crossfade.md +99 -0
  166. package/docs/mpe-expression.md +65 -0
  167. package/docs/musical-clock-seek-boundary.md +31 -0
  168. package/docs/musical-controls-contract.md +144 -0
  169. package/docs/musical-examples.md +255 -0
  170. package/docs/oversampled-drive.md +153 -0
  171. package/docs/performance.md +53 -0
  172. package/docs/pitch-quantizer.md +55 -0
  173. package/docs/prepared-convolution.md +197 -0
  174. package/docs/realtime-audition.md +63 -0
  175. package/docs/resident-recorder.md +151 -0
  176. package/docs/resident-time-stretch.md +196 -0
  177. package/docs/sample.md +191 -0
  178. package/docs/sound-audition-headroom.md +103 -0
  179. package/docs/sources.md +81 -0
  180. package/docs/spatial-chains-entry.md +251 -0
  181. package/docs/spectral-freeze-entry.md +132 -0
  182. package/docs/spectral-gate-entry.md +134 -0
  183. package/docs/spectral-large-candidate.md +81 -0
  184. package/docs/spectral-texture-entry.md +178 -0
  185. package/docs/spectral.md +127 -0
  186. package/docs/virtual-analog.md +77 -0
  187. package/docs/voice-policy.md +38 -0
  188. package/docs/wavetable-bands.md +138 -0
  189. package/docs/wavetable.md +101 -0
  190. package/docs/windowed-pitch-shift.md +112 -0
  191. package/package.json +269 -4
@@ -0,0 +1,65 @@
1
+ # Bounded lower-zone expression candidate
2
+
3
+ Status: CANDIDATE. `mpeExpression` is an expression-routing primitive for a manually configured MIDI 1.0 lower zone. It is not a complete MPE receiver, MPE compliance certification, a voice allocator, a pedal implementation, a MIDI runtime or an instrument.
4
+
5
+ ## Entry and values
6
+
7
+ The public entry is `@denaudio/den/mpe-expression`, exporting `mpeExpression`, `MpeExpressionConfig` and `MpeExpressionVoice`.
8
+
9
+ Instantiate `mpeExpression({ memberChannels, memberBendRange, masterBendRange })` once. `memberChannels` is a fixed integer 1..15, default 15. Native channels are zero-based: master channel 0 (musician-facing channel 1), member channels 1..N (musician-facing channels 2..N+1). Bind once to the same existing `event.midi({from:'main',name:...})` handle as `performancePolicy` or `voicePolicy`. Multiple native handlers share the port; the module neither wraps nor resends MIDI.
10
+
11
+ Pass an allocator voice's unchanged `active` and `channel` fields to `read(voice)`. The returned `inZone` is true only for an active member channel. Every numeric output is zero for inactive voices, master-channel notes, channels outside the configured zone or invalid channel indices. A clamped internal buffer index never turns an invalid channel into a valid member.
12
+
13
+ - `memberBend` and `masterBend`: normalized -1..1. MIDI bend 8192 is exactly zero; 0 is -1 and 16383 is +1. The negative half divides by 8192 and positive half by 8191.
14
+ - `bendSemitones`: member normalized bend times `memberBendRange`, plus master normalized bend times `masterBendRange`. Ranges are symmetric semitones, finite 0..96, construction-fixed, with defaults 48 and 2 respectively. Addition uses f64 intermediates and one final f32 conversion. No clipping or note-frequency conversion is applied here.
15
+ - `memberPressure` and `masterPressure`: channel-pressure bytes normalized 0..127 to 0..1, initially zero. These are separate lanes. A composition chooses how they affect sound and whether to combine them.
16
+ - `memberTimbre` and `masterTimbre`: CC74 bytes normalized 0..127 to 0..1, initially 64/127. The default is the exact MIDI byte center, approximately 0.503937, not 0.5. Raw timbre is not intrinsically bipolar. These are separate lanes with no invented timbre destination or combination law.
17
+
18
+ Use `bendSemitones` exactly once in the pitch path. When composing with `performancePolicy`, do not also add its `bend`, which represents the same member-channel message. Add any independently desired tuning/transpose controls separately. Pressure/timbre from the existing policy also represent the same incoming bytes and must not be accidentally applied twice. No pitch smoothing is provided.
19
+
20
+ ## Notes, tails, steals and pedals
21
+
22
+ The existing allocation policy alone owns note-on/off, velocity-zero note-on, duplicate pairing, held identities, overflow, voice stealing, mono priority, gate/retrigger, pedal and release completion. This module does not filter notes admitted by that policy. A composition wanting member-only sound must use `inZone` to gate its output; master/out-of-zone notes can still consume allocator slots.
23
+
24
+ Expression is channel state, not a per-note identifier or voice-slot cache. It can be set before note-on and persists after note-off. Every active voice carrying that member channel observes the current channel expression, including multiple same-key identities and release tails. Reusing a member channel while an old tail remains means both the new note and old tail receive later changes. A stolen physical slot immediately reads its replacement's channel; the stolen identity's later note-off retains the original allocator semantics. A free slot emits zero expression. Notes, note-offs, steals, CC120, CC123 and release completion do not themselves reset expression.
25
+
26
+ With `performancePolicy`, member-channel CC64 sustain continues to work as documented there. Master-channel CC64 is NOT propagated to members. Master CC120 and CC123 also do not become zone-wide panic/all-notes-off. Their handling remains the original allocator's channel-local behavior. This intentionally limited primitive is unsuitable as a drop-in full MPE pedal receiver.
27
+
28
+ ## Reset, timing and snapshots
29
+
30
+ CC121 resets only the addressed master/member expression state to bend center, pressure zero and timbre byte 64. Master CC121 resets the master lanes only. It does not reset every member. The original allocator/performance policy independently handles that same CC according to its own contract, including its different legacy timbre default. This module's outputs are authoritative for its expression path.
31
+
32
+ `reset(true)` resets every expression channel; `reset(false)` does nothing. Reset is level-sensitive and does not clear notes, stop audio, raise pedals, generate retriggers, or reset DSP. A composition's full panic must explicitly call the allocation policy reset and clear its envelopes/oscillators as appropriate. Call expression reset after native dispatch if reset should win over MIDI in the same quantum.
33
+
34
+ All state is native transient integer state and is excluded from the snapshot. A **fresh-instance** restore (including `renderOffline` into a new driver) starts from centered bend, zero pressure and timbre byte 64. Native 0.4.1 **in-place** `node.restore()` only overlays saved slots: it leaves the current expression, notes and pedals unchanged. It neither recalls excluded gestures from the blob nor clears current ones. For a cold in-place restore, use the composition's existing explicit reset for expression, allocation policy and DSP after restoring; let at least one quantum render before fresh notes. Expression-only reset cannot clear notes or silence an orphan envelope. A composition must also clear persistent voice DSP whenever its note identity is absent.
35
+
36
+ Native unworklet 0.4.1 dispatch is FIFO before each 128-sample process quantum. Multiple messages in a quantum coalesce to the final state. A nominal sample-129 event affects boundary 128. No sample-accurate scheduling, new transport or hardware MIDI claim is made.
37
+
38
+ ## Deliberate exclusions
39
+
40
+ No upper zone, dynamic zone/RPN negotiation, RPN bend-range updates, MIDI-CI/MIDI 2.0, polyphonic key-pressure interpretation, master-pedal propagation, external MIDI host/device verification or per-note release-tail freezing is implemented. Unsupported messages are ignored by this expression primitive, while other handlers on the native input retain their own behavior.
41
+
42
+ The MIDI Association's [MPE overview](https://midi.org/midi-polyphonic-expression-mpe-specification-adopted) explains member-channel expression and zone-wide master control. The primitive implements only the manually configured expression subset documented above. The broader catalog's full MPE gap remains open.
43
+
44
+ ## Evidence
45
+
46
+ The 35 focused tests use byte fixtures with an independent ordinary-object state model at 44.1, 48 and 96 kHz. They cover master/member isolation and bend addition, endpoint/center values, duplicate FIFO pairing, steals and stale note-offs, channel reuse/tails, per-member sustain, explicitly absent master pedal/panic propagation, channel-local CC121, expression-only versus composition-wide reset, empty transient snapshots, unsupported controllers/RPN, native handler registration order and all 15 members.
47
+
48
+ The packed consumer gate requires strict public-subpath TypeScript, native MIDI/envelope/oscillator rendering at the three offline rates, independent pitch/amplitude equations, zero scrubbed samples, snapshot silence and fresh-note safety. Maximum configured member count is storage coverage, not 15-voice allocation or real-time clearance. No browser, hardware, listening approval or approved golden follows from offline evidence.
49
+
50
+ The diagnostic composition routes pitch as note plus `bendSemitones`, then applies separate tuning controls. Its amplitude is velocity times a linear envelope times (0.5 + 0.25 member pressure + 0.25 master pressure) times (0.5 + 0.25 member timbre + 0.25 master timbre), with output gain 0.2. This is an explicit test routing, not a standardized MPE sound or preset. The initial packed proof reports zero pitch/level error, maximum audio error below 3.3e-8 and four-voice chord error below 7.7e-8. The four-voice/eight-identity/15-member composition is 719,110 WASM bytes with 65,536 bytes of memory, unchanged over 256 driver blocks. These are functional/fixed-memory results, not deadline clearance. Exact-head package/source/lock hashes and CANDIDATE status are retained in the generated manifest and result pair.
51
+
52
+ ## Retained live-restore finding
53
+
54
+ The first integrated browser probe on `ed061ca` expected a centered fresh note
55
+ after in-place restore, but observed 187.5 Hz rather than 375 Hz: the current master
56
+ bend of −12 semitones was correctly retained by the native overlay. The fresh-
57
+ driver offline test had not exercised this lifecycle. The corrected browser
58
+ contract separately checks continued held notes/current expression, silence
59
+ when the current note was already released, inherited master expression on a
60
+ new member note, and centered fresh-note audio only after explicit composition
61
+ reset has rendered. This diagnostic composition also tests reset-before-restore
62
+ when a quantum has completed first: its inactive-voice guards clear the restored
63
+ persistent envelope/oscillator tail. General compositions should use the explicit
64
+ reset-after-restore sequence above unless their own ordering is verified. DSP
65
+ statements and upstream restoration are unchanged.
@@ -0,0 +1,31 @@
1
+ # Musical clock seek boundary correction
2
+
3
+ A native entry probe found that negative tiny finite f32 requests such as -2^-60
4
+ and -2^-149 could round to exactly `steps` when wrapped in f64. The old clock
5
+ emitted an invalid `step === steps`, `phase === 0` on that seek sample and stored
6
+ zero position afterward. This violated the existing `[0,steps)` contract.
7
+
8
+ The repair clamps only the rounded wrapped position to the immediately preceding
9
+ f64 below `steps`, computed at construction as
10
+ `steps - 2**(ceil(log2(steps))-53)`. Already-valid wrapped f64 positions retain their
11
+ values; no extra f32 seek quantization, state slot, rate change, or API is added.
12
+ Exact positive/negative integer boundaries still wrap to their correct step;
13
+ negative tiny seeks remain within the final step, with public phase capped below
14
+ one under the existing f32 phase contract. This cannot recover precision already
15
+ lost when an input f32 was created, and is not arbitrary-precision transport.
16
+
17
+ `tests/probes/musical-controls-clock-boundary.mjs` preserves a repeatable native
18
+ probe. The pre-repair source SHA256 was
19
+ `5b9c35ef1ecc9b00d97c649c645179dc25560d81d17b74788e2dd8293c1f6dcd`.
20
+ The original output for both -2^-149 and -2^-60 was `(step,phase)=(1,0),(3,0),
21
+ (64,0)` for cycles of 1, 3, and 64, in free and tempo mode, with zero position
22
+ state at the final seek. The original source, f32 input/output bytes and snapshot
23
+ were retained separately before the repair.
24
+
25
+ `tests/musical-clock-seek.spec.ts` uses exact BigInt f32 units and a bit-adjacent
26
+ f64 upper bound as its independent oracle. It covers every fixed step count
27
+ 1..64, free/tempo modes and extreme tempo divisions, positive/negative integer
28
+ neighbors, the input clamp endpoints, tiny negative normal/subnormal requests,
29
+ actual native stored position, and identical same-schema snapshot continuation.
30
+ Existing modulation/arpeggiator regressions remain required. All native evidence
31
+ is CANDIDATE; runtime remains NOT_CLEARED.
@@ -0,0 +1,144 @@
1
+ # Musical controls candidate contract
2
+
3
+ This bounded additive lane defines `@denaudio/den/musical-controls`, with
4
+ `curvedAdsr` and `musicalLfo`. Existing envelope, LFO, clock, and MSEG entries stay
5
+ compatible. Package export wiring belongs to the integration owner. This document
6
+ records the independently reviewed entry contract; execution evidence is separate.
7
+
8
+ Both graphs use fixed state, instantiate once, and tick exactly once per sample.
9
+ Sample rate is an integer in [8000,192000]. All dynamic numeric inputs are finite
10
+ f32 values; NaN and infinity are outside this contract. Sample-rate/configuration changes
11
+ require reconstruction; same-schema snapshots at the same configuration resume
12
+ exactly. No routing, scheduler, MIDI/MPE/RPN, parameter, snapshot, or clock framework
13
+ is added. Generated output remains CANDIDATE; runtime is NOT_CLEARED.
14
+
15
+ ## Curved ADSR
16
+
17
+ `curvedAdsr({ sampleRate }).tick({ gate, retrigger, reset, attack, decay, sustain,
18
+ release, attackBend, decayBend, releaseBend }) -> { level, done }`.
19
+
20
+ Gate/retrigger/reset are bool nodes; all other inputs and level are f32 nodes;
21
+ done is bool. Durations are seconds, clamped [0,30], then rounded to the nearest
22
+ sample with half samples up after the f32 input conversion. Sustain clamps [0,1].
23
+ Bends clamp [-1,1]. For a segment of N frames, step k uses t=k/N and
24
+ C(t,b)=t+b*t*(1-t). b=0 is linear, -1 is t² (ease-in), +1 is 2t-t² (ease-out).
25
+ The output is start+(target-start)*C(t,b), with exact endpoint assignment on step
26
+ N. This shapes segment time, preserving the latched sustain target and the
27
+ retrigger/release origin. It is not a post-warp of absolute ADSR amplitude.
28
+ Bend zero specifies a linear curve, not byte identity with the legacy iterative
29
+ envelope, because this module deliberately latches the last emitted f32 level.
30
+
31
+ - Stages are idle, attack, decay, sustain, release. Idle/reset output zero and
32
+ done=true. Sustain, including sustain zero, has done=false.
33
+ - A gate rise or high retrigger while gate is high begins attack from the last
34
+ emitted f32 level. Retrigger is level-sensitive; every high sample restarts.
35
+ - Positive N-frame stages emit step 1 on their entry sample and their endpoint
36
+ on step N. Attack duration/bend latch on its entry. Decay duration/bend/target
37
+ latch on the preceding positive attack endpoint, with its first step on the
38
+ next sample. Release duration/bend latch on the gate-fall sample.
39
+ - Zero attack reaches 1 and immediately enters decay on the same sample. A zero
40
+ decay then reaches sustain immediately. Zero decay following a positive attack
41
+ reaches sustain on the next sample. Zero release becomes idle immediately.
42
+ - Gate-off wins over retrigger, releases from the last emitted level, and reaches
43
+ exact zero/done on release step N. Release from zero becomes done immediately.
44
+ A positive release origin can round to f32 zero before step N; that does not
45
+ make done true early. Completion uses the latched segment, not rounded output.
46
+ - Reset wins over gate-off and retrigger, emits zero immediately, clears stage
47
+ state, and consumes the current gate. A held gate after reset needs a new gate
48
+ edge or retrigger. Held reset stays idle. Retrigger with gate=false is ignored.
49
+ - Duration, target, and bend edits do not change an active segment. Sustain stage
50
+ follows the current clamped sustain input every sample. Such live sustain edits,
51
+ zero durations, and reset can intentionally jump; no implicit smoothing exists.
52
+ - Segment state records exact i32 duration/elapsed values and scaled f64 start,
53
+ target, bend, and last-emitted level. Power-of-two scaling protects tiny finite
54
+ f32 values against core 0.4.1 scalar-state flushing. Output rounds to f32.
55
+
56
+ Independent numerical oracles use the algebraically different Bernstein
57
+ form C=(1+b)*t*(1-t)+t², closed-form event intervals, exact endpoint/done checks,
58
+ manual retrigger/release origins, zero-stage precedence, latching counterexamples,
59
+ maximum-duration sample probes, tiny controls, instance isolation, and identical
60
+ same-schema snapshot continuation at 44.1/48/96 kHz. Assertions distinguish absolute-level warping, wrong endpoints, and incorrect
61
+ latching from the specified behavior. The maximum-duration probes inspect the
62
+ first 383 steps and native latched duration; they do not render a full 30 seconds.
63
+
64
+ ## Tempo/free multiwave LFO
65
+
66
+ `musicalLfo({ sampleRate, mode, waveform, beatsPerCycle? }).tick({ rate, reset,
67
+ seek, position, phaseOffset, hold }) -> { value, phase }`.
68
+
69
+ `mode` is fixed `free` or `tempo`; `waveform` is fixed `sine`, `triangle`, `saw`,
70
+ or `square`. `beatsPerCycle` defaults to 1 and accepts powers of two in [1/64,64]
71
+ only in tempo mode; free mode requires 1. Rate/position/phaseOffset and outputs
72
+ are f32; reset/seek/hold are bool.
73
+
74
+ The implementation composes the existing `musicalClock` with one step per cycle,
75
+ using stepsPerBeat=1/beatsPerCycle for tempo. Free rate is Hz clamped [0,20].
76
+ Tempo rate is BPM clamped [0,1000]; frequency is BPM/(60*beatsPerCycle), up to
77
+ 1066 2/3 Hz at the smallest division. No frequency cap changes the tempo ratio.
78
+ That extreme is above conventional LFO rates; use appropriate rates for the
79
+ modulated destination. Position and phaseOffset are in cycles, each clamped to
80
+ [-1048576,1048576]. Position matters only on a rising seek edge. This module
81
+ inherits the existing clock's wrapping and the separately reviewed narrow repair
82
+ in `docs/musical-clock-seek-boundary.md`: a wrapped value that rounds up to the
83
+ cycle length is capped to its immediately preceding f64. Negative tiny seeks
84
+ therefore stay in the last step; exact signed integer seeks reach zero. No extra
85
+ f32 seek quantization is inserted before the clock.
86
+
87
+ - The initial base phase is zero. Output uses current phase before integrating
88
+ this sample's rate. Changing rate preserves phase. Rate zero or hold=true stops
89
+ base-phase integration; the live offset still affects the waveform.
90
+ - Reset pins the base phase to zero throughout a held reset and wins over seek.
91
+ Reset consumes seek level; a seek held across reset release needs a fresh edge.
92
+ Offset still applies while reset. The first reset-low sample emits base zero
93
+ and resumes integration unless held or rate zero.
94
+ - Seek is rising-edge-sensitive, can move phase while held, and emits the sought
95
+ phase on that sample. A held seek does not pin or repeatedly restart phase.
96
+ - The existing clock exposes a canonical f32 base phase (capped below 1). The
97
+ offset first loses its integer part by truncation toward zero, preserving its
98
+ signed fractional part. Shifted phase is wrap(f64(basePhase)+fractionalOffset),
99
+ rounded to f32 and capped at 1-2^-24, with exactly one wrap after the addition.
100
+ This preserves negative-tiny offsets at base zero as the upper capped phase;
101
+ large integral offsets do not erase tiny base phases by f64 addition.
102
+ Waveforms are evaluated at that public phase, deliberately retaining
103
+ the existing clock's f32 quantization. Very slow changes may be inaudible or
104
+ repeat while internal compensated/scaled clock state continues to advance.
105
+ This module does not promise full-f64 waveform phase or sub-ULP edge timing.
106
+ - Sine: sin(2πp), using the bounded f64 polynomial already used by the legacy LFO.
107
+ Triangle: 4p for p<1/4; 2-4p for 1/4<=p<3/4; 4p-4 otherwise.
108
+ Saw: 2p-1. Square: +1 for p<1/2, -1 otherwise (50% duty, boundary is low).
109
+ Output is f32 in [-1,1]. Saw and square have explicit discontinuities; all are
110
+ naïve non-bandlimited waveforms, with no anti-aliasing or smoothing claims.
111
+ - Offset/seek/reset can intentionally jump. Hold freezes the clock, not changing
112
+ offsets. No depth, destination-unit conversion, random waveform, or transport
113
+ synchronization beyond caller-supplied BPM is included.
114
+
115
+ Independent oracles calculate phase from exact f32 input schedules or rational
116
+ sample-index expressions, apply the declared public f32 rounding, and compare
117
+ sine with Math.sin and other waves with independent piecewise formulas. Tests
118
+ cover all waves, free/tempo ratios, quarter/half/wrap boundaries, signed offsets,
119
+ phase seeks (including signed integer neighbors and a retained native raw-clock
120
+ negative-tiny counterexample), held controls/precedence, tempo changes without restart, tiny-rate
121
+ state survival despite public quantization, restore, bounds, and instance isolation
122
+ at 44.1/48/96 kHz. Packed and hosted-browser acceptance belong to integration.
123
+
124
+ ## Verification boundary
125
+
126
+ `tests/musical-controls.spec.ts` performs native numerical and snapshot checks,
127
+ including 44.1/48/96 kHz control schedules and a 49,152-frame smallest-subnormal
128
+ free-rate probe at 48 kHz. The clock repair has its own independent all-capacity
129
+ regressions. Source typechecking is separate from packed-public-entry and hosted
130
+ browser proof; those integration checks are owned by the integration lane.
131
+ No human listening approval, approved golden, or runtime clearance is implied.
132
+
133
+ `tests/musical-controls-packed.test.mjs` packs the real package, installs it into
134
+ an isolated consumer with the pinned fixture lock, checks the actual public
135
+ TypeScript declarations, renders independent ADSR/LFO references at three rates,
136
+ and builds separate ADSR and four-wave LFO worklet/WASM artifacts. Its default
137
+ and CI path uses fresh `npm ci`. The explicit local-only
138
+ `DEN_MUSICAL_CONTROLS_REUSE_LOCKED_CONSUMER` alternative reuses an existing locked
139
+ dependency tree while extracting the newly packed target into a private directory;
140
+ it is rejected under CI and is not presented as fresh-install evidence. Reuse
141
+ records and rechecks the donor tarball, lock, installed target and full dependency
142
+ file identities. The native diagnostic includes input/output copies and reports
143
+ cold/startup/warm timing, zero memory growth and scrubbing, without runtime
144
+ clearance. Raw f32 evidence contains control signals, not an audio audition.
@@ -0,0 +1,255 @@
1
+ # Musical chains and original materials
2
+
3
+ Status: **CANDIDATE**. Four small, editable unworklet compositions address original
4
+ catalog groups 12 (concrete chains/blends) and 13 (sounds/configurations/materials).
5
+ Their A/B variants form a separate future listening group. None inherits the
6
+ initial five settings' listening feedback, golden status, or runtime clearance.
7
+ No original instrument or delay setting is changed.
8
+
9
+ ## Public use and native controls
10
+
11
+ Import the four processors, their plain parameter maps and host-side asset
12
+ builders from `@denaudio/den/musical-examples`. `src/musical-materials.ts` is an
13
+ implementation detail; no private `dist` import is needed. These are ordinary
14
+ `defineProcessor` declarations, native a-rate AudioParams, one native `load`
15
+ message per resident and existing same-schema state. There is no preset format,
16
+ parameter registry, MIDI/voice allocator, loader, routing or second runtime.
17
+
18
+ All processors emit stereo `main`. `shapedEcho` accepts mono audio input `main`.
19
+ The other three have native gate controls. A host can drive those AudioParams
20
+ from buttons, an existing keyboard/MIDI mapping or automation; this example does
21
+ not implement or claim that host input layer. Gates are high at values >=.5.
22
+ Supply finite controls inside the declared bounds. Native parameter descriptors
23
+ are not a sanitizer for arbitrary offline arrays, NaN or infinities.
24
+
25
+ Use the existing unworklet host API to create/connect/resume at **48 kHz** for
26
+ browser use. This lane's evidence is offline at 44.1/48/96 kHz; it does not establish
27
+ browser execution of these exact graphs, scheduling capacity, output-device
28
+ playback or hardware latency. Existing Vite worklets remain 48-kHz-specific.
29
+
30
+ ### Preloading and cleanup
31
+
32
+ `glassDyad` needs the complete `makeGlassDyadTable()` result: 384 mono f32 samples
33
+ comprising three consecutive 128-sample cycles, without duplicate endpoints.
34
+ `grainCloud` needs `makeGrainCloudSample()`: 4096 mono f32 frames at a declared
35
+ source rate of 48 kHz. Generate these arrays on the host, send one native
36
+ `load` message with `{ data }`, and keep gates down until the host has established
37
+ the message's delivery boundary. The examples do not add an acknowledgement or
38
+ loader. The deterministic offline fixtures deliver at quantum 0 and start gates
39
+ at sample 128 or later. Live hosts must establish their own boundary rather than
40
+ assuming that elapsed wall time proves delivery.
41
+
42
+ Loading copies into the resident during native message dispatch; it is bounded,
43
+ not a callback-free/zero-cost promise. Both inputs have capacity-16 message rings.
44
+ Missing/unloaded assets are silent. The wavetable also stays silent if fewer than
45
+ all 384 required frames are loaded. Replacing a table restarts both table phases
46
+ but does not retrigger their existing amplitude envelopes. Grain replacement
47
+ invalidates current grains; while gate stays high, new grains resume only on
48
+ later scheduled onsets, not an implicit sample-player restart. Neither replacement
49
+ crossfades. Keep gates down/reset for changes that must be inaudible.
50
+
51
+ An empty `load` logically unloads the resident; it does not securely erase buffer
52
+ bytes or old snapshots. Host disposal/disconnection uses existing unworklet APIs,
53
+ not a den-specific cleanup method. Tests establish source completion/reset/unload
54
+ silence, not browser-node destruction. Stop gates/input and leave time for the
55
+ stated tails before disconnecting, or use reset for an immediate discontinuous mute.
56
+
57
+ ## A. Glass Dyad
58
+
59
+ `glassDyad`, `glassDyadParameters`, `glassDyadBrightParameters`,
60
+ `glassDyadConstruction`, `makeGlassDyadTable`.
61
+
62
+ Two independently gated, independently tuned resident-wavetable voices share one
63
+ small table. Each uses the existing linear ADSR: 12-ms attack, 200-ms decay to
64
+ .65 sustain, 280-ms release. A fresh gate edge resets that voice's oscillator;
65
+ frequency edits preserve phase. Linear stereo weights are .75/.25 for voice A
66
+ and .25/.75 for B; this is not equal-power panning. Shared frame morph is linear.
67
+
68
+ - A: 220/330 Hz, morph frame .25, final gain .2.
69
+ - B: same notes/envelope/gain, initial morph 1.75 for a richer second/third harmonic.
70
+ - Bounds: each frequency 55–1200 Hz; frame 0–2; final gain 0–.3.
71
+ - Asset: frame weights `[.8]`, `[.6,.2]`, `[.4,.25,.15]` at harmonics 1–3.
72
+ The fixed absolute weight sum .8 bounds every table frame and interpolated read.
73
+ With both envelopes <=1 and per-channel pan weights summing to one, the ideal
74
+ per-channel output is <=.8 × gain, or .16 at the candidate gain. No limiter or
75
+ peak normalization is involved. Arbitrary replacement PCM invalidates this bound.
76
+ - No block/lookahead delay. Sine phase-zero and the attack make the initial sample
77
+ zero; that is waveform/envelope behavior, not claimed hardware latency.
78
+ - Gate-off reaches exact source silence within 280 ms (nearest f32-seconds frame
79
+ count). There is no extra FX tail. Reset consumes a held gate, so it stays
80
+ silent after reset release until a new gate edge.
81
+ - Fixed resources: two oscillators/envelopes, one 1536-byte PCM buffer and a
82
+ 24,576-byte ingress payload arena, plus native state/metadata/I/O.
83
+
84
+ The synthetic frames have only three specified low harmonics, but interpolation,
85
+ fast morph/frequency edits, reset and replacement are not guaranteed alias-free.
86
+ There are no automatic mipmaps or general bandlimiting.
87
+
88
+ ## B. FM Modal Hit
89
+
90
+ `fmModalHit`, `fmModalHitParameters`, `fmModalHitBellParameters`, `fmModalHitModes`.
91
+
92
+ A fixed A3/220-Hz FM body is linearly blended with an impulse-excited modal bank.
93
+ Gate rise latches strength, resets both FM phases, restarts the amplitude envelope,
94
+ and clears the old modal history before injecting one strength-scaled impulse.
95
+ This is intentional hard retrigger, not additive overlapping resonances. It keeps
96
+ the modal contribution inside the independently proved unit-impulse bound.
97
+
98
+ The FM amplitude has 1-ms attack, 180-ms decay to zero and 40-ms gate-off release.
99
+ Its instantaneous deviation follows that envelope; modulation feedback is zero.
100
+ Modes are 220 Hz/T60 .42 s/gain 1, 351 Hz/.24 s/.6 and 563 Hz/.15 s/.35, normalized
101
+ by the fixed sum of absolute mode gains. The modal pitches do not move when the
102
+ FM ratio/deviation changes.
103
+
104
+ - A: strength .8, modulator/carrier ratio 1.5, deviation 100 Hz, modal blend .35,
105
+ final gain .22. B: ratio 2.75, deviation 170 Hz, modal blend .7.
106
+ - Bounds: strength 0–1, ratio .5–4, deviation 0–180 Hz, modal blend 0–1, gain 0–.3.
107
+ - Strength is latched per hit, while ratio/deviation/blend/gain are live controls.
108
+ - The FM sine and normalized reset-per-hit modal impulse each stay within the
109
+ latched strength (small floating-point tolerance); their convex blend does too.
110
+ Candidate output bound is .8 × .22=.176. This reasoning does not apply to
111
+ arbitrary sustained modal excitation; this example injects a single impulse.
112
+ - Modal response begins on the strike sample. No block/lookahead buffer is added.
113
+ - Gate-off releases FM but does not truncate the modal tail. The slowest pole
114
+ envelope reaches -60 dB at .42 s after the last strike, and below -120 dB at
115
+ .84 s. Sampled peaks need not occur exactly there. This is an asymptotic tail,
116
+ not exact silence. Reset mutes immediately, consumes held gate and requires a
117
+ fresh edge to strike again. Hard repeated strikes may click.
118
+ - Fixed resources: one two-operator FM source, one envelope and three modal modes.
119
+
120
+ FM is positive-frequency/clamped FM, not through-zero FM or bandlimited FM. The
121
+ candidate deviation stays below the carrier; more general modulation still has
122
+ sidebands and no blanket alias-free guarantee.
123
+
124
+ ## C. Grain Cloud
125
+
126
+ `grainCloud`, `grainCloudParameters`, `grainCloudReverseParameters`,
127
+ `grainCloudConstruction`, `makeGrainCloudSample`.
128
+
129
+ Two fixed grain slots read an original periodic tone cluster. The 4096-frame
130
+ asset uses integer cycle counts 19/31/47 with weights .32/.17/.08, multiplied by
131
+ `.55−.45*cos(2πn/4096)`. It is analytic material, not sampled or downloaded audio.
132
+ The file's fixed formula and full PCM hashes are recorded in the evidence.
133
+
134
+ - A: position 512, jitter 240 source frames, speed 1, duration 90 ms, density 16 Hz,
135
+ gain .28. B: position 2800, speed −.75, duration 120 ms, density 12 Hz.
136
+ - Bounds: position/jitter 0–4095 source frames, speed −2–2, duration 20–150 ms,
137
+ density 0–20 Hz, final gain 0–.4.
138
+ - Source metadata is always 48 kHz. Readers apply the source/host rate ratio at
139
+ 44.1/48/96 kHz. Speed changes pitch and traversal; this is not time-stretching.
140
+ - Park–Miller seed 1741, two triangular-windowed grains, looped resident reads.
141
+ Position/jitter/rate/duration latch at grain onset. A full pool drops onsets.
142
+ A/B nominal density×duration=1.44, leaving room under two simultaneous grains;
143
+ arbitrary declared-bound combinations can saturate the pool and drop events.
144
+ - Table bound .57, windows<=1, sum divided by the two-slot capacity: ideal source
145
+ <=.57 and candidate final output <=.1596. This fixed divisor is explicit DSP,
146
+ not loudness normalization. Replacement PCM needs its own bound.
147
+ - Gate rise starts an endpoint-zero grain, so the first sample is zero. There is
148
+ no block latency. Grain attacks are part of each triangular window.
149
+ - Gate-off starts no further grains; exact silence follows within the largest
150
+ duration already latched, at most 150 ms. Reset clears grains and reseeds RNG.
151
+ Unlike Glass/Hit, a gate held high starts again on reset release. Empty unload
152
+ stays silent even under held gate.
153
+ - Fixed resources: two grains, one 16,384-byte resident PCM buffer and a 262,144-byte
154
+ ingress payload arena, plus native state/metadata/I/O. Maximum 32-grain capacity
155
+ is deliberately not used and receives no deadline clearance from this example.
156
+
157
+ ## D. Shaped Echo
158
+
159
+ `shapedEcho`, `shapedEchoParameters`, `shapedEchoDarkParameters`.
160
+
161
+ Finite mono input with magnitude<=1 passes through the existing soft first-order
162
+ ADAA drive, a Q=.5 TPT low-pass and the existing stereo ping-pong delay. The left
163
+ delay input receives the filtered signal, the right receives half of it, making
164
+ successive cross-feedback echoes exchange emphasis. A final explicit gain follows
165
+ both output channels. The supplied input is a .4-bound, enveloped 220/660-Hz phrase.
166
+
167
+ - A: drive 2.4, cutoff 1800 Hz, time 125 ms, feedback .25, wet blend .3, gain .2.
168
+ B: drive 4.5, cutoff 650 Hz, time 187.5 ms, wet blend .45; feedback/gain unchanged.
169
+ - Bounds: drive 0–8, cutoff 100–4000 Hz, time 40–250 ms, feedback 0–.4, mix 0–.6,
170
+ gain 0–.25. Delay capacity is fixed250 ms per channel.
171
+ - ADAA averages the nonlinear curve along adjacent driven samples. In its linear
172
+ regime that has a half-sample effective phase delay and high-frequency rolloff.
173
+ The module's own dry branch uses the same adjacent-sample alignment, although
174
+ this composition selects fully wet drive. This is not an integer half-sample
175
+ delay buffer or a constant broadband latency claim for a nonlinear signal.
176
+ - The filter adds frequency-dependent phase/group delay. Echo dry/wet is an
177
+ intentional mixture of current filtered signal and delayed history; there is
178
+ no external parallel-path latency compensation. Delay timing is f32 seconds
179
+ and fractional linear interpolation. Time edits have moving-head/Doppler
180
+ behavior and can click. No pitch-preserving crossfade is claimed.
181
+ - With fixed cutoff, the independent direct-form filter/echo oracle checks the
182
+ complete composition. Tests edit drive mid-phrase. The oracle intentionally
183
+ does not pretend that a static direct-form recurrence proves fast moving-TPT
184
+ coefficient equivalence. Wider modulation needs the module's own evidence and
185
+ renewed composition headroom checks.
186
+ - The chosen .5 peak guard means >6.02 dB measured headroom for fixtures; it is not
187
+ a universal safety theorem for arbitrary controls/history. No limiter or hidden
188
+ normalization is inserted. Reports include output measurements and explicitly
189
+ inferred pre-final-gain peak, not a fabricated raw measurement.
190
+ - At candidate feedback .25, each repeat's envelope falls by about 12.04 dB. Ten
191
+ repeats are below −120 dB relative to the first: 1.25/1.875 seconds at A/B times,
192
+ plus the source/filter tail. Feedback .4 and 250-ms times have longer decay.
193
+ Filter/feedback tails are asymptotic. Held reset mutes and clears history;
194
+ releasing it passes any currently present input through the cold chain.
195
+ - Fixed resources: one ADAA source-history pair, one SVF, two 250-ms f32 delay
196
+ buffers (`2*(ceil(rate*.25)+2)*4` bytes) and native scalar state.
197
+
198
+ ## Reproducible numerical and listening evidence
199
+
200
+ Run `npm ci`, `npm run check`, and the focused
201
+ `npm exec -- vitest run tests/musical-examples.spec.ts --maxWorkers=1`.
202
+ `node --test tests/musical-examples-packed.test.mjs` packs the real package,
203
+ installs it in an isolated locked consumer and type-checks only the public entry.
204
+ It renders 24 A/B cases across 44.1/48/96 kHz and writes a fresh timestamped
205
+ `artifacts/musical-examples/` directory. Existing evidence is never overwritten.
206
+
207
+ Each source/settings/tarball/lock/asset/expanded-parameter/input/PCM/WAV hash is
208
+ recorded. Every WAV is unnormalized stereo 32f with the stated gain, and every
209
+ render is repeated for bit-identical PCM. A variants at all three rates must
210
+ continue bit-exactly after same-schema snapshot restoration, without reloading
211
+ resident PCM and with the remaining host parameter timeline reapplied. Reset,
212
+ held reset, fresh restart, missing/short/unloaded resident and source completion
213
+ have separate regressions. Persistent DSP state does not save pending host
214
+ automation or create a portable/cross-rate preset.
215
+
216
+ Independent oracles use periodic linear interpolation plus piecewise closed-form
217
+ envelopes, Math.sin FM and analytic modal impulse sums, BigInt seeded grain
218
+ scheduling, and piecewise Simpson quadrature followed by a static bilinear filter
219
+ and an unbounded echo timeline. Independent expectations are not recorded audio.
220
+ Deliberately wrong audio, clipping and nonfinite output must fail the same guards.
221
+ Every render requires zero scrubbed/nonfinite/clipped samples and peak<.5, useful
222
+ nonzero output, and a final 100-ms tail peak<5e-6. Source finite-tail cases also
223
+ require exact silence. Instantiation/WASM sizes are diagnostics, not loaded
224
+ browser deadline measurements. No golden is generated or approved by these tests.
225
+
226
+ For later grouped listening, use the eight 48-kHz WAVs in order Glass A/B, Hit A/B,
227
+ Grain A/B, Echo A/B at a comfortable user-controlled playback level. Compare morph
228
+ clarity/stereo balance, struck body versus bell ring, forward versus reverse grain
229
+ motion, and bright versus dark echo articulation. Evaluate clicks and subjective
230
+ quality separately from numerical correctness. Any sound-affecting edit requires
231
+ new hashed audio. Human approval, if given later, must identify those exact hashes;
232
+ it is not inferred from module acceptance or feedback on the original sounds.
233
+
234
+ ### Authored fixture measurements
235
+
236
+ The initial three-rate packed run measured these maxima across each A/B pair:
237
+
238
+ | Example | Maximum output peak | Minimum measured headroom | Maximum independent sample error |
239
+ | --- | ---: | ---: | ---: |
240
+ | Glass Dyad | .150429115 | 16.45 dB | 1.50e-8 |
241
+ | FM Modal Hit | .155718207 | 16.15 dB | 2.24e-8 |
242
+ | Grain Cloud | .064521916 | 23.81 dB | 1.87e-9 |
243
+ | Shaped Echo | .183290541 | 14.74 dB | 1.50e-8 |
244
+
245
+ All had zero nonfinite, clipped or scrubbed samples. Glass/grain final tails were
246
+ exactly zero. The largest final 100-ms tail peak was 6.59e-8 for modal hit and
247
+ 2.22e-8 for echo. Paired variation must additionally have a difference-signal RMS
248
+ above 10% of A's RMS; unequal hashes alone are insufficient to call a variation
249
+ meaningful. This is a numerical distinction, not a subjective-quality score.
250
+
251
+ At 48 kHz, instantiated native memory was 64/64/320/128 KiB respectively; WASM
252
+ sizes were 23,272/48,172/8,904/34,874 bytes. These are fixed-graph allocation
253
+ observations only. They neither measure native message-copy cost nor establish
254
+ loaded worklet callback deadlines. Consult the exact-commit manifest/results when
255
+ reproducing, rather than promoting these summary numbers to a wider guarantee.