@denaudio/den 0.0.0-stage → 0.1.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 +17 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +102 -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 +15 -0
- package/dist/convolution.js +81 -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 +22 -0
- package/dist/prepared-convolution.js +99 -0
- package/dist/resident-recorder.d.ts +28 -0
- package/dist/resident-recorder.js +68 -0
- package/dist/resident-time-stretch.d.ts +44 -0
- package/dist/resident-time-stretch.js +135 -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 +48 -0
- package/dist/spatial-chains.js +76 -0
- package/dist/spectral-fft.d.ts +21 -0
- package/dist/spectral-fft.js +74 -0
- package/dist/spectral-freeze.d.ts +22 -0
- package/dist/spectral-freeze.js +74 -0
- package/dist/spectral-gate.d.ts +21 -0
- package/dist/spectral-gate.js +58 -0
- package/dist/spectral-stft.d.ts +13 -0
- package/dist/spectral-stft.js +69 -0
- package/dist/spectral-texture.d.ts +42 -0
- package/dist/spectral-texture.js +141 -0
- package/dist/spectral.d.ts +16 -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 +431 -0
- package/docs/character-filter.md +139 -0
- package/docs/contracts.md +131 -0
- package/docs/control-modulation.md +194 -0
- package/docs/convolution.md +45 -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 +61 -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 +188 -0
- package/docs/realtime-audition.md +63 -0
- package/docs/resident-recorder.md +151 -0
- package/docs/resident-time-stretch.md +190 -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 +250 -0
- package/docs/spectral-freeze-entry.md +130 -0
- package/docs/spectral-gate-entry.md +130 -0
- package/docs/spectral-large-candidate.md +74 -0
- package/docs/spectral-texture-entry.md +174 -0
- package/docs/spectral.md +118 -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
package/docs/delay-fx.md
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Stereo delay FX — GEN-618 / GEN-620
|
|
2
|
+
|
|
3
|
+
One `delayFx` unworklet subgraph composes the existing readhead, low-pass and LFO.
|
|
4
|
+
It has no loader, parameter registry, state codec, routing framework, extra effect
|
|
5
|
+
engine or FFT. Call `tick(left, right, controls)` once per sample. Audio and numeric
|
|
6
|
+
controls are finite f32 nodes; sync/bypass/reset are boolean nodes. Duplicate mono
|
|
7
|
+
input explicitly. State is independent per engine/channel; there is no crossfeed.
|
|
8
|
+
|
|
9
|
+
Configuration is immutable: `sampleRate` (integer 8000–192000, from processor ctx),
|
|
10
|
+
`maxDelaySeconds` (default 8), `tone` (`lowpass`, default, or `flat`) and
|
|
11
|
+
`stereoPhaseCycles` (default 0.5, wrapped modulo one). Eight seconds is a provisional
|
|
12
|
+
construction default covering a whole note at 30 BPM, not a product ceiling.
|
|
13
|
+
Allocation is two readhead buffers of `ceil(rate * capacity) + 2` f32 samples, plus
|
|
14
|
+
scalar filter/LFO/control state. At 96 kHz the default buffer storage is 6,144,016
|
|
15
|
+
bytes; callers own the memory budget. No audio-thread resize occurs.
|
|
16
|
+
|
|
17
|
+
## Controls and sound
|
|
18
|
+
|
|
19
|
+
| Control | Unit / policy |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| timeLeftSeconds / timeRightSeconds | Independent base delays when sync is false; minimum one sample, maximum construction capacity. |
|
|
22
|
+
| sync, bpm, beatsLeft / beatsRight | Sync uses `60 * beats / bpm`; one beat is a quarter note. BPM domain 30–300; positive fractional beats express subdivisions, triplets and dotted values. No independent beat-count product ceiling; actual time must fit capacity. |
|
|
23
|
+
| feedback | Linear multiplier, clamped to [0,0.95]. Positive repeats only. |
|
|
24
|
+
| cutoffHz | Feedback tone cutoff clamped to [20,min(20000,0.24*rate)]; Q is fixed at 0.5. Flat mode bypasses tone in the feedback path. |
|
|
25
|
+
| mix | Linear dry/wet crossfade, clamped to [0,1]. No equal-power boost, limiter or normalization. |
|
|
26
|
+
| rateHz / depthSeconds | Shared LFO module, rate clamped to [0,20], depth [0,0.05]. Left reset phase 0, right reset phase stereoPhaseCycles. Both initialized on the first tick. |
|
|
27
|
+
| bypass | Unity dry, stop feeding new input, keep the internal feedback/tone/LFO running. Unbypass may reveal remaining tail. |
|
|
28
|
+
| reset | Clear logical delay history/pointers, filter history and LFO phases at this sample; new input at this sample begins new history. Dry contribution remains audible. Held reset silences wet. |
|
|
29
|
+
|
|
30
|
+
A read happens before the current input write. Per channel,
|
|
31
|
+
`wet[n] = delayRead(history, time[n])` and
|
|
32
|
+
`write[n] = admittedInput[n] + feedback[n] * tone(wet[n])`.
|
|
33
|
+
There is no additional sample in the feedback loop. The first repeat is unfiltered;
|
|
34
|
+
the tone shapes each subsequent circulation. Input stop (zeros) preserves decay.
|
|
35
|
+
Bypass and rejected timing admit zero new input. Reset is independent of bypass.
|
|
36
|
+
|
|
37
|
+
Time, tempo and stereo-tap edits move the existing readheads immediately. There
|
|
38
|
+
is no crossfade or smoothing: abrupt changes can click, skip/repeat source samples,
|
|
39
|
+
and cause Doppler pitch motion. LFO modulation uses the same moving-head behavior;
|
|
40
|
+
right phase 0.5 gives opposite motion, while phase 0 and equal taps preserve dual
|
|
41
|
+
mono. Dry/wet or bypass steps can also click. These discontinuities are explicit
|
|
42
|
+
policies, not a claim of sound approval. No arbitrary audio-rate modulation
|
|
43
|
+
anti-alias guarantee is made. GEN-621's two musical settings remain separate.
|
|
44
|
+
|
|
45
|
+
## Capacity rejection and clipping
|
|
46
|
+
|
|
47
|
+
Both requested base times must fit the f32-representable bounds. In sync mode BPM
|
|
48
|
+
must also be in range. A bad stereo request (including a too-short/nonpositive
|
|
49
|
+
base time) sets `timingRejected=true`, emits unity dry on both channels and admits
|
|
50
|
+
zero new input. Internally, both previous accepted base times are retained so the
|
|
51
|
+
old tail continues. On reset, rejected times fall back to one sample with cleared
|
|
52
|
+
history. A later valid request is accepted immediately with the same moving-head
|
|
53
|
+
transition policy. There is no silently clamped rhythmic playback.
|
|
54
|
+
|
|
55
|
+
LFO depth is applied after an accepted base time. Final modulation saturates at
|
|
56
|
+
the buffer bounds and sets `modulationClipped=true` when clipping occurs. This
|
|
57
|
+
flag is separate from invalid base timing. Consumers should display rejection
|
|
58
|
+
and clipping using existing unworklet events/state if desired; no new transport
|
|
59
|
+
is supplied here. Runtime sample-rate-specific compilation is unchanged: browser
|
|
60
|
+
acceptance is 48 kHz only; 44.1/48/96 kHz offline coverage is separate.
|
|
61
|
+
|
|
62
|
+
## Feedback bound, including cutoff modulation
|
|
63
|
+
|
|
64
|
+
A fixed-frequency low-pass magnitude bound alone does not prove a time-varying
|
|
65
|
+
loop stable. For the shared SVF with Q=0.5, let `g=tan(pi*cutoff/rate)`, and express
|
|
66
|
+
its stored state in coordinates `p=band+low`, `q=low`. The same equations are:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
u = (p + g*x)/(1+g)
|
|
70
|
+
y = (q + g*u)/(1+g)
|
|
71
|
+
pNext = r*p + (1-r)*x
|
|
72
|
+
qNext = r*q + (1-r)*u
|
|
73
|
+
r = (1-g)/(1+g)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For `0<g<1`, every expression is a convex combination. The chosen 0.24*rate cap
|
|
77
|
+
keeps g below one with margin for the shared coefficient approximation. This
|
|
78
|
+
property holds under sample-by-sample cutoff changes, unlike a stationary-only
|
|
79
|
+
frequency response argument. Linear readhead interpolation is also convex, and
|
|
80
|
+
independent stereo channels do not add energy to each other. If input magnitude
|
|
81
|
+
is at most A and history starts clear, the common amplitude invariant is
|
|
82
|
+
`A/(1-0.95) = 20*A` for delay storage, tone coordinates and wet output, apart from
|
|
83
|
+
floating-point rounding. Thus arbitrary admitted time/cutoff modulation cannot
|
|
84
|
+
create unbounded self-oscillation under these control bounds. Feedback <1, finite
|
|
85
|
+
capacity and strictly positive bounded cutoff also give eventual decay after
|
|
86
|
+
input stops; the tests include the slowest-cutoff/highest-gain tail. There is no
|
|
87
|
+
threshold tail kill added by den; pinned unworklet flushes tiny values (<1e-30).
|
|
88
|
+
Flat tone has the same loop bound without the filter stage. At DC, unity input
|
|
89
|
+
can accumulate to 20; users must gain-stage the output. No hidden compensation
|
|
90
|
+
conceals this behavior.
|
|
91
|
+
|
|
92
|
+
## State and composition cost
|
|
93
|
+
|
|
94
|
+
Readhead history remains a logical reset, not a physical memory wipe. Persistent
|
|
95
|
+
slots are inherited readhead/filter/LFO state plus `first`, `acceptedLeft` and
|
|
96
|
+
`acceptedRight`. Restore is supported only for the same schema, configuration and
|
|
97
|
+
rate, using unworklet's existing snapshot path after parameters have rendered.
|
|
98
|
+
|
|
99
|
+
Seven transient f32 scalar slots and one transient boolean materialize LFO
|
|
100
|
+
outputs, final times, wet reads, cutoff and timing validity within each sample.
|
|
101
|
+
The accepted-time state is read after its current-sample write, avoiding repeated
|
|
102
|
+
expansion of the same selection. They are overwritten before use and add no sample latency.
|
|
103
|
+
They prevent recursive expansion of the composite expression graph in pinned
|
|
104
|
+
unworklet 0.4.1; no compiler/runtime replacement or upstream edit is introduced.
|
|
105
|
+
|
|
106
|
+
## Evidence
|
|
107
|
+
|
|
108
|
+
`tests/delay-fx.spec.ts` compares against an unbounded written-input timeline,
|
|
109
|
+
independent direct-form bilinear biquad and (for time-varying cutoff) two one-pole
|
|
110
|
+
cascade coordinates. It does not copy the circular storage or SVF recurrence.
|
|
111
|
+
It checks repeat positions/amplitudes, fractional taps, gain bounds, filtering,
|
|
112
|
+
bypass/reset/restart, stereo/mono/mix endpoints, tempo acceptance/rejection,
|
|
113
|
+
modulation clipping, discontinuity policy and same-schema continuation at all
|
|
114
|
+
three rates. Ordinary references allow 3e-6 absolute error, chorus/variable tone
|
|
115
|
+
4e-6. Wrong waveforms fail. Extreme LFO-depth/cutoff modulation additionally checks
|
|
116
|
+
the derived common amplitude invariant, separately from sample comparisons to
|
|
117
|
+
avoid treating tiny sine-polynomial errors amplified by seconds-to-samples
|
|
118
|
+
conversion as filter recurrence errors. Each long-tail test runs 2^20 samples
|
|
119
|
+
(10.9–23.8 seconds depending on rate), with bounded peak and final tail <1e-6.
|
|
120
|
+
|
|
121
|
+
`tests/delay-fx-packed.test.mjs` installs the actual tarball into an isolated locked
|
|
122
|
+
consumer, strictly checks declarations, compares rendered feedback/tone against
|
|
123
|
+
a separate direct-form reference, builds the standard unworklet browser path and
|
|
124
|
+
exercises mix/bypass/reset/capacity rejection and snapshot restore at 48 kHz.
|
|
125
|
+
|
|
126
|
+
The sustained browser gate records 2^19 frames (10.92 seconds) downstream of the
|
|
127
|
+
worklet: actual input plus left/right output, with four seconds of sustained
|
|
128
|
+
220-Hz input and more than six seconds of tail. `capture.ts` is a fixed test fixture
|
|
129
|
+
built with existing unworklet buffers, audio ports and snapshots. It writes three
|
|
130
|
+
channels on the audio thread, emits silence, and reads its snapshot after suspend.
|
|
131
|
+
A guard quantum protects the requested prefix after recording fills. The frame
|
|
132
|
+
counter must cover the entire requested prefix. No custom loader, transport or
|
|
133
|
+
capture framework is added; the product processor is unchanged.
|
|
134
|
+
|
|
135
|
+
Every captured output sample is compared to an independent reference driven by
|
|
136
|
+
captured input at the unchanged 6e-6 threshold. Input continuity retains its 1e-6
|
|
137
|
+
sine-recurrence threshold and exact four-second span. A ten-second native source
|
|
138
|
+
control always runs, including its full sustained span; all three captured control
|
|
139
|
+
channels must be identical. There are no passing-retry selection or skipped
|
|
140
|
+
startup samples. Raw PCM and source/hash manifests are retained. This verifies
|
|
141
|
+
graph-sample continuity, not hardware-output delivery or scheduler deadlines.
|
|
142
|
+
|
|
143
|
+
The original ScriptProcessor apparatus produced false discontinuities. In one
|
|
144
|
+
simultaneous recording, internal input was continuous and internal outputs matched
|
|
145
|
+
the independent reference (left 7.45e-9, right 0), while external capture first
|
|
146
|
+
diverged at sample 73728 and lost 640 source samples. A separate internal-only run
|
|
147
|
+
had the same reference accuracy. Another saved tail failure started exactly at
|
|
148
|
+
capture block 393216: shifting the reference by 128 samples restored right-channel
|
|
149
|
+
bit equality and left error below 5.83e-11. The source was already silent, so a
|
|
150
|
+
short native-source control could not diagnose that tail capture loss. This is
|
|
151
|
+
why the baseline now sustains for ten seconds. See `delay-fx-capture-findings.json`
|
|
152
|
+
for numerical results and hashes; raw evidence and diagnostic fixtures are kept
|
|
153
|
+
in `artifacts/delay-fx/capture-investigation/`. The original failures are preserved,
|
|
154
|
+
not converted into passing comparisons by alignment or tolerance changes.
|
|
155
|
+
|
|
156
|
+
Deadline safety remains a separate unresolved question. Real AudioWorklet traces
|
|
157
|
+
have included startup and non-startup CPU/wall-time budget exceedances even after
|
|
158
|
+
expression materialization; replacing the recorder does not clear those findings.
|
|
159
|
+
No listening approval is inferred from corrected waveform capture.
|
|
160
|
+
|
|
161
|
+
`performance.mjs` separately measures the existing driver with byte-identical
|
|
162
|
+
packed browser WASM. It records size/hash, controls, warm-up, mean/p99/max block
|
|
163
|
+
time and total wall time for 12 seconds of audio. This host-dependent direct-WASM
|
|
164
|
+
diagnostic excludes browser scheduling/copy overhead and does not replace the
|
|
165
|
+
real-time gate. After the composition audit, WASM decreased from 62,653 to 45,935 bytes. Local
|
|
166
|
+
direct-driver mean decreased from 0.366 to 0.071 ms per 128 frames. The latter run
|
|
167
|
+
still had a 4.677 ms maximum versus a 2.667 ms budget; average cost alone does not
|
|
168
|
+
establish real-time readiness. Against commit 6cf15d1, all four output/status
|
|
169
|
+
channels were bit-identical over 18 cases: 44.1/48/96 kHz, flat/lowpass, and LFO
|
|
170
|
+
rate/depth pairs 0/0, 4 Hz/2 ms, 12 Hz/20 ms. Each case rendered 16384 samples with
|
|
171
|
+
sample-varying time, cutoff, gain, mix, sync, bypass and reset. This equivalence
|
|
172
|
+
check supplements the independent numerical references; it is not a golden. That
|
|
173
|
+
composition audit changed no shared module or unworklet source; no general
|
|
174
|
+
real-time performance guarantee is made.
|
|
175
|
+
|
|
176
|
+
The subsequent main integration includes the filter materialization described in
|
|
177
|
+
`filter-materialization.md`. It preserves the persistent schema and tiny-value
|
|
178
|
+
behavior while reducing this consumer's WASM to 24,707 bytes (SHA-256
|
|
179
|
+
`74ab305c15c932d3d7bc32e7d4a0499439adfdfb107355f329e46d1ffea9fdb0`).
|
|
180
|
+
The prior 18-case comparison against the 7ef7ce6 delay engine also found identical
|
|
181
|
+
complete snapshot bytes, and bidirectional restored continuation matched continuous
|
|
182
|
+
rendering with an ignored-restore negative control. The delay engine, readhead and
|
|
183
|
+
LFO sources are unchanged by this main integration.
|
|
184
|
+
|
|
185
|
+
The exact same generated WASM is covered by the recorded four cold candidate
|
|
186
|
+
trials in `filter-materialization-results.json` (first callback wall time
|
|
187
|
+
1.659–1.900 ms). Reusing matching source/WASM hashes avoids another timing sample;
|
|
188
|
+
these local measurements are not universal deadline clearance or evidence of a
|
|
189
|
+
hot-loop speedup. Non-first callback outliers, native-control observations and
|
|
190
|
+
clock anomalies remain separate limitations. The full numerical/packed suite also
|
|
191
|
+
checks the shared audition consumer introduced on main.
|
|
192
|
+
|
|
193
|
+
`artifacts/delay-fx/` contains CANDIDATE WAVs/raw browser PCM, static plots and a
|
|
194
|
+
manifest linking source hashes, exact settings/input, rates, dependency locks,
|
|
195
|
+
package tarball and verification results. No golden is approved or updated.
|
|
196
|
+
|
|
197
|
+
## Public integration
|
|
198
|
+
|
|
199
|
+
`@denaudio/den/delay-fx` exports `delayFx`, `DelayFxConfig` and `DelayFxControls`.
|
|
200
|
+
The isolated consumer imports that public subpath. The engine fixture remains
|
|
201
|
+
separate from the Chorus/Rhythmic setting fixtures and the complete integrated
|
|
202
|
+
site. Shared exports/package/lock/contracts and the UI retain one integration owner.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Delay readhead — GEN-615
|
|
2
|
+
|
|
3
|
+
`src/delay-readhead.ts` supplies `delayReadhead` and `DelayReadheadConfig`.
|
|
4
|
+
It is a mono unworklet subgraph, with no feedback, filter, stereo, mix, or tempo
|
|
5
|
+
policy. Instantiate with a required config object and a unique stable name:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
const cell = instantiate(delayReadhead, {
|
|
9
|
+
sampleRate: ctx.sampleRate,
|
|
10
|
+
maxDelaySeconds: 8,
|
|
11
|
+
}, { name: 'delayLeft' });
|
|
12
|
+
// Once per sample, inside forSample:
|
|
13
|
+
const { output, outOfRange } = cell.tick(input, delaySeconds, reset);
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
For same-sample feedback or other processing of the delayed output, use the
|
|
17
|
+
read-before-write boundary instead of `tick`:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
const tap = cell.read(delaySeconds, reset);
|
|
21
|
+
tap.write(input.add(tap.output.mul(0.5)));
|
|
22
|
+
output.write(tap.output);
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Each sample must use exactly one `read` followed by exactly one call to **that
|
|
26
|
+
read's** `write`, or exactly one `tick`; do not combine the two forms on one instance.
|
|
27
|
+
`read` captures the current delayed output and reset-adjusted pointers. Its `write`
|
|
28
|
+
commits the current input and advances history once. This allows
|
|
29
|
+
`writeInput[n] = input[n] + gain * delayedOutput[n]`, with no previous-output bridge
|
|
30
|
+
and no extra circulation latency. The returned object and closure exist only while
|
|
31
|
+
constructing the unworklet graph, not during audio processing. `tick` delegates to
|
|
32
|
+
the same read/write pair. Reset masks old history before the read, then the write
|
|
33
|
+
starts new history at the reset sample. The minimal feedback test uses a closed-form
|
|
34
|
+
one-sample-delay impulse `[0, 1, 0.5, 0.25, ...]`, including reset and isolation at
|
|
35
|
+
all three offline rates; it does not implement the dependent feedback engine.
|
|
36
|
+
|
|
37
|
+
`input` / `delaySeconds` are `Node<'f32'>`; `reset` and `outOfRange` are
|
|
38
|
+
`Node<'bool'>`. Input audio must be finite. The sample rate must be the enclosing
|
|
39
|
+
processor's compile-time rate. Use existing AudioParams/events and snapshot APIs;
|
|
40
|
+
this module introduces no control, routing, buffer, or persistence framework.
|
|
41
|
+
|
|
42
|
+
## Capacity and timeline
|
|
43
|
+
|
|
44
|
+
Construction allocates `ceil(sampleRate * maxDelaySeconds) + 2` f32 samples.
|
|
45
|
+
The default is two seconds, configurable at construction, with no resize during
|
|
46
|
+
processing. Eight seconds is covered by tests at all three offline rates. Capacity
|
|
47
|
+
must include at least one sample and fit signed i32 indexing; allocation is also
|
|
48
|
+
subject to the host's memory budget. These are representation constraints, not a
|
|
49
|
+
musical/product duration limit. Each instance has independent history and pointers.
|
|
50
|
+
|
|
51
|
+
At frame `n`, delay `d` in samples produces the linear interpolation of the input
|
|
52
|
+
at timeline position `n-d`. Reads precede the write of input `n`; one sample is the
|
|
53
|
+
minimum delay, and zero is not a dry feedthrough. For `d=k+f`, the weights are
|
|
54
|
+
`(1-f) * x[n-k] + f * x[n-k-1]`. Both adjacent integer indices wrap independently.
|
|
55
|
+
No fractional ring-coordinate subtraction is performed in f32: seconds are widened
|
|
56
|
+
to f64 before rate conversion, and the fractional weight is finally cast to f32.
|
|
57
|
+
This avoids losing small fractional offsets near the end of a long ring.
|
|
58
|
+
|
|
59
|
+
Finite times saturate to `[1/sampleRate, maxDelaySeconds]`. `outOfRange` reports
|
|
60
|
+
requests outside the **f32-representable seconds endpoints**, allowing an AudioParam
|
|
61
|
+
set to either endpoint without a spurious warning caused by f32 conversion. NaN
|
|
62
|
+
and negative infinity use the minimum; positive infinity uses the maximum, all
|
|
63
|
+
with `outOfRange=true`. Saturation ensures safe reads but does **not** establish
|
|
64
|
+
correct rhythmic playback. A later tempo consumer must use the flag to reject or
|
|
65
|
+
report unavailable times; it must not silently claim that a clamped rhythm is exact.
|
|
66
|
+
|
|
67
|
+
## Acoustic choice
|
|
68
|
+
|
|
69
|
+
| Candidate | Time change behavior | Decision |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| One continuously moving head, linear interpolation | Read velocity is `1 - delta(delaySamples)`; changing time bends pitch. Abrupt jumps can click or repeat/skip source material. | Selected: one explicit trajectory for modulation and steps, no hidden smoothing or extra transition state. |
|
|
72
|
+
| Two stationary heads crossfaded after a change | Avoids the sustained pitch glide but overlaps two different source times; correlated content can comb or cancel. Requires a fade duration and a policy for changes during a fade. | Deferred; does not express the selected moving-head modulation behavior. |
|
|
73
|
+
| Higher-order interpolation on a moving head | Retains Doppler behavior with a different high-frequency response; more taps and possible overshoot. | Not needed for this initial bounded two-tap readhead; a future quality change requires new evidence. |
|
|
74
|
+
|
|
75
|
+
Linear interpolation is not spectrally transparent: for fractional weight `f`,
|
|
76
|
+
`H(w)=(1-f)+f*exp(-jw)` apart from integer delay. At half-sample offset its magnitude
|
|
77
|
+
is `abs(cos(w/2))`, reaching zero at Nyquist. Nonnegative weights sum to one, so it
|
|
78
|
+
cannot overshoot the extrema of its two finite inputs. Arbitrary fast modulation
|
|
79
|
+
can alias; no anti-aliasing or click-free transition guarantee is made. Abrupt
|
|
80
|
+
controls take effect at their sample with no smoothing. A delay slope of +1 holds
|
|
81
|
+
the source position, and -1 doubles its traversal rate; analytic tests verify both.
|
|
82
|
+
Human sound evaluation remains a later batched instrument/FX step.
|
|
83
|
+
|
|
84
|
+
## Reset and state
|
|
85
|
+
|
|
86
|
+
Reset applies before the read at the dispatched sample. It resets the logical
|
|
87
|
+
write pointer and valid-history count, producing zero immediately. The current
|
|
88
|
+
input is then written as the first sample of the new history. Held reset keeps
|
|
89
|
+
output silent. There is no pre-reset contribution to either fractional tap.
|
|
90
|
+
|
|
91
|
+
This is a constant-time **logical history clear**, not a physical memory wipe.
|
|
92
|
+
Pinned unworklet 0.4.1 has no buffer-clear DSL operation. Old storage bytes can
|
|
93
|
+
remain in a snapshot until overwritten but are unreadable by this readhead; this
|
|
94
|
+
is not a data-erasure API. Reset may click. `history` (persistent f32 buffer),
|
|
95
|
+
`cursor` (i32), and `valid` (i32) use unworklet state with stable instance prefixes.
|
|
96
|
+
Snapshot continuation is supported only with the same schema, capacity, and rate.
|
|
97
|
+
The tests restore history, compare continuation, and prove reset cannot resurrect it.
|
|
98
|
+
|
|
99
|
+
## Evidence and integration
|
|
100
|
+
|
|
101
|
+
`tests/delay-readhead.spec.ts` uses an unbounded input-timeline reference, independent
|
|
102
|
+
of ring storage, and analytic impulse/pitch expectations. Tests cover minimum and
|
|
103
|
+
maximum, fractional wrap, steps, modulation, invalid controls, state isolation,
|
|
104
|
+
reset across block/wrap boundaries, and same-schema continuation. Sample comparisons
|
|
105
|
+
use absolute tolerance 2e-6 (4e-6 for analytic pitch tests); scrubbing must remain zero.
|
|
106
|
+
The fixture rejects an intentionally wrong waveform. No audio golden is used.
|
|
107
|
+
|
|
108
|
+
`tests/delay-packed.test.mjs` packs the package, installs it into an isolated locked
|
|
109
|
+
consumer, checks declarations strictly, renders offline at 44100/48000/96000, and
|
|
110
|
+
builds and renders Chromium at **48000 only**. It imports the public
|
|
111
|
+
`@denaudio/den/delay-readhead` subpath. It neither
|
|
112
|
+
imports repository source nor edits an installed dependency. The existing entry
|
|
113
|
+
test retains the other-rate browser rejection probes.
|
|
114
|
+
|
|
115
|
+
Artifacts in `artifacts/delay/` are CANDIDATE: raw WAVs, static waveforms, the packed
|
|
116
|
+
tarball/consumer lock, browser samples, and a manifest linking source/settings/input
|
|
117
|
+
and file hashes. CI uploads these through the existing artifact step.
|
|
118
|
+
|
|
119
|
+
The integration owner exposes `./delay-readhead` with its declarations and ESM
|
|
120
|
+
implementation. The packed fixture exercises that public import; package/lock,
|
|
121
|
+
shared consumers and shared contracts stay under one integration owner.
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
{
|
|
2
|
+
"status": "CANDIDATE",
|
|
3
|
+
"approval": null,
|
|
4
|
+
"previousApprovedAudio": null,
|
|
5
|
+
"description": "Initial reference candidates, not approved golden. No normalization or limiter.",
|
|
6
|
+
"sourceCommit": "ace4deafaf41f8c4336df7fdb532bbac10a6be42",
|
|
7
|
+
"sourceDirty": false,
|
|
8
|
+
"sourceFiles": {
|
|
9
|
+
"src/delay-settings.ts": "fb7a8ec25889514d9fd75a883c55afe58d2ffad412eb2c35918dd9ad8aa1d626",
|
|
10
|
+
"src/delay-fx.ts": "1df28860ffee67b594088ece185a81827fec90abb2acd81199ea90df047c420a",
|
|
11
|
+
"src/delay-readhead.ts": "b859b816bdccb9e638a79f07c8084761fb4f951a943ca2d48fb962162477ce38",
|
|
12
|
+
"src/filter.ts": "61d49d6b1e52b17791237e00dec5c5089e594ee0030ecaa37f2c632ecb441278",
|
|
13
|
+
"src/lfo.ts": "a2d73e0c389b91b62e3e833635dc7406d354dcce2d76e83d12e96abce7ca3daf",
|
|
14
|
+
"tests/fixtures/delay-settings.mjs": "ebf999b689f5f7608547a9d41b2cd72924712946216833992573786393a66318",
|
|
15
|
+
"scripts/render-delay-settings.mjs": "5da957ade4a82f7863d27d237250cbfc6c4b5cfb4a6260b9fa1ae00c81001815",
|
|
16
|
+
"scripts/plot-delay-settings.py": "e027a9958cadf9ea1321de1d64ccf689e3db2bba0e44728673f5e250ea21fe19",
|
|
17
|
+
"tests/delay-settings.spec.ts": "0e5eb94a49c484a871810d65f334f4da65e7db852213f132fac1495eadc18eb9",
|
|
18
|
+
"tests/delay-settings-packed.test.mjs": "947fdbfa4ef5d1e8cf82365bf4e4ece7640e75868dbd9528809ca83af96bd52a",
|
|
19
|
+
"package-lock.json": "9aebb16d62097a630ef0022e375f0570dfcc7c082746e74e4b9427637c6fa53a"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"den": "0.0.0",
|
|
23
|
+
"unworklet": "0.4.1",
|
|
24
|
+
"lockSHA256": "9aebb16d62097a630ef0022e375f0570dfcc7c082746e74e4b9427637c6fa53a"
|
|
25
|
+
},
|
|
26
|
+
"sampleRate": 48000,
|
|
27
|
+
"channels": 2,
|
|
28
|
+
"frames": 262144,
|
|
29
|
+
"durationSeconds": 5.461333333333333,
|
|
30
|
+
"initialState": "Fresh engine: zero history/filter states and initial LFO phase; no restored snapshot",
|
|
31
|
+
"seed": null,
|
|
32
|
+
"midi": null,
|
|
33
|
+
"audioEncoding": "IEEE float32 WAV",
|
|
34
|
+
"browserScope": "48kHz engine packed gate is separate; these candidate files are offline renders",
|
|
35
|
+
"cases": [
|
|
36
|
+
{
|
|
37
|
+
"id": "chorus-base",
|
|
38
|
+
"setting": {
|
|
39
|
+
"config": {
|
|
40
|
+
"maxDelaySeconds": 2,
|
|
41
|
+
"tone": "flat",
|
|
42
|
+
"stereoPhaseCycles": 0.5
|
|
43
|
+
},
|
|
44
|
+
"parameters": {
|
|
45
|
+
"timeLeftSeconds": 0.018,
|
|
46
|
+
"timeRightSeconds": 0.018,
|
|
47
|
+
"sync": false,
|
|
48
|
+
"bpm": 120,
|
|
49
|
+
"beatsLeft": 1,
|
|
50
|
+
"beatsRight": 1,
|
|
51
|
+
"feedback": 0,
|
|
52
|
+
"cutoffHz": 3200,
|
|
53
|
+
"mix": 0.45,
|
|
54
|
+
"rateHz": 0.65,
|
|
55
|
+
"depthSeconds": 0.003,
|
|
56
|
+
"bypass": false,
|
|
57
|
+
"reset": false
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"events": [],
|
|
61
|
+
"source": "Identical mono L/R; 220/330/440Hz sine sum gains 0.12/0.06/0.03; 20ms linear attack, 50ms release; source stops at 2s.",
|
|
62
|
+
"input": {
|
|
63
|
+
"file": "chorus-base-input.wav",
|
|
64
|
+
"sha256": "7323fd06940d219a2c0e96af964394d19fa99d879d3b5a97390943e87d134473"
|
|
65
|
+
},
|
|
66
|
+
"raw": {
|
|
67
|
+
"file": "chorus-base-raw.wav",
|
|
68
|
+
"sha256": "3eeeffd71e1a7d5e23729f1b29f630ab9befd15994a58b36e3c529f1d7dc1461"
|
|
69
|
+
},
|
|
70
|
+
"peaks": [
|
|
71
|
+
0.19121864438056946,
|
|
72
|
+
0.19117864966392517
|
|
73
|
+
],
|
|
74
|
+
"verification": {
|
|
75
|
+
"independentTimelineBiquadMaxError": [
|
|
76
|
+
2.0489096641540527e-07,
|
|
77
|
+
1.9371509552001953e-07
|
|
78
|
+
],
|
|
79
|
+
"tolerance": 5e-06,
|
|
80
|
+
"scrubbedSamples": 0
|
|
81
|
+
},
|
|
82
|
+
"listeningFocus": "Slow widening and pitch motion; mono fold coloration; rate/depth edit can click.",
|
|
83
|
+
"waveform": {
|
|
84
|
+
"file": "chorus-base-waveform.png",
|
|
85
|
+
"sha256": "44840f45455ee91e93a918495e23f6a99070dd85de6960a72e618cf92c3eb450",
|
|
86
|
+
"bucketSamples": 128,
|
|
87
|
+
"amplitudeRange": [
|
|
88
|
+
-0.5,
|
|
89
|
+
0.5
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "chorus-edits",
|
|
95
|
+
"setting": {
|
|
96
|
+
"config": {
|
|
97
|
+
"maxDelaySeconds": 2,
|
|
98
|
+
"tone": "flat",
|
|
99
|
+
"stereoPhaseCycles": 0.5
|
|
100
|
+
},
|
|
101
|
+
"parameters": {
|
|
102
|
+
"timeLeftSeconds": 0.018,
|
|
103
|
+
"timeRightSeconds": 0.018,
|
|
104
|
+
"sync": false,
|
|
105
|
+
"bpm": 120,
|
|
106
|
+
"beatsLeft": 1,
|
|
107
|
+
"beatsRight": 1,
|
|
108
|
+
"feedback": 0,
|
|
109
|
+
"cutoffHz": 3200,
|
|
110
|
+
"mix": 0.45,
|
|
111
|
+
"rateHz": 0.65,
|
|
112
|
+
"depthSeconds": 0.003,
|
|
113
|
+
"bypass": false,
|
|
114
|
+
"reset": false
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"events": [
|
|
118
|
+
{
|
|
119
|
+
"sample": 72000,
|
|
120
|
+
"values": {
|
|
121
|
+
"rateHz": 1.3,
|
|
122
|
+
"depthSeconds": 0.006
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"sample": 144000,
|
|
127
|
+
"values": {
|
|
128
|
+
"bypass": true
|
|
129
|
+
}
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"sample": 168000,
|
|
133
|
+
"values": {
|
|
134
|
+
"bypass": false
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
"sample": 216000,
|
|
139
|
+
"values": {
|
|
140
|
+
"reset": true
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
"sample": 216001,
|
|
145
|
+
"values": {
|
|
146
|
+
"reset": false
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
"source": "Identical mono L/R; 220/330/440Hz sine sum gains 0.12/0.06/0.03; 20ms linear attack, 50ms release; source stops at 2s.",
|
|
151
|
+
"input": {
|
|
152
|
+
"file": "chorus-edits-input.wav",
|
|
153
|
+
"sha256": "7323fd06940d219a2c0e96af964394d19fa99d879d3b5a97390943e87d134473"
|
|
154
|
+
},
|
|
155
|
+
"raw": {
|
|
156
|
+
"file": "chorus-edits-raw.wav",
|
|
157
|
+
"sha256": "eaafa1a74a47b189a3bf6c26cf287c9c6c12e4813801f3fc6bc29c82e566d8a3"
|
|
158
|
+
},
|
|
159
|
+
"peaks": [
|
|
160
|
+
0.19121313095092773,
|
|
161
|
+
0.19117864966392517
|
|
162
|
+
],
|
|
163
|
+
"verification": {
|
|
164
|
+
"independentTimelineBiquadMaxError": [
|
|
165
|
+
3.0919909477233887e-07,
|
|
166
|
+
1.9371509552001953e-07
|
|
167
|
+
],
|
|
168
|
+
"tolerance": 5e-06,
|
|
169
|
+
"scrubbedSamples": 0
|
|
170
|
+
},
|
|
171
|
+
"listeningFocus": "Slow widening and pitch motion; mono fold coloration; rate/depth edit can click.",
|
|
172
|
+
"waveform": {
|
|
173
|
+
"file": "chorus-edits-waveform.png",
|
|
174
|
+
"sha256": "9c2ab4f7b0ffc1d195b24c78ff02d992bac62e65935301317bd15d5224dcea24",
|
|
175
|
+
"bucketSamples": 128,
|
|
176
|
+
"amplitudeRange": [
|
|
177
|
+
-0.5,
|
|
178
|
+
0.5
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "rhythmic-delay-base",
|
|
184
|
+
"setting": {
|
|
185
|
+
"config": {
|
|
186
|
+
"maxDelaySeconds": 2,
|
|
187
|
+
"tone": "lowpass",
|
|
188
|
+
"stereoPhaseCycles": 0.5
|
|
189
|
+
},
|
|
190
|
+
"parameters": {
|
|
191
|
+
"timeLeftSeconds": 0.375,
|
|
192
|
+
"timeRightSeconds": 0.5,
|
|
193
|
+
"sync": true,
|
|
194
|
+
"bpm": 120,
|
|
195
|
+
"beatsLeft": 0.75,
|
|
196
|
+
"beatsRight": 1,
|
|
197
|
+
"feedback": 0.48,
|
|
198
|
+
"cutoffHz": 3200,
|
|
199
|
+
"mix": 0.35,
|
|
200
|
+
"rateHz": 0,
|
|
201
|
+
"depthSeconds": 0,
|
|
202
|
+
"bypass": false,
|
|
203
|
+
"reset": false
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
"events": [],
|
|
207
|
+
"source": "Identical mono L/R; four 0.5s plucks at t=0/0.5/1/1.5; 0.3*exp(-local/0.065)*(sin(2pi*110*local)+0.25*sin(2pi*330*local)); stops at 2s.",
|
|
208
|
+
"input": {
|
|
209
|
+
"file": "rhythmic-delay-base-input.wav",
|
|
210
|
+
"sha256": "b58394aba9656fd2949937c71881d6e9a45add8c41cb8247a726389aa3e2982c"
|
|
211
|
+
},
|
|
212
|
+
"raw": {
|
|
213
|
+
"file": "rhythmic-delay-base-raw.wav",
|
|
214
|
+
"sha256": "3421ba3682892b307e7f426cdbf512ae9f47fd578ff954f6117465a2dc0c9fc9"
|
|
215
|
+
},
|
|
216
|
+
"peaks": [
|
|
217
|
+
0.18629704415798187,
|
|
218
|
+
0.32658955454826355
|
|
219
|
+
],
|
|
220
|
+
"verification": {
|
|
221
|
+
"independentTimelineBiquadMaxError": [
|
|
222
|
+
1.4901161193847656e-08,
|
|
223
|
+
2.9802322387695312e-08
|
|
224
|
+
],
|
|
225
|
+
"tolerance": 5e-06,
|
|
226
|
+
"scrubbedSamples": 0
|
|
227
|
+
},
|
|
228
|
+
"listeningFocus": "Dotted-eighth/quarter spacing and darkening repeats; abrupt tempo edit can jump/click; bypass hides but preserves tail.",
|
|
229
|
+
"waveform": {
|
|
230
|
+
"file": "rhythmic-delay-base-waveform.png",
|
|
231
|
+
"sha256": "5e420ffe22efd3dfc57955286ae9d66bf838672f3b7a2f6a33d123c30fe2c467",
|
|
232
|
+
"bucketSamples": 128,
|
|
233
|
+
"amplitudeRange": [
|
|
234
|
+
-0.5,
|
|
235
|
+
0.5
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"id": "rhythmic-delay-edits",
|
|
241
|
+
"setting": {
|
|
242
|
+
"config": {
|
|
243
|
+
"maxDelaySeconds": 2,
|
|
244
|
+
"tone": "lowpass",
|
|
245
|
+
"stereoPhaseCycles": 0.5
|
|
246
|
+
},
|
|
247
|
+
"parameters": {
|
|
248
|
+
"timeLeftSeconds": 0.375,
|
|
249
|
+
"timeRightSeconds": 0.5,
|
|
250
|
+
"sync": true,
|
|
251
|
+
"bpm": 120,
|
|
252
|
+
"beatsLeft": 0.75,
|
|
253
|
+
"beatsRight": 1,
|
|
254
|
+
"feedback": 0.48,
|
|
255
|
+
"cutoffHz": 3200,
|
|
256
|
+
"mix": 0.35,
|
|
257
|
+
"rateHz": 0,
|
|
258
|
+
"depthSeconds": 0,
|
|
259
|
+
"bypass": false,
|
|
260
|
+
"reset": false
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
"events": [
|
|
264
|
+
{
|
|
265
|
+
"sample": 72000,
|
|
266
|
+
"values": {
|
|
267
|
+
"bpm": 180,
|
|
268
|
+
"feedback": 0.65
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
"sample": 144000,
|
|
273
|
+
"values": {
|
|
274
|
+
"bypass": true
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"sample": 168000,
|
|
279
|
+
"values": {
|
|
280
|
+
"bypass": false
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
"sample": 216000,
|
|
285
|
+
"values": {
|
|
286
|
+
"reset": true
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"sample": 216001,
|
|
291
|
+
"values": {
|
|
292
|
+
"reset": false
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
],
|
|
296
|
+
"source": "Identical mono L/R; four 0.5s plucks at t=0/0.5/1/1.5; 0.3*exp(-local/0.065)*(sin(2pi*110*local)+0.25*sin(2pi*330*local)); stops at 2s.",
|
|
297
|
+
"input": {
|
|
298
|
+
"file": "rhythmic-delay-edits-input.wav",
|
|
299
|
+
"sha256": "b58394aba9656fd2949937c71881d6e9a45add8c41cb8247a726389aa3e2982c"
|
|
300
|
+
},
|
|
301
|
+
"raw": {
|
|
302
|
+
"file": "rhythmic-delay-edits-raw.wav",
|
|
303
|
+
"sha256": "a8e395239a030f920735f61c770b63e6cd40eaba2541bd604235763c74e7ae78"
|
|
304
|
+
},
|
|
305
|
+
"peaks": [
|
|
306
|
+
0.1877381056547165,
|
|
307
|
+
0.30593806505203247
|
|
308
|
+
],
|
|
309
|
+
"verification": {
|
|
310
|
+
"independentTimelineBiquadMaxError": [
|
|
311
|
+
2.9802322387695312e-08,
|
|
312
|
+
2.9802322387695312e-08
|
|
313
|
+
],
|
|
314
|
+
"tolerance": 5e-06,
|
|
315
|
+
"scrubbedSamples": 0
|
|
316
|
+
},
|
|
317
|
+
"listeningFocus": "Dotted-eighth/quarter spacing and darkening repeats; abrupt tempo edit can jump/click; bypass hides but preserves tail.",
|
|
318
|
+
"waveform": {
|
|
319
|
+
"file": "rhythmic-delay-edits-waveform.png",
|
|
320
|
+
"sha256": "72bbeef4e0453e180968e6a520a197fd0d26454768e50d739f5ff7c4da311a33",
|
|
321
|
+
"bucketSamples": 128,
|
|
322
|
+
"amplitudeRange": [
|
|
323
|
+
-0.5,
|
|
324
|
+
0.5
|
|
325
|
+
]
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
],
|
|
329
|
+
"plotter": {
|
|
330
|
+
"matplotlib": "3.10.8",
|
|
331
|
+
"numpy": "2.3.5"
|
|
332
|
+
}
|
|
333
|
+
}
|