@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.
- package/CHANGELOG.md +63 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +107 -2
- package/dist/biquad-eq.d.ts +11 -0
- package/dist/biquad-eq.js +35 -0
- package/dist/catalog-filter-math.d.ts +4 -0
- package/dist/catalog-filter-math.js +30 -0
- package/dist/character-filter.d.ts +13 -0
- package/dist/character-filter.js +53 -0
- package/dist/convolution-spectrum.d.ts +25 -0
- package/dist/convolution-spectrum.js +82 -0
- package/dist/convolution.d.ts +17 -0
- package/dist/convolution.js +87 -0
- package/dist/crossover.d.ts +11 -0
- package/dist/crossover.js +17 -0
- package/dist/curve-shaper.d.ts +26 -0
- package/dist/curve-shaper.js +73 -0
- package/dist/delay-fx.d.ts +37 -0
- package/dist/delay-fx.js +84 -0
- package/dist/delay-readhead.d.ts +19 -0
- package/dist/delay-readhead.js +54 -0
- package/dist/delay-settings.d.ts +55 -0
- package/dist/delay-settings.js +20 -0
- package/dist/drive.d.ts +24 -0
- package/dist/drive.js +116 -0
- package/dist/dual-head-delay.d.ts +28 -0
- package/dist/dual-head-delay.js +77 -0
- package/dist/dynamics.d.ts +61 -0
- package/dist/dynamics.js +130 -0
- package/dist/envelope.d.ts +34 -0
- package/dist/envelope.js +62 -0
- package/dist/filter.d.ts +9 -0
- package/dist/filter.js +34 -0
- package/dist/filterbank-vocoder.d.ts +35 -0
- package/dist/filterbank-vocoder.js +52 -0
- package/dist/formant-bank.d.ts +15 -0
- package/dist/formant-bank.js +28 -0
- package/dist/freeze-reverb.d.ts +35 -0
- package/dist/freeze-reverb.js +61 -0
- package/dist/frequency-shifter.d.ts +24 -0
- package/dist/frequency-shifter.js +57 -0
- package/dist/gate.d.ts +2 -0
- package/dist/gate.js +12 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/instrument-example.d.ts +12 -0
- package/dist/instrument-example.js +29 -0
- package/dist/instrument-settings.d.ts +95 -0
- package/dist/instrument-settings.js +36 -0
- package/dist/instrument.d.ts +42 -0
- package/dist/instrument.js +166 -0
- package/dist/lfo.d.ts +19 -0
- package/dist/lfo.js +55 -0
- package/dist/live-buffer.d.ts +39 -0
- package/dist/live-buffer.js +52 -0
- package/dist/live-granular.d.ts +41 -0
- package/dist/live-granular.js +64 -0
- package/dist/lookahead-limiter.d.ts +31 -0
- package/dist/lookahead-limiter.js +67 -0
- package/dist/loop-crossfade.d.ts +28 -0
- package/dist/loop-crossfade.js +72 -0
- package/dist/modulation-fx.d.ts +47 -0
- package/dist/modulation-fx.js +58 -0
- package/dist/modulation.d.ts +132 -0
- package/dist/modulation.js +228 -0
- package/dist/mpe-expression.d.ts +36 -0
- package/dist/mpe-expression.js +73 -0
- package/dist/multiband-dynamics.d.ts +50 -0
- package/dist/multiband-dynamics.js +60 -0
- package/dist/musical-controls.d.ts +76 -0
- package/dist/musical-controls.js +125 -0
- package/dist/musical-examples.d.ts +21 -0
- package/dist/musical-examples.js +142 -0
- package/dist/musical-materials.d.ts +110 -0
- package/dist/musical-materials.js +56 -0
- package/dist/oscillator.d.ts +10 -0
- package/dist/oscillator.js +35 -0
- package/dist/oversampled-drive.d.ts +19 -0
- package/dist/oversampled-drive.js +99 -0
- package/dist/performance.d.ts +61 -0
- package/dist/performance.js +241 -0
- package/dist/pitch-quantizer.d.ts +26 -0
- package/dist/pitch-quantizer.js +88 -0
- package/dist/prepared-convolution.d.ts +26 -0
- package/dist/prepared-convolution.js +104 -0
- package/dist/resident-recorder.d.ts +28 -0
- package/dist/resident-recorder.js +68 -0
- package/dist/resident-time-stretch.d.ts +46 -0
- package/dist/resident-time-stretch.js +136 -0
- package/dist/resonator.d.ts +32 -0
- package/dist/resonator.js +63 -0
- package/dist/reverb.d.ts +27 -0
- package/dist/reverb.js +52 -0
- package/dist/sample.d.ts +113 -0
- package/dist/sample.js +212 -0
- package/dist/source.d.ts +58 -0
- package/dist/source.js +113 -0
- package/dist/spatial-chains.d.ts +50 -0
- package/dist/spatial-chains.js +76 -0
- package/dist/spectral-fft.d.ts +27 -0
- package/dist/spectral-fft.js +84 -0
- package/dist/spectral-freeze.d.ts +24 -0
- package/dist/spectral-freeze.js +74 -0
- package/dist/spectral-gate.d.ts +23 -0
- package/dist/spectral-gate.js +58 -0
- package/dist/spectral-stft.d.ts +14 -0
- package/dist/spectral-stft.js +70 -0
- package/dist/spectral-texture.d.ts +46 -0
- package/dist/spectral-texture.js +141 -0
- package/dist/spectral.d.ts +18 -0
- package/dist/spectral.js +10 -0
- package/dist/state-variable-filter.d.ts +17 -0
- package/dist/state-variable-filter.js +25 -0
- package/dist/stereo-delay.d.ts +45 -0
- package/dist/stereo-delay.js +70 -0
- package/dist/virtual-analog.d.ts +26 -0
- package/dist/virtual-analog.js +66 -0
- package/dist/voice-policy.d.ts +37 -0
- package/dist/voice-policy.js +245 -0
- package/dist/wavetable-bands.d.ts +37 -0
- package/dist/wavetable-bands.js +145 -0
- package/dist/wavetable-read.d.ts +6 -0
- package/dist/wavetable-read.js +11 -0
- package/dist/wavetable.d.ts +29 -0
- package/dist/wavetable.js +49 -0
- package/dist/windowed-pitch-shift.d.ts +34 -0
- package/dist/windowed-pitch-shift.js +51 -0
- package/docs/catalog-audition.md +76 -0
- package/docs/catalog-filters.md +125 -0
- package/docs/catalog-modulation-fx.md +220 -0
- package/docs/catalog-status.md +433 -0
- package/docs/character-filter.md +139 -0
- package/docs/contracts.md +141 -0
- package/docs/control-modulation.md +194 -0
- package/docs/convolution.md +50 -0
- package/docs/curve-shaper.md +117 -0
- package/docs/delay-fx-capture-findings.json +134 -0
- package/docs/delay-fx.md +202 -0
- package/docs/delay-readhead.md +121 -0
- package/docs/delay-settings-candidate.json +333 -0
- package/docs/delay-settings.md +111 -0
- package/docs/dependency-findings.md +58 -0
- package/docs/deployment-status.md +37 -0
- package/docs/drive.md +152 -0
- package/docs/dual-head-delay.md +131 -0
- package/docs/dynamics.md +180 -0
- package/docs/envelope-integer-staging.md +64 -0
- package/docs/filter-materialization-results.json +71 -0
- package/docs/filter-materialization.md +135 -0
- package/docs/filter-oscillator.md +126 -0
- package/docs/filterbank-vocoder-entry.md +58 -0
- package/docs/freeze-reverb.md +179 -0
- package/docs/frequency-shifter.md +133 -0
- package/docs/frontend-audition.md +22 -0
- package/docs/gain-schedule-verification.md +53 -0
- package/docs/initial-acceptance.md +79 -0
- package/docs/instrument-performance.md +191 -0
- package/docs/instrument-settings.md +130 -0
- package/docs/instrument.md +204 -0
- package/docs/integration-candidate.md +114 -0
- package/docs/live-buffer.md +163 -0
- package/docs/live-granular.md +179 -0
- package/docs/lookahead-multiband.md +183 -0
- package/docs/loop-crossfade.md +99 -0
- package/docs/mpe-expression.md +65 -0
- package/docs/musical-clock-seek-boundary.md +31 -0
- package/docs/musical-controls-contract.md +144 -0
- package/docs/musical-examples.md +255 -0
- package/docs/oversampled-drive.md +153 -0
- package/docs/performance.md +53 -0
- package/docs/pitch-quantizer.md +55 -0
- package/docs/prepared-convolution.md +197 -0
- package/docs/realtime-audition.md +63 -0
- package/docs/resident-recorder.md +151 -0
- package/docs/resident-time-stretch.md +196 -0
- package/docs/sample.md +191 -0
- package/docs/sound-audition-headroom.md +103 -0
- package/docs/sources.md +81 -0
- package/docs/spatial-chains-entry.md +251 -0
- package/docs/spectral-freeze-entry.md +132 -0
- package/docs/spectral-gate-entry.md +134 -0
- package/docs/spectral-large-candidate.md +81 -0
- package/docs/spectral-texture-entry.md +178 -0
- package/docs/spectral.md +127 -0
- package/docs/virtual-analog.md +77 -0
- package/docs/voice-policy.md +38 -0
- package/docs/wavetable-bands.md +138 -0
- package/docs/wavetable.md +101 -0
- package/docs/windowed-pitch-shift.md +112 -0
- 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.
|