@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,81 @@
|
|
|
1
|
+
# Larger STFT offline candidate
|
|
2
|
+
|
|
3
|
+
The reviewed initial proof at `262d74b` remains frozen. After independent review
|
|
4
|
+
of the private large-frame proof at `cea9dfe`, this follow-up promotes the public
|
|
5
|
+
`stftIdentity` wrapper to power-of-two N in8..1024, with H=N/2 or N/4. This is an
|
|
6
|
+
offline candidate with explicit snapshot and execution-cost contracts.
|
|
7
|
+
Convolution remains B<=32 and IR<=128 samples.
|
|
8
|
+
|
|
9
|
+
## Kernel proof
|
|
10
|
+
|
|
11
|
+
N1024 now uses counted native loops for initialization/bit reversal, butterfly
|
|
12
|
+
stages and buffer copies. Eight complex test vectors at each of 44.1/48/96 kHz
|
|
13
|
+
cover impulse, DC, Nyquist, positive/negative bins, off-bin tone and seeded
|
|
14
|
+
complex/real data. Forward/inverse results match a direct O(N²) DFT rounded to
|
|
15
|
+
the f32 output format within 1e-6; signs, ordering, normalization and real-input
|
|
16
|
+
conjugate symmetry are checked independently.
|
|
17
|
+
|
|
18
|
+
A fresh frozen-head N1024 kernel-only probe completed under the unchanged
|
|
19
|
+
2-GiB/90-second watchdog: 114,709-byte WASM, 128-KiB fixed memory, 188-MB process
|
|
20
|
+
RSS peak and 15.82-ms first quantum. This is a kernel diagnostic, not evidence
|
|
21
|
+
for windowed framing, snapshots, practical concurrency or realtime acceptance.
|
|
22
|
+
|
|
23
|
+
## Persistent frame phase
|
|
24
|
+
|
|
25
|
+
The measurements and dispatch description below record the original 0.4.1
|
|
26
|
+
candidate at quantum 128. Since den 0.2.0, callers pass `ctx.renderQuantumSize`
|
|
27
|
+
and dispatch is `gcd(H, 128, renderQuantumSize)`; fixed FFT loops use `forIndex`.
|
|
28
|
+
The [current spectral contract](spectral.md) describes snapshot behavior and
|
|
29
|
+
increased work for smaller or coprime quanta. Historical timings below are not
|
|
30
|
+
measurements of those targets.
|
|
31
|
+
|
|
32
|
+
Public unworklet 0.4.1 has no condition-driven statement block. Its select is
|
|
33
|
+
eager; everyNSamples/byN bounds are construction-fixed. Native everyNSamples
|
|
34
|
+
counters are omitted from snapshots. Simply changing a hop to256 or512 would
|
|
35
|
+
therefore change its phase when restoring a snapshot taken at an intermediate
|
|
36
|
+
128-sample quantum boundary.
|
|
37
|
+
|
|
38
|
+
The candidate uses native dispatch every min(H,128) samples, a divisor of the
|
|
39
|
+
snapshot quantum. Its existing named persistent ring cursor determines whether
|
|
40
|
+
cursor modulo H is zero. Only these true H-phase frames update overlap state
|
|
41
|
+
and consume pending-reset invalidation. Reset never changes cursor/hop phase.
|
|
42
|
+
|
|
43
|
+
For H>128 the FFT/IFFT pair still executes every128 samples. Off-phase results
|
|
44
|
+
are discarded using eager selects; this is expressly not H-rate CPU savings.
|
|
45
|
+
Window, history and spectrum scratch use explicit buffer indices, with no
|
|
46
|
+
implicit audio/parameter sample position inside nested work loops.
|
|
47
|
+
|
|
48
|
+
## Verified so far
|
|
49
|
+
|
|
50
|
+
- Original small FFT/WOLA/FIR tolerances and reset/latency assertions unchanged
|
|
51
|
+
- N256/512/1024 identity reconstruction at H=N/2 and N/4, all three rates
|
|
52
|
+
- Every reset phase, separated sufficiently to yield nonzero reconstructed audio
|
|
53
|
+
- Exact N-sample latency and reset's current-input discard
|
|
54
|
+
- All four quantum-offset phase classes for H<=512, including reset immediately
|
|
55
|
+
before snapshots; continuation is bit-identical
|
|
56
|
+
- Held reset, varying signed f32 subnormals and full finite f32 range, zero scrubs
|
|
57
|
+
- 29 focused tests passed in26 seconds, with502-MB observed process-tree RSS
|
|
58
|
+
|
|
59
|
+
## Measured eager-dispatch profile
|
|
60
|
+
|
|
61
|
+
The actual internal N1024/H512 STFT dispatches every128 samples and completed a
|
|
62
|
+
fresh-process 48-kHz profile under the unchanged2-GiB/60-second watchdog in2.22s.
|
|
63
|
+
It generated119,327 bytes of WASM, used128KiB fixed DSP memory and peaked at194MB
|
|
64
|
+
process RSS. Capture was97ms, compile1.60s and first/startup maximum18.98ms.
|
|
65
|
+
After128 warmup quanta,512 measured quanta had process p50 0.163ms,
|
|
66
|
+
p99 1.428ms and maximum4.680ms; maximum including copies was4.687ms. Zero output
|
|
67
|
+
scrubs and fixed memory were checked. This is one local Node instance with host
|
|
68
|
+
jitter, not browser or concurrency evidence.
|
|
69
|
+
|
|
70
|
+
Both the cold and observed warm maxima exceed the2.67-ms 48-kHz quantum.
|
|
71
|
+
The algorithmic audio latency is separately1024 samples:21.33ms at48kHz,
|
|
72
|
+
23.22ms at44.1kHz and10.67ms at96kHz. Choosing H512 does not reduce execution to
|
|
73
|
+
once every512 samples: preserving arbitrary-quantum snapshot phase currently
|
|
74
|
+
costs one FFT/IFFT pair per128 samples, with off-phase results discarded.
|
|
75
|
+
|
|
76
|
+
An isolated public packed consumer verifies the actual export, both N1024 hops,
|
|
77
|
+
three offline rates, reset and all four snapshot phase classes, plus the
|
|
78
|
+
generated48-kHz Vite worklet. Exact-head package evidence and final independent
|
|
79
|
+
QA gate integration of this promotion. No browser, human sound or realtime clearance is
|
|
80
|
+
claimed. This does not complete pitch/time processing, phase-vocoder effects or
|
|
81
|
+
practical long-IR convolution.
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Bounded spectral texture entry contract
|
|
2
|
+
|
|
3
|
+
Source scope: catalog group 11, [STFT blur and cross-synthesis](https://app.notion.com/p/3ef449b4e81d817db1acf1fff48779b3).
|
|
4
|
+
This proposal adds two CANDIDATE native subgraphs. It does not claim spectral
|
|
5
|
+
resynthesis completeness, transparent speech, phase-vocoder quality, phase
|
|
6
|
+
locking, human sound approval, or real-time clearance.
|
|
7
|
+
|
|
8
|
+
## Shared framing
|
|
9
|
+
|
|
10
|
+
- Pass `renderQuantumSize: ctx.renderQuantumSize` in both module configurations.
|
|
11
|
+
Dispatch follows the shared [spectral contract](spectral.md), preserving
|
|
12
|
+
block-atomic snapshot phase at the actual render quantum.
|
|
13
|
+
- Construction-fixed power-of-two N=8..256, H=N/2 or N/4. Rebuild to change.
|
|
14
|
+
- Instantiate with an explicit subgraph name and call exactly once per sample
|
|
15
|
+
inside stride-1 `forSample`, passing its native `everyNSamples`.
|
|
16
|
+
- Every audio input is finite normalized f32 in [-1,1]. The two cross-synthesis
|
|
17
|
+
inputs must belong to the same sample timeline and call.
|
|
18
|
+
- Reuse the existing FFT/STFT code unchanged: periodic sqrt-Hann analysis and
|
|
19
|
+
synthesis, forward unnormalized DFT, inverse 1/N, WOLA scale 2H/N, internal
|
|
20
|
+
2^128 scaling, and live reference delay N.
|
|
21
|
+
- A committed frame at t=mH consumes [t-N,t), excludes current input, and samples
|
|
22
|
+
controls at t. Missing startup/reset history is zero padded. Dispatch occurs
|
|
23
|
+
every `gcd(H, 128, renderQuantumSize)` samples; off-phase work is eager and
|
|
24
|
+
commits no overlap. At the original quantum 128, H<=128 needs no off-phase
|
|
25
|
+
calls. Smaller or coprime quanta add work; the N1024 single-input evidence is
|
|
26
|
+
not inherited by this two-framer composition. N<=256 retains its initial bound.
|
|
27
|
+
- Clamp amount to [0,1]; NaN becomes 0, infinities clamp. Interpolation is linear
|
|
28
|
+
in bin magnitude, not dB, power, audio time, or equal-power gain. It replaces
|
|
29
|
+
newly committed frames; previous overlap remains until it finishes within N.
|
|
30
|
+
- DC and Nyquist are real; negative bins are reconstructed as conjugates.
|
|
31
|
+
- There is no output normalizer, limiter, extra transition ramp, or dry path.
|
|
32
|
+
Processed onset can precede the N-sample identity alignment. Drain 2N zeros
|
|
33
|
+
after the last nonzero input (both inputs for cross-synthesis).
|
|
34
|
+
|
|
35
|
+
## Frequency-magnitude blur
|
|
36
|
+
|
|
37
|
+
`spectralBlur({ size, hopSize, radius, renderQuantumSize }).tick(input, amount, reset, everyNSamples)`
|
|
38
|
+
|
|
39
|
+
Radius is a fixed integer 1..min(8,N/2-1). Let X[k] be the full two-sided complex
|
|
40
|
+
DFT and A[k]=|X[k]|. Define a circular triangular blur on the raw, two-sided DFT
|
|
41
|
+
magnitudes, not on single-sided coherent-amplitude estimates:
|
|
42
|
+
|
|
43
|
+
B[k] = sum(j=-R..R) (R+1-|j|)/(R+1)^2 * A[(k+j) mod N]
|
|
44
|
+
T[k] = (1-amount)*A[k] + amount*B[k]
|
|
45
|
+
|
|
46
|
+
The normalized symmetric kernel crosses DC and Nyquist through the corresponding
|
|
47
|
+
conjugate bins. No endpoint duplication or renormalized truncated kernel occurs.
|
|
48
|
+
Let P=max_j A[j]. Retain the current input phase only when A[k]>0 and
|
|
49
|
+
A[k]>=2^-20*P: Y[k]=T[k]*X[k]/A[k]. Otherwise use phase zero,
|
|
50
|
+
Y[k]=T[k]+i*0. At DC/Nyquist a retained real bin preserves its sign; a bin below
|
|
51
|
+
the floor or exactly zero gets positive real magnitude. Equality belongs to
|
|
52
|
+
the retained-phase side only when A[k]>0.
|
|
53
|
+
|
|
54
|
+
Implement phase as separately normalized real/imaginary components, then
|
|
55
|
+
multiply by T, rather than multiplying X by an unbounded T/A gain. The divisor
|
|
56
|
+
is replaced by 1 when A=0 before evaluating either select branch. All finite
|
|
57
|
+
inputs remain finite. The floor is relative to the same frame's maximum raw
|
|
58
|
+
magnitude, so uniformly scaling a frame preserves the phase decision in ideal
|
|
59
|
+
arithmetic, including tiny inputs. This deliberately discards weak-bin phase
|
|
60
|
+
and can abruptly change phase at the floor. It is a deterministic texture
|
|
61
|
+
choice, not extra precision, smoothing or a phase-locking guarantee.
|
|
62
|
+
For target magnitude T and retained phase phi, replacing zero phase changes
|
|
63
|
+
that complex bin by 2*T*abs(sin(phi/2)), up to 2T, even when the original A is
|
|
64
|
+
tiny. The below/at/above floor cases and this jump bound are required evidence;
|
|
65
|
+
the threshold must not be changed to make an oracle comparison pass.
|
|
66
|
+
|
|
67
|
+
The rejected exact-nonzero phase rule could promote FFT roundoff at a spectral
|
|
68
|
+
null into audible redistributed magnitude and make two valid DFT algorithms
|
|
69
|
+
disagree audibly. Keep that counterexample in validation. The floor stabilizes
|
|
70
|
+
nulls, but decisions extremely close to the floor remain finite-precision
|
|
71
|
+
sensitive, and full-frame rescaling may quantize f32 input differently.
|
|
72
|
+
|
|
73
|
+
The fixed amount=0 path selects the original unmodified spectrum; its PCM
|
|
74
|
+
identity claim must be tested, including subnormal inputs and arbitrary reset.
|
|
75
|
+
Once amount is changed to 0, previous processed overlap first needs to settle.
|
|
76
|
+
At amount=1 the full triangular blur is used, which can add energy to previously
|
|
77
|
+
empty bins using the deterministic phase rule above. This is frequency blur,
|
|
78
|
+
not a time-memory blur or a freeze.
|
|
79
|
+
|
|
80
|
+
## Gain-bounded cross-synthesis
|
|
81
|
+
|
|
82
|
+
`spectralCrossSynthesis({ size, hopSize, maxGain, renderQuantumSize }).tick(carrier, modulator, amount, reset, everyNSamples)`
|
|
83
|
+
|
|
84
|
+
maxGain is a finite construction-fixed number in [1,16]. Let C[k], M[k] be the
|
|
85
|
+
synchronized carrier/modulator frames, A[k]=|C[k]|, and B[k]=|M[k]|:
|
|
86
|
+
|
|
87
|
+
target[k] = min(B[k], maxGain*A[k])
|
|
88
|
+
T[k] = (1-amount)*A[k] + amount*target[k]
|
|
89
|
+
Y[k] = C[k]*((1-amount) + amount*min(B[k]/A[k],maxGain))
|
|
90
|
+
|
|
91
|
+
For exactly zero A, set the ratio to zero, with a safe denominator before select
|
|
92
|
+
evaluation. Zero carrier therefore remains silent, including when the modulator
|
|
93
|
+
is nonzero. A near-zero carrier is amplified by at most maxGain, not promoted to
|
|
94
|
+
the modulator level. The amount=0 path selects the original unmodified carrier
|
|
95
|
+
spectrum. A silent modulator with amount=1 suppresses the carrier spectrum.
|
|
96
|
+
Same-input amount=1 should numerically preserve the carrier; any stronger
|
|
97
|
+
bit-identity claim depends on measured proof and is not presumed.
|
|
98
|
+
|
|
99
|
+
Carrier phase is unchanged; modulator phase does not contribute. Both spectra
|
|
100
|
+
use identical raw DFT/window calibration, so the single-sided coherent-window
|
|
101
|
+
factor (1/windowSum at DC/Nyquist and 2/windowSum elsewhere) cancels in every
|
|
102
|
+
same-bin ratio. Full cross-synthesis replaces raw carrier magnitudes with
|
|
103
|
+
modulator magnitudes only where the gain cap permits. It does not transfer a
|
|
104
|
+
smooth vocal envelope, move formants, estimate frequencies, or guarantee speech
|
|
105
|
+
intelligibility. No audio-rate modulator bypass/identity is inferred from a zero
|
|
106
|
+
gain or silent control input.
|
|
107
|
+
|
|
108
|
+
## Composition, reset and native state
|
|
109
|
+
|
|
110
|
+
The cross-synthesis implementation will first tick a separately named private
|
|
111
|
+
modulator STFT instance whose construction hook materializes current magnitudes
|
|
112
|
+
into its own transient buffer, then tick the carrier STFT instance whose hook
|
|
113
|
+
reads those magnitudes. Both use the same N, H, reset and outer sample callback.
|
|
114
|
+
The modulator's inverse transform/output is unused; it still executes. This
|
|
115
|
+
deliberate bounded overhead avoids modifying shared framing or adding a second
|
|
116
|
+
framing engine. Native source inspection shows separate everyNSamples counters
|
|
117
|
+
and sequential statement emission; synchronized analysis and no one-frame lag
|
|
118
|
+
remain requirements for independent sample-by-sample proof.
|
|
119
|
+
The materializing hook writes and native frame statements must remain present
|
|
120
|
+
even though the tap's audio return is unused; inspect the captured graph and
|
|
121
|
+
prove observed modulator changes reach the matching carrier frame.
|
|
122
|
+
|
|
123
|
+
Reset immediately silences and discards the current input(s), invalidates old
|
|
124
|
+
history/overlap, and does not rephase either frame cursor. Held reset remains
|
|
125
|
+
silent. State contains the reviewed native history/overlap/cursor/reset state
|
|
126
|
+
per instance. Magnitudes, FFT buffers, windows and sampled controls are transient
|
|
127
|
+
and are fully recomputed before use at the next frame. No cross-frame blur
|
|
128
|
+
memory or private snapshot format is introduced.
|
|
129
|
+
|
|
130
|
+
Only full native same-graph/configuration/sample-rate/quantum snapshots are supported.
|
|
131
|
+
Restoring only one member of a cross-synthesis pair is outside the contract.
|
|
132
|
+
Tests must prove exact continuation across every legal H class at native
|
|
133
|
+
block boundaries, including non-128-aligned boundaries, active processing,
|
|
134
|
+
pending reset, and held reset. No cross-configuration/schema/rate/quantum
|
|
135
|
+
migration is promised.
|
|
136
|
+
|
|
137
|
+
## Ideal headroom and validation
|
|
138
|
+
|
|
139
|
+
For normalized inputs, an analysis frame has L2 norm <=sqrt(N). The normalized
|
|
140
|
+
circular nonnegative blur kernel is an L2 contraction on magnitudes, and linear
|
|
141
|
+
interpolation preserves that bound. Cross-synthesis's target magnitude is
|
|
142
|
+
componentwise <=|M|; triangle inequality bounds the interpolated norm by the
|
|
143
|
+
larger of the normalized carrier/modulator frame norms. Phase replacement does
|
|
144
|
+
not change norm. Each inverse-frame sample is thus bounded by sqrt(N), and at
|
|
145
|
+
most N/H frames each scaled by 2H/N overlap, giving |output|<=2*sqrt(N), at most
|
|
146
|
+
32 for N256. This is conservative ideal arithmetic, not a finite-f64 theorem or
|
|
147
|
+
an enforced ceiling. No per-channel output normalization is permitted in proof.
|
|
148
|
+
|
|
149
|
+
Required evidence: independent dense DFT/magnitude operation/IFFT/absolute-time
|
|
150
|
+
WOLA; analytic circular-kernel DC/Nyquist/isolated-bin expectations; amount
|
|
151
|
+
endpoints/interior/nonfinite controls; modulator scaling/phase independence and
|
|
152
|
+
explicit carrier zero/near-zero gain-cap behavior; current-sample exclusion and
|
|
153
|
+
simultaneous transient frames; every small reset offset, held reset, snapshots,
|
|
154
|
+
subnormals/extrema, finite drain and same-schema state at 44.1/48/96 kHz. Keep
|
|
155
|
+
blur's full-frame scale invariance, exact-zero/weak/retained phase boundary,
|
|
156
|
+
DC/Nyquist sign and rejected near-null amplification counterexamples explicit.
|
|
157
|
+
Keep existing no-hook framing/schema/PCM tests unchanged. Independently review this
|
|
158
|
+
contract before DSP, and review actual implementation before integration.
|
|
159
|
+
|
|
160
|
+
Measure maximum N256/R8 graph memory/cold/warm render costs later under the
|
|
161
|
+
coordinator's fixed guard and exclusive resource lease. Public package exports,
|
|
162
|
+
installed-consumer and hosted browser acceptance belong to the coordinator.
|
|
163
|
+
No install, runtime/loader/snapshot/framework/upstream changes are proposed.
|
|
164
|
+
|
|
165
|
+
## Initial focused evidence
|
|
166
|
+
|
|
167
|
+
The initial 21-test focused/native compatibility pass and package typecheck
|
|
168
|
+
completed in 20.25 seconds with 580 MB summed process-group RSS under the fixed
|
|
169
|
+
120-second/2 GiB guard. The untouched source implementation passed independent
|
|
170
|
+
DFT/WOLA, exact amount-zero identity, synchronized dual frames, tiny inputs,
|
|
171
|
+
gain caps, reset, N256 native continuation and original no-hook compatibility.
|
|
172
|
+
Independent review requested small-hop snapshot classes and explicit measured
|
|
173
|
+
phase-floor ratios. The expanded 24-test pass and typecheck completed in 23.03
|
|
174
|
+
seconds with 576 MB summed process-group RSS under the same guard. It covers
|
|
175
|
+
every supported H class, pending/held reset snapshots, measured actual f32
|
|
176
|
+
floor ratios, the non-real phase jump, and exact power-of-two scale invariance
|
|
177
|
+
where f32 can represent the scaled waveform without subnormal quantization.
|
|
178
|
+
This evidence is not public-package, hosted-browser or human-sound approval.
|
package/docs/spectral.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Spectral framing candidate
|
|
2
|
+
|
|
3
|
+
This lane is CANDIDATE. It is an FFT/framing and identity reconstruction entry
|
|
4
|
+
proof for catalog §10/11, not completion of pitch/time, phase-vocoder, long-IR,
|
|
5
|
+
freeze/shimmer or spectral-source work. No human sound approval or browser
|
|
6
|
+
realtime clearance is implied.
|
|
7
|
+
|
|
8
|
+
## Entry contract
|
|
9
|
+
|
|
10
|
+
`stftIdentity({ size, hopSize, renderQuantumSize: ctx.renderQuantumSize }).tick(input, reset, everyNSamples)` is an ordinary
|
|
11
|
+
unworklet subgraph. Instantiate it once with an explicit name. Call tick exactly
|
|
12
|
+
once per sample from stride-1 `forSample((i, everyNSamples) => ...)`. The last
|
|
13
|
+
argument is unworklet's existing conditional scheduler, not a den scheduler or a
|
|
14
|
+
runtime JavaScript callback. `select` is never used to skip the FFT workload.
|
|
15
|
+
|
|
16
|
+
- Pass the enclosing processor's `ctx.renderQuantumSize` in the configuration.
|
|
17
|
+
It is a positive integer, matching unworklet's compile target, not the FFT size
|
|
18
|
+
or hop. All STFT-based modules use this value for snapshot-safe dispatch.
|
|
19
|
+
- Supported N is a power of two in 8..1024. H is N/2 or N/4. These are
|
|
20
|
+
offline-candidate bounds, not a performance or concurrency recommendation.
|
|
21
|
+
- The internal, non-public complex radix-2 kernel has natural-order real/imaginary
|
|
22
|
+
planes, forward exponent -i2πkn/N, inverse exponent +i2πkn/N and inverse scale 1/N.
|
|
23
|
+
All intermediate work/history use fixed f64 buffers; I/O is f32. The stream
|
|
24
|
+
uses an exact 2^128 scale internally to avoid 0.4.1's 1e-30 buffer-write
|
|
25
|
+
flush discarding quiet f32 inputs. Stage JavaScript loops execute during
|
|
26
|
+
graph construction. Within each stage, the public `forIndex(count)` loop
|
|
27
|
+
visits exactly the work-buffer capacity, independently of the render quantum,
|
|
28
|
+
without audio/parameter reads or updates to sample time. The outer `forSample`
|
|
29
|
+
loop alone follows the audio quantum. Twiddle step constants are calculated at build
|
|
30
|
+
time, then a bounded f64 recurrence reanchors to (1,0) at every butterfly group.
|
|
31
|
+
- A frame at sample t=mH consumes samples [t-N,t), with absent history set to
|
|
32
|
+
zero. It is added to output samples [t,t+N). Output therefore has exactly N
|
|
33
|
+
samples of latency, including startup. Drain with at least N zero input samples.
|
|
34
|
+
- Both windows are periodic sqrt-Hann: w[n]=sqrt(0.5-0.5 cos(2πn/N)). The product
|
|
35
|
+
is Hann. For K=N/H in {2,4}, the shifted cosines sum to zero, so the sum of
|
|
36
|
+
squared windows is K/2=N/(2H). The synthesis scale is its reciprocal 2H/N.
|
|
37
|
+
- Reset is level-sensitive: it emits zero immediately, discards the current
|
|
38
|
+
input, and invalidates prior history and overlap. Held reset remains silent.
|
|
39
|
+
It does not restart the native hop phase. The first later frame clears stale
|
|
40
|
+
overlap; output is masked until then. Post-reset reconstruction retains the
|
|
41
|
+
same N-sample delay. Discontinuities from reset are intentional.
|
|
42
|
+
- Input must be finite f32. No smoothing, gain normalization beyond WOLA, bypass,
|
|
43
|
+
dry/wet routing, sample-rate converter, hidden loader, external FFT runtime,
|
|
44
|
+
or new storage/snapshot system is supplied.
|
|
45
|
+
- Native persistent history/overlap/cursor/valid/reset state is saved. FFT and
|
|
46
|
+
spectral scratch are transient and fully overwritten at each scheduled frame.
|
|
47
|
+
Only identical graph/rate/quantum full-snapshot continuation is in scope;
|
|
48
|
+
migration and cross-rate or cross-quantum continuation are not promised.
|
|
49
|
+
Native hop counters are not included in snapshots. Dispatch therefore occurs
|
|
50
|
+
every `gcd(H, 128, renderQuantumSize)` samples, a divisor of both the hop and
|
|
51
|
+
every block-atomic restore boundary. The persistent cursor modulo H selects
|
|
52
|
+
frame commits. Off-phase FFT results are computed eagerly and discarded.
|
|
53
|
+
At quantum 128 this retains the former dispatch period. For H>=128, quantum
|
|
54
|
+
32 evaluates four times as many FFT pairs per sample; a quantum coprime to
|
|
55
|
+
128 dispatches every sample, up to 128 times the old workload. Correct output
|
|
56
|
+
and snapshot phase do not imply realtime feasibility. See
|
|
57
|
+
[large-frame evidence](spectral-large-candidate.md) for historical costs.
|
|
58
|
+
|
|
59
|
+
## Verification required before integration
|
|
60
|
+
|
|
61
|
+
Independent direct O(N²) DFT covers complex impulse, DC, Nyquist, positive and
|
|
62
|
+
negative bins, off-bin tone, seeded inputs, signs, natural ordering, conjugate
|
|
63
|
+
symmetry and inverse normalization. Independent time-indexed DFT/WOLA covers
|
|
64
|
+
startup, every hop phase, held/arbitrary resets, block boundaries and three
|
|
65
|
+
offline rates (44.1/48/96 kHz). The rate/quantum matrix includes quantum
|
|
66
|
+
32/128/256, independent numerical oracles and exact quantum-128 PCM comparisons.
|
|
67
|
+
Exact same-schema snapshots include non-128-aligned boundaries at quantum 32
|
|
68
|
+
and non-power-of-two quantum 48. Reset discard is checked separately. Larger construction sizes require measured capture/compile
|
|
69
|
+
cost, WASM size, memory and quantum-burst timing before becoming public bounds.
|
|
70
|
+
|
|
71
|
+
Node driver measurements describe that environment only, including whether copies
|
|
72
|
+
were included. They do not certify browser deadlines, concurrency or a device
|
|
73
|
+
budget. The playground now compiles for its actual AudioContext sample rate and
|
|
74
|
+
render quantum using unworklet 0.6.0; this does not grant realtime clearance.
|
|
75
|
+
|
|
76
|
+
## Initial local evidence
|
|
77
|
+
|
|
78
|
+
The initial N8/16/32/64 FFT/identity suite passed at all three rates, including
|
|
79
|
+
all reset hop phases, native snapshots and quiet/full-range finite f32 input.
|
|
80
|
+
For one N64/H16 instance in Node 24, capture was 32 ms, compile 1.16 s, generated
|
|
81
|
+
WASM 314,881 bytes, fixed WASM memory 65,536 bytes. A 512-quantum warmed run had
|
|
82
|
+
process p99 0.525 ms and maximum 1.07 ms, excluding input/output copies.
|
|
83
|
+
|
|
84
|
+
**Cold first-quantum execution was 298 ms.** This far exceeds a 48-kHz quantum
|
|
85
|
+
and is an explicit startup/deadline hazard. No deployment/realtime clearance is
|
|
86
|
+
granted by the warmed result. The exact-head packed report supersedes these
|
|
87
|
+
initial diagnostic values; larger frames need separate evidence.
|
|
88
|
+
|
|
89
|
+
### Graph-shape corrections and current verification
|
|
90
|
+
|
|
91
|
+
The original fully unrolled kernel is retained in commit `8d3df3c` for
|
|
92
|
+
reproduction. At N256/H64, a fresh two-transform driver probe generated 1.61 MB
|
|
93
|
+
of WASM, took 12.22 s for its first quantum and peaked at 4.21 GB process RSS.
|
|
94
|
+
The fixed DSP allocation was still only 64 KiB. This host compilation/execution
|
|
95
|
+
scaling failure is distinct from audio-buffer capacity; the unrolled N1024
|
|
96
|
+
probe was deliberately not started.
|
|
97
|
+
|
|
98
|
+
The first reduction put butterfly stages in native fixed loops. Its independent
|
|
99
|
+
hop7 entry probe confirms four inner work-buffer iterations, sequential
|
|
100
|
+
write/read visibility and preservation of the outer sample index at all three
|
|
101
|
+
rates. The full 16 FFT/STFT/convolution tests still pass. For N64/H16, generated
|
|
102
|
+
WASM falls to 84,607 bytes, fresh first quantum to 31.6 ms and peak process RSS
|
|
103
|
+
to 179 MB. Warm process p99 is 0.279 ms, maximum 0.395 ms in that local run.
|
|
104
|
+
This is a measured reduction, not a deadline clearance: the first quantum still
|
|
105
|
+
greatly exceeds the 2.67-ms 48-kHz deadline. The same partial reduction at N256
|
|
106
|
+
produced 273 KB WASM, a 110-ms first quantum and 466 MB peak process RSS. Its
|
|
107
|
+
N1024 diagnostic was stopped by the external 2-GiB RSS watchdog during the first
|
|
108
|
+
quantum: 1.122 MB WASM had compiled, but no cold/warm render completed. This was
|
|
109
|
+
not a successful N1024 render or numerical verification.
|
|
110
|
+
|
|
111
|
+
The current second reduction also uses native indexed loops for FFT
|
|
112
|
+
initialization/bit reversal, spectrum copies and STFT overlap-add. The window is
|
|
113
|
+
generated by a reanchored f64 sine recurrence each frame, rather than N copies
|
|
114
|
+
of graph code. This is still the same periodic sqrt-Hann contract. No audio or
|
|
115
|
+
parameter access occurs inside an inner buffer loop; all indices are explicit.
|
|
116
|
+
|
|
117
|
+
All original small-size DFT, WOLA and direct FIR assertions remain unchanged.
|
|
118
|
+
Together with three additional N256 direct-DFT cases, all 19 tests passed in
|
|
119
|
+
10.5 seconds. The larger test compares f32 outputs with independently f32-rounded
|
|
120
|
+
direct DFT values; it does not broaden public module support. The test process
|
|
121
|
+
tree's RSS was unavailable to that run's supervisor, so no zero-memory or peak
|
|
122
|
+
memory claim is made for it. Final exact-head resource/packed evidence is a
|
|
123
|
+
separate gate. The later independently reviewed large-frame proof and public
|
|
124
|
+
packed promotion extend STFT alone to N<=1024; B<=32/IR<=128 convolution bounds
|
|
125
|
+
remain unchanged. At N1024/H512 the measured first quantum was18.98ms and warm
|
|
126
|
+
maximum4.687ms with copies, both above a48-kHz quantum. Longer IRs and all
|
|
127
|
+
realtime claims remain unadvertised.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Pulse, triangle and deterministic noise candidates
|
|
2
|
+
|
|
3
|
+
`@denaudio/den/virtual-analog` exports `virtualAnalogSource`, `seededNoise` and
|
|
4
|
+
their config/control types. These additions do not change existing sine/saw,
|
|
5
|
+
instrument, unison, presets or sound candidates.
|
|
6
|
+
|
|
7
|
+
## Pulse and triangle
|
|
8
|
+
|
|
9
|
+
`virtualAnalogSource({sampleRate,waveform,phaseCycles?})` fixes waveform to
|
|
10
|
+
`'pulse'` or `'triangle'`. Sample rate must be finite 8000..192000 Hz;
|
|
11
|
+
initial/reset phase is in [0,1), default0. `tick({frequencyHz,duty,reset})`
|
|
12
|
+
returns one float32 sample. Frequency clamps to [0,.45 × sampleRate], with
|
|
13
|
+
NaN→0 and infinities saturated. Zero Hz holds phase. Duty clamps to [0,1],
|
|
14
|
+
NaN→.5; triangle ignores duty.
|
|
15
|
+
|
|
16
|
+
Pulse is +1 during phase<duty and −1 otherwise. Two-sample-support quadratic
|
|
17
|
+
polynomial BLEP residuals smooth its positive and negative steps. Pulse mean is
|
|
18
|
+
2×duty−1: non-50% pulse intentionally contains DC. Duty0 is exactly −1 and duty1
|
|
19
|
+
exactly +1, including reset and zero frequency. No DC blocker is hidden inside.
|
|
20
|
+
|
|
21
|
+
Triangle starts at −1 at phase0, rises linearly to +1 at phase.5, then falls.
|
|
22
|
+
Cubic polynomial BLAMP residuals correct slope jumps +8 and −8 cycles⁻¹. It is
|
|
23
|
+
direct phase evaluation, not a leaky-integrated pulse, so no integration drift or
|
|
24
|
+
settling tail is added. At positive step d, its phase-zero output is −1+4d/3.
|
|
25
|
+
At zero Hz it emits the raw triangle, including −1 at phase0.
|
|
26
|
+
|
|
27
|
+
Both constructions equal convolution of their periodic raw waveform with a
|
|
28
|
+
unit-area triangular kernel of half-width one output sample at constant frequency
|
|
29
|
+
and duty. The corresponding static harmonic transfer is sinc²(k×frequency/rate),
|
|
30
|
+
where sinc(x)=sin(πx)/(πx). Tests verify this independently using piecewise exact
|
|
31
|
+
Gaussian quadrature and Fourier coefficients, including above-Nyquist harmonics
|
|
32
|
+
folding into the sampled spectrum. This is finite polynomial alias reduction,
|
|
33
|
+
not ideal bandlimiting. Residual aliases and high-frequency attenuation remain.
|
|
34
|
+
The two-sample span denotes the correction support, not a two-sample output delay.
|
|
35
|
+
|
|
36
|
+
Reset is level-sensitive: emit configured phase and then advance on every true
|
|
37
|
+
sample. At default phase, ordinary pulse is0 when the two edge corrections do
|
|
38
|
+
not overlap; near duty endpoints overlap changes that value. A held reset repeats
|
|
39
|
+
phase, while output can still change with frequency/duty. Zero-frequency correction
|
|
40
|
+
is disabled with a safe denominator in every eagerly evaluated branch. Abrupt
|
|
41
|
+
PWM, frequency jumps and resets do not receive a crossing-time correction or
|
|
42
|
+
oversampling guarantee and can alias/click. PWM is not subsample-sync support.
|
|
43
|
+
|
|
44
|
+
Phase uses exact power-of-two-scaled f64 native state, avoiding the native scalar
|
|
45
|
+
flush below 1e-30. The smallest positive f32 frequency accumulates from zero;
|
|
46
|
+
ordinary f64 addition resolution still applies at a nonzero phase. No minimum-Hz
|
|
47
|
+
gate is introduced. At fixed valid controls pulse and triangle stay in [−1,1]
|
|
48
|
+
within float32 numerical tolerance. Sample-rate/config changes require a new graph.
|
|
49
|
+
The 2^1020 phase scale also preserves f64 `Number.MIN_VALUE` initial phase. Stored
|
|
50
|
+
phase is wrapped below 1 before scaling, leaving over 16× finite headroom; all
|
|
51
|
+
waveform and advance arithmetic happens after unscaling.
|
|
52
|
+
|
|
53
|
+
The distinction between step BLEP and slope BLAMP follows the concepts discussed
|
|
54
|
+
in [Esqueda, Välimäki and Bilbao, Rounding Corners with BLAMP, DAFx 2016](https://www.dafx.de/paper-archive/2016/dafxpapers/18-DAFx-16_paper_33-PN.pdf).
|
|
55
|
+
This implementation and its two-sample triangular-kernel coefficients were
|
|
56
|
+
derived independently; the paper's reported performance is not a den claim.
|
|
57
|
+
|
|
58
|
+
## Noise
|
|
59
|
+
|
|
60
|
+
`seededNoise({seed?})` uses the Park–Miller multiplier48271 and modulus2147483647.
|
|
61
|
+
Seed is an integer1..2147483646, default1. For each sample it computes
|
|
62
|
+
next=(current×48271) mod2147483647 using exact f64 integer arithmetic (product
|
|
63
|
+
below 2⁴⁷), stores next as i32, and returns float32(2×next/2147483647−1).
|
|
64
|
+
Float32 rounding can include an endpoint even though the real-valued map is open.
|
|
65
|
+
This deterministic generator is not cryptographic or a quality guarantee for
|
|
66
|
+
all statistical/audio uses; there is no pink/brown noise or noise-shaping mode.
|
|
67
|
+
|
|
68
|
+
`tick(reset)` chooses the configured seed before generating that sample whenever
|
|
69
|
+
reset is true. Reset therefore emits the same first sample; held reset repeats
|
|
70
|
+
it. Independent instances do not share state. Noise sequence is sample-indexed,
|
|
71
|
+
so the same seed/reset samples match at all supported host rates. Snapshot
|
|
72
|
+
continuation is exact for identical graph/config/rate using existing native state.
|
|
73
|
+
|
|
74
|
+
Tests at 44.1/48/96 kHz cover an independent BigInt PRNG oracle, endpoint seeds,
|
|
75
|
+
held reset, isolated instances, bounded finite output, mean/power diagnostics and
|
|
76
|
+
snapshots. Those finite-window statistics do not prove a perfectly white spectrum.
|
|
77
|
+
All generated audio remains CANDIDATE until separate human approval.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Voice policy integration (GEN-616)
|
|
2
|
+
|
|
3
|
+
`src/voice-policy.ts` provides a construction-time fixed-capacity unworklet subgraph. It allocates voices and emits control values; it contains no oscillator, envelope, filter, audio mixing, or MIDI transport. The public package subpath is `@denaudio/den/voice-policy`. The isolated entry consumer checks its types and renders note-on/note-off gate behavior at 44.1/48/96 kHz.
|
|
4
|
+
|
|
5
|
+
Instantiate with a required configuration and an explicit instance name (the same workaround for unworklet #104 used by the entry gate). Call `bindMidi` once in declaration scope with an existing `event.midi({ from: 'main', name: 'midi' })` input. The host sends through unworklet's existing node MIDI API. Alternatively, invoke the graph methods from existing graph event handlers. Arguments are unworklet nodes, not host numbers.
|
|
6
|
+
|
|
7
|
+
| Contract | Behavior |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| `mode` | Required `mono` or `poly`; mono uses one voice. |
|
|
10
|
+
| `capacity` | Poly defaults to 16, configurable 1–32; mono requires 1. |
|
|
11
|
+
| `heldCapacity` | Defaults to 128; configurable from voice capacity through 256. Counts accepted, still-held note-on identities, including stolen notes. |
|
|
12
|
+
| `legato` | Mono defaults to true. True preserves gate and suppresses retriggers when held-note priority changes. False retriggers on changes and fallbacks. |
|
|
13
|
+
| `voices[n].read()` | Graph nodes: `active`, `gate`, integer MIDI `note`, integer `channel`, normalized `velocity`. Inactive note/channel are -1 and velocity is zero. |
|
|
14
|
+
| `voices[n].takeRetrigger()` | Returns and clears a latched start/retrigger. Consume once per sample before the envelope; share that returned node if several consumers need it. |
|
|
15
|
+
| `voices[n].releaseFinished(done)` | Frees a released voice on envelope completion. Never frees a currently held replacement. Call with the envelope's current completion signal. |
|
|
16
|
+
| `noteOn(note, channel, velocity)` / `noteOff(note, channel)` | Channels 0–15, notes 0–127, velocities 0–127. Velocity-zero note-on is note-off. Invalid graph values are ignored. |
|
|
17
|
+
| `allNotesOff(channel)` | Removes held keys for that channel and starts their release; mono may fall back to another channel's held note. |
|
|
18
|
+
| `allSoundOff(channel)` | Removes keys and immediately frees that channel's voices; mono may fall back to another channel. |
|
|
19
|
+
| `reset(when)` | Immediately clears every voice, held identity, pending retrigger and overflow flag when true. Default condition is true. |
|
|
20
|
+
| `overflowed()` | Latched boolean cleared by reset. Signals rejected note-ons when the held ledger is full. |
|
|
21
|
+
|
|
22
|
+
The capacity defaults and ceilings are implementation bounds for the generated graph, not approved product limits. No runtime capacity changes or dynamic allocation are introduced.
|
|
23
|
+
|
|
24
|
+
Poly chooses the lowest-index free slot, then the oldest released slot, then steals the oldest held slot. Allocation and release ranks remain bounded by capacity. Duplicate note-offs consume the oldest outstanding matching channel/note identity. Stolen identities remain in the bounded held ledger, so their later note-offs cannot release a newer duplicate. Mono chooses the most recently held identity and falls back through still-held identities. Duplicate identities retain their own velocity and channel.
|
|
25
|
+
|
|
26
|
+
A full held ledger rejects new note-ons and latches overflow; it does not evict accepted identities. This preserves release bookkeeping for accepted notes. MIDI 1.0 contains no per-note identity, so interleaved note-offs for rejected duplicate pitches cannot be distinguished from accepted duplicate pitches. FIFO pairing is the explicit policy. Reset or channel all-sound-off clears retained identities. Sustain CC64 and other controllers are not implemented; bindMidi handles CC123 and CC120 only.
|
|
27
|
+
|
|
28
|
+
Unworklet 0.4.1 drains MIDI at block boundaries in FIFO order, before processing samples. Generic control messages drain before MIDI in the same block. This module preserves those semantics; it does not add a sample-accurate event scheduler. Multiple starts in a block coalesce to one retrigger latch and the final voice assignment. An on/off pair in one block may produce a retrigger with gate already low; envelope integration must define that case. Do not claim sample-accurate MIDI from these tests.
|
|
29
|
+
|
|
30
|
+
All voice and held-key storage uses unworklet transient state. Snapshots exclude live held notes. A fresh instance therefore starts empty, while native in-place restore leaves currently held notes unchanged. Use explicit reset for a cold live restore; no new snapshot/preset serializer is provided. A reset is the explicit panic operation. Completion signals belong to the current envelope attached to each slot; a delayed completion from a previous released assignment must not be reused after a steal and subsequent release.
|
|
31
|
+
|
|
32
|
+
## Verification
|
|
33
|
+
|
|
34
|
+
The numerical fixture renders voice state through diagnostic outputs using published unworklet 0.6.0. It checks FIFO duplicates, stolen-note releases, release reuse, mono priority/legato/retrigger, channel controls, reset, independent instances, bounded overflow and repeated reuse. These outputs are diagnostics, not audition audio or approved goldens. Offline 44.1/48/96 kHz tests are separate from the [browser entry coverage](dependency-findings.md#browser-sample-rate-and-render-quantum).
|
|
35
|
+
|
|
36
|
+
Upstream [#99](https://github.com/yuichkun/unworklet/issues/99) was fixed in unworklet 0.5.0: `expectMidiBalance` treats velocity-zero note-on as note termination. The 0.6.0 regression accepts a positive-velocity note-on followed by velocity-zero note-on, and rejects both a hanging note and an additional unmatched note-off. Den retains its independent numerical gate/identity assertions. The legacy half-sample duration margin remains in this fixture; exact-duration behavior is checked separately in the [dependency regressions](dependency-findings.md).
|
|
37
|
+
|
|
38
|
+
Capacity probes also exposed recursive expression expansion in unworklet's analysis of nested selects. Transient scratch accumulators keep the traversal and selection expressions shallow. Before this change the default-capacity test was stopped after more than one minute of CPU work; afterward the full numerical suite including capacity probes ran in seconds. A 64-voice/256-held diagnostic graph failed WebAssembly compilation with `local count too large`; the supported construction ceiling is therefore 32 voices, tested with 256 held identities. Larger combined instrument graphs still require their own compile and runtime budget checks. The current release-completion path performs rank maintenance across slots; passing these fixtures does not establish a real-time CPU budget for a complete instrument.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Prepared pitch-band wavetable candidate
|
|
2
|
+
|
|
3
|
+
`@denaudio/den/wavetable` additionally exports `prepareWavetableBands`,
|
|
4
|
+
`bandedWavetableSource`, `PreparedWavetableBands`, `WavetableBandsOptions` and
|
|
5
|
+
`BandedWavetableConfig`. The unbanded `wavetableSource` retains its layout and
|
|
6
|
+
control contract with the cycle-local precision correction documented separately.
|
|
7
|
+
Oscillator and instrument behavior is unchanged. This candidate implements the original catalog group's
|
|
8
|
+
host-prepared pitch-band assets and shared native reader; it is not an approved
|
|
9
|
+
sound or a claim of equivalence to another synth.
|
|
10
|
+
|
|
11
|
+
## Host preparation and provenance
|
|
12
|
+
|
|
13
|
+
Call `prepareWavetableBands({ frames })` on the host, outside audio processing.
|
|
14
|
+
Supply 1..16 `Float32Array` mono cycles of equal power-of-two length 16..4096.
|
|
15
|
+
A cycle contains no duplicate final endpoint. All input samples must be finite.
|
|
16
|
+
The helper does not decode files, load assets or assume a license for user data.
|
|
17
|
+
Tests use original analytic sine/cosine combinations, silence and synthetic
|
|
18
|
+
finite/nonfinite edge fixtures; no third-party audio or proprietary tables.
|
|
19
|
+
|
|
20
|
+
A host-only radix-2 FFT obtains each cycle's complex spectrum. The first band's
|
|
21
|
+
positive harmonic limit is N/2−1; each next limit is floor(previous/2), ending at 1.
|
|
22
|
+
DC is retained in every band. Only conjugate pairs through that limit are kept;
|
|
23
|
+
the table Nyquist bin is omitted. Explicit conjugate symmetry and inverse FFT
|
|
24
|
+
produce real cycles. Original DC, retained gain and phase are preserved within
|
|
25
|
+
FFT/float32 roundoff. There is no peak or per-band normalization. Finite source
|
|
26
|
+
PCM can overshoot after harmonic truncation: if any reconstructed value becomes
|
|
27
|
+
nonfinite when converted to float32, preparation throws `RangeError`, without
|
|
28
|
+
clamping or returning a partially prepared asset. Nonfinite input also throws.
|
|
29
|
+
The input cycles are never modified.
|
|
30
|
+
|
|
31
|
+
The result is an ordinary local object `{data, frameLength, frameCount,
|
|
32
|
+
harmonicLimits}`. `harmonicLimits` is frozen, descending and explanatory; it is
|
|
33
|
+
not a transport protocol. Native construction derives the same fixed schedule.
|
|
34
|
+
Editing a source frame means preparing it again and loading the resulting full
|
|
35
|
+
PCM through the existing resident. This is not a new serializer or asset manager.
|
|
36
|
+
|
|
37
|
+
## Fixed layout, capacity and ingress
|
|
38
|
+
|
|
39
|
+
The resulting `data` is band-major then frame-major: band0/frame0 cycle,
|
|
40
|
+
band0/frame1 cycle, …, band1/frame0 cycle, …. Required PCM length is
|
|
41
|
+
N × morph-frame count × band count. Both host preparation and native construction
|
|
42
|
+
reject layouts larger than 65536 samples. Native construction also rejects a
|
|
43
|
+
resident with insufficient capacity. There are 3..11 bands. Examples:
|
|
44
|
+
|
|
45
|
+
- 512 samples × 16 frames × 8 bands = 65536 samples, the full 256 KiB resident
|
|
46
|
+
- 1024 × 7 × 9 = 64512 samples
|
|
47
|
+
- 2048 × 3 × 10 = 61440 samples
|
|
48
|
+
- 4096 × 1 × 11 = 45056 samples
|
|
49
|
+
|
|
50
|
+
Host preparation allocates the output plus four reused float64 work arrays,
|
|
51
|
+
32N bytes (at most 128 KiB), excluding the caller's original frames and ordinary
|
|
52
|
+
object overhead. Transform cost is O(frameCount × bandCount × N log N).
|
|
53
|
+
No FFT, arbitrary JS, allocation or user callback is invoked by the native tick.
|
|
54
|
+
|
|
55
|
+
Instantiate the existing `/sample` `residentSample`, then pass it with matching
|
|
56
|
+
`frameLength`/`frameCount` to `bandedWavetableSource`. Load `prepared.data` with the
|
|
57
|
+
resident's established native `event<{data:Float32Array}>` and `sample.load(data)`.
|
|
58
|
+
The native event copies PCM at a block boundary. For the full layout the existing
|
|
59
|
+
CAPACITY_16 ingress and 262144-byte payload reservation contribute 4 MiB of event
|
|
60
|
+
arena, in addition to the 256 KiB resident and native I/O/state/metadata. The reader
|
|
61
|
+
adds eight integer resident reads (16 underlying PCM taps), four local cycle
|
|
62
|
+
interpolations and 76 bytes of declared scalar
|
|
63
|
+
state before native alignment. Multiple sources can share the resident/ingress.
|
|
64
|
+
There is no extra native PCM bank, new loader, streaming or upstream patch.
|
|
65
|
+
|
|
66
|
+
A short or empty load is entirely silent with `missing=true`. Extra PCM after
|
|
67
|
+
the required layout is ignored. Construction dimensions stay fixed; replacing
|
|
68
|
+
with a different-dimensional asset requires a matching graph or intentionally
|
|
69
|
+
adapted data. Length alone cannot certify harmonic content or identify a same-size
|
|
70
|
+
layout mismatch. The caller must load the matching prepared layout. Arbitrary
|
|
71
|
+
PCM is still protected by the resident's nonfinite-tap-to-zero rule; the native
|
|
72
|
+
reader does not validate its spectrum or metadata.
|
|
73
|
+
|
|
74
|
+
## Pitch bands, frame scan and state
|
|
75
|
+
|
|
76
|
+
The controls and result match `WavetableControls`: `{frequencyHz, frame, reset}`
|
|
77
|
+
and `{output, missing}`. The source rate is finite 8000..192000 Hz, fixed at
|
|
78
|
+
construction. Resident `sourceSampleRate` does not affect cyclic pitch.
|
|
79
|
+
|
|
80
|
+
`frequencyHz` is clipped to [0,.45 × sampleRate]. NaN/negative/negative infinity
|
|
81
|
+
become0; positive infinity saturates at the upper limit. Zero Hz holds phase.
|
|
82
|
+
`frame` is a fractional index clipped to [0,frameCount−1], with NaN→0. Neighboring
|
|
83
|
+
frames interpolate linearly without frame-selector wrap or control smoothing.
|
|
84
|
+
`phaseCycles` is the finite initial/reset phase in [0,1), default 0.
|
|
85
|
+
|
|
86
|
+
For each bright band with limit H, its transition to the next darker band starts
|
|
87
|
+
at .225 × sampleRate/H and finishes at .45 × sampleRate/H. The mix is linear in
|
|
88
|
+
frequency. Transitions do not overlap; any gap holds the darker band. The last
|
|
89
|
+
H=1 band is held through the accepted .45 × sampleRate frequency cap. Thus every
|
|
90
|
+
represented positive table harmonic with nonzero band weight is below or equal
|
|
91
|
+
to .45 × sampleRate for static playback. This statement concerns the retained
|
|
92
|
+
base harmonics only, not the images of linear table interpolation.
|
|
93
|
+
|
|
94
|
+
The reader retains each local fractional phase before adding the cycle base,
|
|
95
|
+
fetches two integer-position resident reads per cycle, interpolates periodically
|
|
96
|
+
within each of four cycles, then
|
|
97
|
+
interpolates their two frame pairs and the pitch bands. Each cycle wraps to its
|
|
98
|
+
own first sample, never to another frame/band. There is no output limiter or gain
|
|
99
|
+
normalization. Pitch/frame control changes are instantaneous; rapidly jumping
|
|
100
|
+
bands or frames is not click-free. Band transition continuity is a control-domain
|
|
101
|
+
property, not a promise that arbitrary modulation adds no bandwidth.
|
|
102
|
+
|
|
103
|
+
Reset is level-sensitive: emit the initial phase on that sample, then advance.
|
|
104
|
+
A held reset repeats the initial phase while pitch-band/frame selection can vary.
|
|
105
|
+
Every resident revision, including equal-length replacement and signed integer
|
|
106
|
+
wrap, also restarts phase. There is no asset-replacement fade. Phase advances
|
|
107
|
+
while an asset is missing; a subsequent load restarts it. Tiny frequencies and
|
|
108
|
+
frame mixes use exact binary-scaled f64 control state; phase uses 2^1020 so even
|
|
109
|
+
f64 `Number.MIN_VALUE` survives native scalar flushing. Arithmetic is unscaled
|
|
110
|
+
before combining with PCM, and all declared state remains finite.
|
|
111
|
+
|
|
112
|
+
Native snapshots include resident PCM, revision and reader state. Exact
|
|
113
|
+
continuation is tested only for identical graph/configuration/sample rate. No new
|
|
114
|
+
snapshot API or cross-schema/rate migration is promised.
|
|
115
|
+
|
|
116
|
+
## Quality boundary and evidence
|
|
117
|
+
|
|
118
|
+
Pitch bands reject high source harmonics that otherwise fold at high static
|
|
119
|
+
pitch. Linear interpolation still attenuates retained harmonics and generates
|
|
120
|
+
image aliases. The test suite explicitly measures one such residual image
|
|
121
|
+
instead of labeling the reader brickwall-bandlimited. Rapid frame scan, FM/PM,
|
|
122
|
+
reset, sync, warp and asset replacement can add bandwidth; static truncated
|
|
123
|
+
assets do not make them alias-free. No automatic oversampling is added.
|
|
124
|
+
|
|
125
|
+
Independent direct real-DFT preparation checks and Fourier coefficients verify
|
|
126
|
+
retained/removed harmonics, DC/gain/phase, silent/edited frames and rejection of
|
|
127
|
+
float32 overflow. Native tests cover 44.1/48/96 kHz, sweep and every band-transition
|
|
128
|
+
boundary, frame morph, control extremes, held reset/zero Hz, missing/short/edited
|
|
129
|
+
native assets, revision wrap, invalid/subnormal PCM, tiny phase/frequency/morph,
|
|
130
|
+
full capacity and exact snapshot continuation. The packed isolated consumer
|
|
131
|
+
imports only public `/wavetable` and `/sample`, checks strict types, loads full
|
|
132
|
+
native assets, renders and compares an independent piecewise reader oracle,
|
|
133
|
+
checks spectral content and replacement, and records source/package/audio hashes.
|
|
134
|
+
|
|
135
|
+
Generated WAVs and manifests are CANDIDATE. Compile-driver memory/timing is
|
|
136
|
+
reported with its unloaded-state limitation; loaded offline ingress/render tests
|
|
137
|
+
are separate. These are neither human listening approval nor browser/hardware
|
|
138
|
+
real-time performance proof.
|