@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
package/docs/sources.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Candidate source and resonator catalogue
|
|
2
|
+
|
|
3
|
+
These modules extend the catalogue's FM/PM, additive, unison and tuned/modal resonator groups. They use unworklet 0.4.1 and the existing oscillator and delay-readhead. Existing instrument, voice policy, envelopes and initial presets are unchanged. Compose their outputs with the existing envelope/filter/voice instead of introducing another engine framework.
|
|
4
|
+
|
|
5
|
+
All new audio is **CANDIDATE**, not a listening-approved golden. These modules are not a commercial-instrument equivalence claim. Sample, granular, spectral/FFT, arbitrary operator routing, automatic anti-aliasing and stochastic exciters are outside this slice.
|
|
6
|
+
|
|
7
|
+
## Shared contract
|
|
8
|
+
|
|
9
|
+
Import sources from `@denaudio/den/source` and resonators from `@denaudio/den/resonator`. Construct with `instantiate(module, config, {name})` inside processor declarations. Call each instance's `tick` exactly once per sample. `sampleRate` is an immutable finite number in [8000,192000], and must match the enclosing renderer. Offline evidence covers 44100, 48000 and 96000 Hz. Use separate named instances for separate voices/channels.
|
|
10
|
+
|
|
11
|
+
All runtime controls and excitation must be finite f32 values. NaN/Infinity are outside the contract, not a musical mute/reset signal; upstream scrubbing is not validation or a quality guarantee. Out-of-range finite controls clamp as described below. Construction rejects invalid/nonfinite configuration and capacities. Core f32 precision and scalar-state flushing below 1e-30 still apply; no subnormal oscillator-frequency accumulation guarantee is made. No runtime allocation, arbitrary JavaScript callback, unbounded loop, asset load, smoothing or voice allocation is added. All loop counts are fixed at graph construction.
|
|
12
|
+
|
|
13
|
+
The existing unworklet snapshot mechanism owns state. Same configuration/schema snapshots resume phases, feedback, modes and delay history exactly in the tested renderer. Reset is a sample-level boolean, not an edge detector. A caller wanting one reset should supply one true sample. Rate or structural configuration changes require a new graph; no cross-rate/cross-schema migration guarantee is made.
|
|
14
|
+
|
|
15
|
+
## Phase modulation
|
|
16
|
+
|
|
17
|
+
`phaseModulation({sampleRate}).tick({carrierHz, modulatorHz, depthRadians, feedbackRadians, reset})` returns mono f32.
|
|
18
|
+
|
|
19
|
+
- Carrier/modulator frequencies clamp to [0,.45 sampleRate] Hz. Zero Hz holds phase.
|
|
20
|
+
- Depth clamps to [-8π,8π] **radians**. Feedback clamps to [-π,π] radians.
|
|
21
|
+
- At sample n, output is `sin(2π p[n] + depth[n] sin(2π m[n]) + feedback[n] y[n-1])`. The modulator and previous output are f32; accumulated phase and sine polynomial arithmetic use f64.
|
|
22
|
+
- Output precedes phase advance by each current frequency/sampleRate. A frequency change preserves phase. Reset sets both phases and the previous carrier output to zero before this output. Held reset emits zero every sample. Initial phase and feedback are zero; no random seed or random phase is used.
|
|
23
|
+
- Feedback is explicitly the final carrier output from the preceding sample, not current-sample algebraic feedback and not the modulator.
|
|
24
|
+
|
|
25
|
+
## Frequency modulation
|
|
26
|
+
|
|
27
|
+
`frequencyModulation({sampleRate}).tick({carrierHz, modulatorHz, deviationHz, feedbackHz, reset})` returns mono f32.
|
|
28
|
+
|
|
29
|
+
- Base and modulator Hz clamp to [0,.45 sampleRate]. Deviation and feedback clamp to ±.45 sampleRate **Hz**.
|
|
30
|
+
- Emit `sin(2π p[n])`, then advance the carrier by `clamp(baseHz + deviationHz sin(2π m[n]) + feedbackHz y[n-1], 0, .45 sampleRate)/sampleRate`.
|
|
31
|
+
- This is positive-frequency linear FM with clipped negative instantaneous frequency, not through-zero FM. It is distinct from PM radians, including at changing depth and frequency.
|
|
32
|
+
- Initialization, zero-frequency hold, reset and one-sample feedback source match PM. Held reset emits zero. There are two oscillator phases and one previous-output slot per PM/FM instance.
|
|
33
|
+
|
|
34
|
+
Neither PM nor FM is bandlimited. Their legal frequency clamps do **not** constrain sidebands to Nyquist. Strong phase modulation, clipped FM, reset and feedback can alias. The fixture explicitly measures a folded PM sideband above 0.4 amplitude with legal carrier/modulator controls; this is evidence of the limitation, not an anti-alias test pass. Feedback can produce sensitive/chaotic waveform divergence while output remains in [-1,1].
|
|
35
|
+
|
|
36
|
+
## Additive partial bank
|
|
37
|
+
|
|
38
|
+
`additiveSource({sampleRate, partials:[{ratio,gain}, ...]}).tick(frequencyHz, reset)` returns mono f32.
|
|
39
|
+
|
|
40
|
+
- 1–32 immutable sine partials. Ratio is finite in (0,128], signed gain in [-16,16]. Each reuses the existing sine oscillator.
|
|
41
|
+
- Base frequency clamps [0,.45 sampleRate]. Requested partial frequency is base×ratio. A partial above .45 sampleRate is muted rather than accumulated at the clamp frequency. Its oscillator still advances at the existing oscillator's clamped frequency while muted, so re-entry does not restart phase.
|
|
42
|
+
- All output weights are divided by the construction-time sum of absolute gains. All-zero gains produce silence. The denominator does not change when partials mute. Thus the output magnitude is at most one, without a limiter.
|
|
43
|
+
- All phases initially zero; reset aligns all partial phases to zero, held reset emits zero. Each partial owns independent f64 phase and has fixed per-sample cost, even when muted or weighted zero.
|
|
44
|
+
- Crossing a partial mute boundary is abrupt and unsmoothed. Dynamic modulation/reset can generate extra bandwidth; this is not a blanket anti-alias claim.
|
|
45
|
+
|
|
46
|
+
## Unison stereo bank
|
|
47
|
+
|
|
48
|
+
`unisonSource({sampleRate, waveform:'sine'|'saw', voices:[{detuneCents,pan}, ...]}).tick(frequencyHz, reset)` returns `{left,right}`.
|
|
49
|
+
|
|
50
|
+
- 1–8 immutable oscillator voices. Detune cents is finite [-1200,1200], pan [-1,1]. Each voice reuses the existing sine or polynomial-BLEP saw oscillator.
|
|
51
|
+
- Base frequency clamps [0,.45 sampleRate], then each voice multiplies by `2^(cents/1200)` and clamps again to .45 sampleRate. At high base frequencies, positive detuned voices can coincide at that ceiling; tests explicitly cover this.
|
|
52
|
+
- For N voices, left weight is `(1-pan)/(2N)`, right weight `(1+pan)/(2N)`. This is **linear amplitude pan**, not equal-power pan. The channel sum L+R equals the average of the mono voices; an arithmetic mono fold-down `(L+R)/2` is half that average. Centered one-voice output is half amplitude per channel. Each channel remains bounded by one.
|
|
53
|
+
- All phases initially zero and align on reset, with no random dispersion. Sine held reset emits zero; saw held reset emits the existing oscillator's phase-zero value (zero for positive frequency; -1 at exactly zero frequency). Frequency changes preserve phase.
|
|
54
|
+
- A saw's existing BLEP does not imply arbitrary audio-rate modulation, feedback or resets are alias-free. No new quality mode is advertised.
|
|
55
|
+
|
|
56
|
+
## Modal resonator
|
|
57
|
+
|
|
58
|
+
`modalResonator({sampleRate, modes:[{frequencyHz,decaySeconds,gain}, ...]}).tick(input, reset)` returns mono f32.
|
|
59
|
+
|
|
60
|
+
- 1–16 immutable modes. Mode Hz in [20,.45 sampleRate], amplitude T60 in [.005,30] seconds, gain in [-16,16]. Excitation clamps to [-1,1].
|
|
61
|
+
- A mode has complex poles `r exp(±jω)`, where `r=exp(-ln(1000)/(T60 sampleRate))`, `ω=2π Hz/sampleRate`. Recurrence is `y[n]=sin(ω)x[n]+2r cos(ω)y[n-1]-r²y[n-2]`.
|
|
62
|
+
- Unit impulse response is `r^n sin(ω(n+1))`. It starts on the current sample; T60 is the pole envelope decay, not a guaranteed peak ratio between arbitrary sampled extrema. The magnitude-response peak can differ from pole angle slightly.
|
|
63
|
+
- Signed mode gains divide by their absolute sum. All-zero gains return silence. This bounds the **unit-impulse** response by one, not sustained resonant output. Sustained excitation can exceed unity by large amounts. With bounded input, a conservative weighted bound is the largest `1/(1-r)`. Long decay requires substantial downstream headroom/gain control; no limiter is included.
|
|
64
|
+
- Reset clears both histories per mode before current excitation; held reset emits only the zero-history `sin(ω)×input` contribution each sample. Initial histories are zero. Two fixed f64 slots per mode store histories scaled by 2^128 to preserve legitimate tiny f32 tails through the upstream scalar-state flush; external units remain unchanged.
|
|
65
|
+
- The modes form a fixed linear resonator bank after an explicitly clipped excitation stage, not a nonlinear physical string/plate model. Excitation waveform, envelope, velocity mapping and musical mode choices belong to the composition.
|
|
66
|
+
|
|
67
|
+
## Tuned comb
|
|
68
|
+
|
|
69
|
+
`tunedComb({sampleRate,minFrequencyHz}).tick({input,frequencyHz,feedback,damping,reset})` returns mono f32.
|
|
70
|
+
|
|
71
|
+
- Minimum frequency is finite [20,.45 sampleRate]. Fixed buffer capacity is `ceil(sampleRate/minFrequencyHz)+2` f32 samples plus cursor/validity and damping state. Tick frequency clamps [minFrequencyHz,.45 sampleRate]. Delay is 1/frequencyHz seconds through the existing moving, linear-interpolated delay reader. Actual minimum delay exceeds two samples at the maximum frequency, and is always at least one sample.
|
|
72
|
+
- Excitation is passed linearly, without a clipper. Supply finite input bounded by a known magnitude B with adequate f32 headroom. Feedback clamps [-.999,.999]. Damping clamps [0,1] and is the one-pole memory coefficient: `filtered=(1-damping)tap+damping*previousFiltered`. Zero bypasses damping; one freezes the filter history (zero when just initialized/reset).
|
|
73
|
+
- Read the delayed tap, update damping, then write `(1-|feedback|)×input+feedback×filtered` into the line. Normalization is applied to excitation before storage. Return the raw delayed tap, with no instantaneous dry path. For fixed feedback this equals output-normalizing the conventional feedback comb, but changing feedback never rescales stored history. Reset invalidates history and zeroes damping **before** current excitation is written; held reset returns zero.
|
|
74
|
+
- Input bounded by B, convex damping/interpolation and `|feedback|≤.999` give the ideal-arithmetic induction `|write|≤(1-|g|)×B+|g|×B=B`, including arbitrary finite feedback/frequency/damping changes. Reset/empty history is zero, so history, filter and output remain within B in ideal arithmetic. Unit-input tests use a 0.001 floating-point headroom allowance (scaled with B) and an independent normalized-history oracle and a retained executable counterexample for the rejected output-normalization topology; this is a normalized feedback network, not a brickwall limiter. Parameter edits still change the sound and can click.
|
|
75
|
+
- Integer-delay undamped positive feedback has repeats every delay samples; negative feedback reverses alternate repeats. Fractional interpolation and damping change pitch and decay, so `frequencyHz` is the delay tuning control rather than a pitch-perfect physical-string guarantee. Moving delay produces Doppler behavior. Abrupt parameter changes/reset need caller-managed transitions if clicks are unwanted.
|
|
76
|
+
|
|
77
|
+
## Verification and evidence
|
|
78
|
+
|
|
79
|
+
`tests/source.spec.ts` independently checks Math.sin recurrence, Bessel sidebands, intentional folded alias, 32-partial peaks, detune ceilings, stereo normalization, reset, held reset and snapshots. `tests/resonator.spec.ts` checks analytical impulse/pole/T60 behavior, maximum 16-mode sustained excitation/headroom, tiny tails, signed comb repeats, damping freeze, moving-delay/feedback changes, fixed capacities and snapshots. Every in-contract render requires zero upstream scrubbed samples.
|
|
80
|
+
|
|
81
|
+
`tests/source-packed.test.mjs` installs a fresh packed package using an exact consumer lock, checks public declarations/imports, renders all families at three rates, compares analytical references, verifies snapshot continuation, and records package/source/audio hashes and fixed-graph driver diagnostics. Evidence is local offline/Node driver evidence only, not browser/hardware real-time acceptance. The integration owner must still run exact-head CI and independent review. Candidate WAVs remain unapproved.
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# Bounded hybrid space and feedforward pitched tail: proposed entry
|
|
2
|
+
|
|
3
|
+
Status: entry, source and native/public-package evidence independently reviewed
|
|
4
|
+
on 2026-10-06. Both entries remain CANDIDATE / NOT_CLEARED, with no human,
|
|
5
|
+
hardware or realtime acceptance.
|
|
6
|
+
|
|
7
|
+
## Finite catalog scope
|
|
8
|
+
|
|
9
|
+
This is one concrete mono-input/stereo-output composition for catalog groups 10
|
|
10
|
+
and 12, plus one explicitly feedforward pitched-tail variation. It composes the
|
|
11
|
+
reviewed `partitionedConvolution`, `multiTapDelay`, `algorithmicReverb` and
|
|
12
|
+
`windowedPitchShift` modules without modifying them. The convolution shapes three
|
|
13
|
+
early reflections; the parallel damped FDN supplies the late tail. The variation
|
|
14
|
+
pitch-shifts only a copy of the late output. Nothing from either pitch reader is
|
|
15
|
+
returned to the FDN or early-reflection input.
|
|
16
|
+
|
|
17
|
+
This is not regenerative feedback shimmer, a long measured-room convolution, a
|
|
18
|
+
physical room model, transparent pitch shifting, or completion of those broader
|
|
19
|
+
catalog categories. Existing frozen-tail support remains separate.
|
|
20
|
+
|
|
21
|
+
## Proposed public API
|
|
22
|
+
|
|
23
|
+
One new subpath, `@denaudio/den/spatial-chains`; no root-entry re-export:
|
|
24
|
+
|
|
25
|
+
- `hybridReverb({ sampleRate, renderQuantumSize }).tick(input, controls, everyNSamples)` returns
|
|
26
|
+
`{ left, right }`.
|
|
27
|
+
- `feedforwardPitchedReverb({ sampleRate, renderQuantumSize, windowSamples? }).tick(input,
|
|
28
|
+
controls, everyNSamples)` returns `{ left, right, ratioRejected }`.
|
|
29
|
+
- `SpatialChainConfig`: integer `sampleRate` in 8000..192000, taken from the
|
|
30
|
+
enclosing `ctx.sampleRate`, and positive integer `renderQuantumSize` from
|
|
31
|
+
`ctx.renderQuantumSize` for the early convolution's snapshot-safe dispatch.
|
|
32
|
+
- `FeedforwardPitchedReverbConfig`: same rate plus even integer `windowSamples`
|
|
33
|
+
in 32..16384, default 2048. This is the pitch delay excursion, not a grain or
|
|
34
|
+
FFT-frame duration.
|
|
35
|
+
- `SpatialChainControls`: f32 `mix`, boolean `bypass`, boolean `reset`.
|
|
36
|
+
- `FeedforwardPitchedReverbControls`: the same controls plus f32 `ratio`, f32
|
|
37
|
+
`pitchMix`, and boolean `retrigger`.
|
|
38
|
+
|
|
39
|
+
Call exactly once per sample in a stride-1 `forSample`, passing its native
|
|
40
|
+
`everyNSamples`. Construction is fixed. There is no IR ingress, runtime topology
|
|
41
|
+
change, alternate scheduler, preset engine or custom snapshot mechanism.
|
|
42
|
+
|
|
43
|
+
`mix` and `pitchMix` must be finite and clamp to [0,1]. `ratio` uses the reviewed
|
|
44
|
+
pitch reader's inclusive [.5,2] acceptance; invalid/nonfinite values retain its
|
|
45
|
+
last accepted ratio, or 1 on reset. A valid ratio is accepted during reset.
|
|
46
|
+
`ratioRejected` describes rejection of the current ratio. Both channel readers
|
|
47
|
+
receive identical controls and therefore have the same phase and rejection.
|
|
48
|
+
|
|
49
|
+
## Literal topology and gains
|
|
50
|
+
|
|
51
|
+
Let x be the input unless bypass or reset is true, in which case excitation is
|
|
52
|
+
zero. The unchanged public modules are composed as follows:
|
|
53
|
+
|
|
54
|
+
1. Convolve x with B=8, N=16, one fixed eight-tap original FIR:
|
|
55
|
+
h=[.625,0,0,.25,0,0,0,-.125]. Its L1 gain is 1 and DC gain is .75.
|
|
56
|
+
2. Feed that convolution to `multiTapDelay` with `maxDelaySeconds=.023`,
|
|
57
|
+
feedback=0, mix=1 and bypass=false. Its three fixed taps are:
|
|
58
|
+
(.007 seconds, left .5, right .25),
|
|
59
|
+
(.013 seconds, left .25, right -.25),
|
|
60
|
+
(.023 seconds, left -.125, right .375).
|
|
61
|
+
The existing normalization divisor is exactly 1; each channel's L1 sum is
|
|
62
|
+
.875. Call this stereo early output E.
|
|
63
|
+
3. Independently feed (x,0) to `algorithmicReverb` with roomScale=1,
|
|
64
|
+
decaySeconds=.6, dampingHz=min(3500,.4*sampleRate), mix=1, bypass=false.
|
|
65
|
+
Call this stereo late output F. No early signal enters the FDN.
|
|
66
|
+
4. Plain hybrid wet output is .5*E+.5*F.
|
|
67
|
+
5. The pitched variation applies one reviewed moving-window reader to each F
|
|
68
|
+
channel. Let that output be Q(F). Its late blend is
|
|
69
|
+
T=(1-pitchMix)*F+pitchMix*Q(F), and wet=.5*E+.5*T.
|
|
70
|
+
6. The final output is (1-mix)*originalInput+mix*wet, with mono dry copied to
|
|
71
|
+
both channels. Use f64 for the wrapper blend and convert once to f32.
|
|
72
|
+
|
|
73
|
+
The plain entry has no pitch readers or pitch allocation. The two entries may
|
|
74
|
+
share a private construction helper, not a new exported routing abstraction.
|
|
75
|
+
|
|
76
|
+
Supply finite normalized input [-1,1]. The FIR output fits the existing
|
|
77
|
+
multitap/convolution operating range and |E|<=.875*peak|input| in real arithmetic.
|
|
78
|
+
Start auditions around input peak .25 to leave working headroom; this is a
|
|
79
|
+
recommended level, not a proved output ceiling. FDN peaks can exceed unity.
|
|
80
|
+
There is no limiter, automatic normalization or output saturation.
|
|
81
|
+
|
|
82
|
+
The unmodified FDN keeps its fixed contractive feedback and damping. For a
|
|
83
|
+
finite-energy mono input from zero state its existing conservative wet L2 bound
|
|
84
|
+
uses 1/(1-qMax), where qMax=10^(-3*dMin/(sampleRate*.6)). Pitch is strictly outside
|
|
85
|
+
that loop. Convex interpolation/window weights bound each pitched sample by the
|
|
86
|
+
largest retained late sample, but do not imply energy preservation: a moving
|
|
87
|
+
reader can repeat, omit or cancel input. Do not transfer the FDN's orthogonality
|
|
88
|
+
claim to a hypothetical pitch-in-feedback implementation.
|
|
89
|
+
|
|
90
|
+
## Timing, phase and finite storage
|
|
91
|
+
|
|
92
|
+
Dry is immediate. There is no host latency compensation or hidden dry alignment.
|
|
93
|
+
The early FIR contributes exactly 8 samples of scheduling latency. It is not
|
|
94
|
+
linear phase, so its coloration has frequency-dependent group delay. Its impulse
|
|
95
|
+
support is 0..7 before that scheduling delay. Reflection tap times are fixed
|
|
96
|
+
f32-seconds requests through the reviewed linear readhead, not exact integer
|
|
97
|
+
delays: effective d=clamp(sampleRate*fround(seconds),1,.023*sampleRate).
|
|
98
|
+
Their fractional interpolation must be included in the numerical oracle.
|
|
99
|
+
|
|
100
|
+
The first ideal early support is at 8+floor(d7ms). The last ideal early support
|
|
101
|
+
after a last nonzero input at n is no later than n+8+7+ceil(d23ms), subject to
|
|
102
|
+
native floating-point residuals. This 7..23 ms reflection geometry does not
|
|
103
|
+
rescale the eight FIR samples with rate.
|
|
104
|
+
|
|
105
|
+
The FDN's four lengths are round(sampleRate*[.0297,.0371,.0411,.0437]). Its f32
|
|
106
|
+
seconds readhead boundary can introduce the existing tiny fractional difference.
|
|
107
|
+
First late arrival is near 29.7 ms, and the damping filters add frequency-dependent
|
|
108
|
+
loop group delay. The .6 second setting specifies loop loss, not a hard tail
|
|
109
|
+
cutoff or exact measured RT60. There is no finite mathematical drain for the
|
|
110
|
+
IIR tail. Continue processing zeros or reset it.
|
|
111
|
+
|
|
112
|
+
Pitched read ages are 1..W+1 after the FDN output. After reset/retrigger, ratio=1
|
|
113
|
+
gives exactly 1+W/2 samples of extra delay. Other ratios, or unity after arbitrary
|
|
114
|
+
modulation, have no single latency. Parallel pitched/unpitched mixing may comb or
|
|
115
|
+
cancel. Linear reads can alias; coherent and noncoherent pitch behavior must both
|
|
116
|
+
be retained as evidence. If the late input to a reader actually becomes zero,
|
|
117
|
+
that reader is silent from its last nonzero sample+W+2 onward; the FDN itself has
|
|
118
|
+
no such finite cutoff.
|
|
119
|
+
|
|
120
|
+
Storage is the sum of unchanged modules:
|
|
121
|
+
|
|
122
|
+
- Three reflection histories, each ceil(.023*sampleRate)+2 f32 values.
|
|
123
|
+
- Four FDN histories, each ceil(sampleRate*(d_i/sampleRate))+2 f32 values,
|
|
124
|
+
including any existing floating-point ceiling guard; four damping states.
|
|
125
|
+
- One B8/P1 convolution: 8 f64 input, 2*16 f64 previous spectrum, 16 f64 overlap,
|
|
126
|
+
2*16 f64 mixed scratch and 4*16 f64 FFT scratch values, plus native scalar state.
|
|
127
|
+
- Pitched entry only: two (W+3)-value f64 histories, i.e. 16*(W+3) bytes, plus
|
|
128
|
+
native cursors, valid counts, phases, accepted ratios and transient wet slots.
|
|
129
|
+
|
|
130
|
+
These are construction-fixed. Actual compiled/native instance memory includes
|
|
131
|
+
runtime, scalar and port overhead and will be measured. No callback allocation
|
|
132
|
+
or buffer growth is introduced. The FDN/reflection f32 histories inherit their
|
|
133
|
+
existing very-small-state precision/scrub behavior; this wrapper does not promise
|
|
134
|
+
retention of all subnormal input magnitudes.
|
|
135
|
+
|
|
136
|
+
## Controls, reset, bypass and native restoration
|
|
137
|
+
|
|
138
|
+
Reset has highest priority: emit zero even when bypass is true, suppress the
|
|
139
|
+
current excitation, invalidate every module's history and reset pitch phase.
|
|
140
|
+
Held reset remains silent and discards every reset sample. Native convolution
|
|
141
|
+
hop phase is preserved. This explicit wrapper behavior is stricter than the
|
|
142
|
+
underlying FDN/multitap reset-accepts-current-input contract.
|
|
143
|
+
|
|
144
|
+
Bypass emits unity dry, admits no new wet excitation, and continues all internal
|
|
145
|
+
tail/readhead processing. It does not save CPU; removing bypass reveals the then
|
|
146
|
+
current tail. mix=0 still excites and advances both wet branches. pitchMix=0 still
|
|
147
|
+
advances the pitched entry's readers. Retrigger resets both pitch phases before
|
|
148
|
+
their current read, retains history and can click. No smoothing or universally
|
|
149
|
+
click-free automation, reset, retrigger or bypass is claimed.
|
|
150
|
+
|
|
151
|
+
Use only native same-rate, same-quantum, same-schema snapshots. Every inherited persistent
|
|
152
|
+
history/cursor/valid count, convolution scheduling state, FDN damping state and
|
|
153
|
+
pitch ratio/phase persists; transient scratch must be rewritten before use.
|
|
154
|
+
Restore requires the same configuration and coordinated input/control timeline.
|
|
155
|
+
The existing non-atomic core 0.4.1 AudioParam/state restore boundary remains:
|
|
156
|
+
render and verify saved controls before browser restoration. Native offline
|
|
157
|
+
quantum continuation is a separate exact test, not a claim of atomic live restore.
|
|
158
|
+
|
|
159
|
+
## Required independent evidence
|
|
160
|
+
|
|
161
|
+
Before acceptance: independent absolute-time FIR/tap/FDN/pitch references at
|
|
162
|
+
44.1/48/96 kHz; impulse arrivals and path signs; linear dry/wet and pitch endpoints;
|
|
163
|
+
simultaneous versus separate-path algebra; normalized bounded excitation and
|
|
164
|
+
finite zero-scrub output; every B8 reset phase; held reset and reset+bypass;
|
|
165
|
+
bypass admission/re-entry and uninterrupted tails; pitch rejection, retrigger and
|
|
166
|
+
known coloration/cancellation; same-schema noninitial quantum restoration with
|
|
167
|
+
poisoned transient scratch; minimum/maximum valid capacities and fixed memory.
|
|
168
|
+
|
|
169
|
+
Integration requires actual packed public imports/declarations, native three-rate
|
|
170
|
+
rendering and an actual small 48 kHz browser composition with independently
|
|
171
|
+
predicted phase/gain or sample waveforms, control edits/reset and correctly
|
|
172
|
+
coordinated native restore. Diagnostic costs retain cold/warm misses and do not
|
|
173
|
+
certify realtime behavior. Candidate audio stays CANDIDATE and hashed.
|
|
174
|
+
|
|
175
|
+
## Initial native verification
|
|
176
|
+
|
|
177
|
+
On 2026-10-06 the new source passed `npm run check` and all 20 focused cases in
|
|
178
|
+
`tests/spatial-chains.spec.ts`. The absolute-time reference includes f32 fraction,
|
|
179
|
+
multiplication, addition and storage boundaries of the inherited readheads. The
|
|
180
|
+
three-rate tests cover the complete chain equations, signed reflection arrivals,
|
|
181
|
+
every B8 reset phase, reset+bypass priority, native same-schema continuation,
|
|
182
|
+
and bypass re-entry after suppressed excitation with a continuing nonzero tail.
|
|
183
|
+
The pitched entry at pitchMix=0 is bit-identical to the plain hybrid output.
|
|
184
|
+
|
|
185
|
+
The full-composition W64/ratio2 counterexample removes independently predicted
|
|
186
|
+
early output and finds no intended doubled-frequency late component for a
|
|
187
|
+
128-sample-period input, while an original-frequency component remains. It is
|
|
188
|
+
retained as a pitch-quality limitation, not promoted by a waveform-oracle pass.
|
|
189
|
+
|
|
190
|
+
Native capacity/scratch probes at 8/44.1/48/96/192 kHz, including W32 and W16384,
|
|
191
|
+
keep memory fixed, produce finite output with zero nonfinite-output scrubs, and
|
|
192
|
+
match the scalar oracle within the predeclared 2e-6 absolute tolerance under
|
|
193
|
+
normalized square-wave excitation. Poisoning all 11 declared transient scalar
|
|
194
|
+
temporaries and six transient work buffers leaves subsequent audio bit-identical.
|
|
195
|
+
The separate public snapshot test restores serialized persistent histories;
|
|
196
|
+
the poisoning probe clones native memory only as a test of scratch independence.
|
|
197
|
+
|
|
198
|
+
The guarded source/type/native batch completed in 15.35 seconds, with 486,039,552
|
|
199
|
+
bytes maximum summed process-group RSS. This process measurement is not a
|
|
200
|
+
per-quantum deadline result. Actual browser worklet and exact-head aggregate CI
|
|
201
|
+
remain separate acceptance requirements. No human listening or hardware clearance
|
|
202
|
+
follows.
|
|
203
|
+
|
|
204
|
+
## Packed and independent review evidence
|
|
205
|
+
|
|
206
|
+
`tests/spatial-chains-packed.test.mjs` checks strict public declarations, both
|
|
207
|
+
actual public subgraphs, native a-rate mix/ratio/pitchMix and input controls at
|
|
208
|
+
44.1/48/96 kHz, and the W16384/192 kHz maximum. The first clean packed proof at
|
|
209
|
+
commit `b173a3e` had maximum absolute waveform error 2.981e-8, bit-identical suffix
|
|
210
|
+
PCM and final native snapshots, fixed memory and zero nonfinite-output scrubs.
|
|
211
|
+
Both distinct Vite WASM/worklet bundles were built; building is not browser
|
|
212
|
+
execution. Three hashed five-channel WAVs retain raw input and stereo outputs
|
|
213
|
+
as finite CANDIDATE excerpts, with no normalization or complete-tail-drain claim.
|
|
214
|
+
|
|
215
|
+
The local-only dependency reuse option is rejected in CI. It requires the exact
|
|
216
|
+
locked non-target resolutions, creates a private installed target from the new
|
|
217
|
+
tarball, preserves the donor tar/lock/installed target, and fingerprints every
|
|
218
|
+
non-target dependency package before and after. The first proof verified all of
|
|
219
|
+
those identities unchanged. CI retains the isolated fresh-install path.
|
|
220
|
+
|
|
221
|
+
The packed process batch completed in 14.61 seconds with 776,314,880 bytes maximum
|
|
222
|
+
summed process-group RSS. Loaded native measurements include host input/parameter/
|
|
223
|
+
output copies over 256 quanta per profile:
|
|
224
|
+
|
|
225
|
+
| Rate | Hybrid cold / warm max | W512 pitched cold / warm max | Quantum budget |
|
|
226
|
+
| --- | --- | --- | --- |
|
|
227
|
+
| 44.1 kHz | 3.203 / .711 ms | 3.694 / 3.723 ms | 2.902 ms |
|
|
228
|
+
| 48 kHz | 3.242 / .942 ms | 3.302 / 1.326 ms | 2.667 ms |
|
|
229
|
+
| 96 kHz | 3.398 / 1.712 ms | 5.530 / 2.402 ms | 1.333 ms |
|
|
230
|
+
|
|
231
|
+
Every measured profile missed its cold deadline; some warm maxima also missed.
|
|
232
|
+
The maximum pitched configuration used 458,752 native bytes and 54,056 WASM
|
|
233
|
+
bytes with no memory growth. These observations preserve NOT_CLEARED and do not
|
|
234
|
+
predict browser scheduling or hardware capacity.
|
|
235
|
+
|
|
236
|
+
An independent review imported neither the author processor nor its scalar
|
|
237
|
+
oracle. It compared all 182 installed package files against the actual tarball
|
|
238
|
+
and passed 16 native/public groups. A separate 4-by-4 complex FDN resolvent,
|
|
239
|
+
combined with the FIR/fractional taps, dry path and initial unity-pitch delay,
|
|
240
|
+
predicted both channels at four simultaneous coherent frequencies. Across both
|
|
241
|
+
entries and 44.1/48/96 kHz, maximum complex-transfer error was 9.664e-10 against a
|
|
242
|
+
predeclared 2e-6 tolerance. Independent reset/bypass/mix, ratio rejection including
|
|
243
|
+
NaN/infinities, native snapshot and minimum/maximum capacity probes also passed.
|
|
244
|
+
|
|
245
|
+
The independent 8 kHz headroom witness used mix=1 and input +1 for residues
|
|
246
|
+
0..127 of each 257-sample period, -1 for residues 128..256, then zero after sample
|
|
247
|
+
8191. Hybrid output reached 1.131077647; the W32 variant at ratio=1 and pitchMix=1
|
|
248
|
+
reached 1.088592649. These concrete unclipped peaks confirm that normalized input
|
|
249
|
+
can exceed unity at output. They are examples, not peak ceilings. Independent
|
|
250
|
+
source/native review found no implementation blocker; browser, final-head CI,
|
|
251
|
+
listening and realtime acceptance remain separate.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# Bin-centered spectral freeze candidate
|
|
2
|
+
|
|
3
|
+
Source scope: catalog group 11, [Pitch / time / STFT / spectral engine](https://app.notion.com/p/3ef449b4e81d817db1acf1fff48779b3).
|
|
4
|
+
This separate native STFT effect is not the FDN reverb freeze. It uses the reviewed
|
|
5
|
+
STFT/FFT sources unchanged, with a bounded construction-time spectrum hook.
|
|
6
|
+
CANDIDATE only: no human sound approval, browser/realtime clearance, transparent
|
|
7
|
+
sustain, phase-vocoder or phase-locking claim.
|
|
8
|
+
|
|
9
|
+
## Public contract
|
|
10
|
+
|
|
11
|
+
`spectralFreeze({ size, hopSize, renderQuantumSize: ctx.renderQuantumSize }).tick(input, freeze, reset, everyNSamples)`
|
|
12
|
+
|
|
13
|
+
- Instantiate once with an explicit subgraph name. Call exactly once per sample
|
|
14
|
+
inside stride-1 `forSample`, supplying its existing `everyNSamples`.
|
|
15
|
+
- Construction-fixed power-of-two N=8..1024; H=N/2 or N/4. Input is finite,
|
|
16
|
+
normalized f32 in [-1,1]. `freeze` and `reset` are boolean graph nodes, not
|
|
17
|
+
thresholded or smoothed floating-point parameters.
|
|
18
|
+
- A committed frame at t=mH consumes [t-N,t). Freeze is sampled at t. The first
|
|
19
|
+
high sampled frame captures its complex spectrum; the current input sample
|
|
20
|
+
is excluded. Missing startup/reset history is zero padded. Capturing at t=0
|
|
21
|
+
therefore holds silence until release/reset and a later capture.
|
|
22
|
+
- Subsequent high frames hold the captured magnitudes and advance each bin's
|
|
23
|
+
phase by +2*pi*k*H/N per frame. No actual-frequency estimator, phase locking,
|
|
24
|
+
neighboring-bin rule, formant preservation or transient detector exists.
|
|
25
|
+
- Only nonnegative bins are stored. DC and Nyquist imaginary values are zero;
|
|
26
|
+
negative-frequency bins are reconstructed as complex conjugates. Since legal
|
|
27
|
+
H is even, Nyquist advances by an integer number of full turns and stays real.
|
|
28
|
+
- H=N/2 or N/4 makes the phase advance an exact +/-1 or +/-i rotation. An integer
|
|
29
|
+
modulo-(N/H) counter rotates the original captured values, avoiding cumulative
|
|
30
|
+
rotation drift. All captured values retain the reviewed internal 2^128 scale
|
|
31
|
+
so the existing 1e-30 buffer-write flush does not discard f32 subnormal audio.
|
|
32
|
+
- Low freeze releases on that committed frame. Live input and analysis history
|
|
33
|
+
continue during hold, so release uses the latest live frame. An unsampled low
|
|
34
|
+
pulse cannot rearm capture. A low committed frame followed by a high committed
|
|
35
|
+
frame releases and recaptures. Between-frame freeze edits have no effect.
|
|
36
|
+
- Keeping freeze=false from initialization, or after a reset that discards prior
|
|
37
|
+
frozen overlap, uses the original unmodified spectrum and preserves identity
|
|
38
|
+
PCM exactly. On release, already scheduled frozen overlap may remain and
|
|
39
|
+
settles within N samples of the committed release frame; identity PCM is
|
|
40
|
+
restored after that overlap settles. New freeze state makes its schema distinct; interchange
|
|
41
|
+
with identity-module snapshots is not supported.
|
|
42
|
+
|
|
43
|
+
## Phase meaning and transitions
|
|
44
|
+
|
|
45
|
+
For captured analysis frame a[n]=input[t0-N+n]*sin(pi*n/N), a positive phase
|
|
46
|
+
increment means the inverse transform on held frame t is exactly the circular
|
|
47
|
+
left shift a[(n+t-t0) mod N], in ideal arithmetic. This equivalence is used as a
|
|
48
|
+
separate, linear-cost numerical oracle, alongside dense complex DFT/IFFT tests.
|
|
49
|
+
It also describes an important limitation: the captured analysis window is part
|
|
50
|
+
of the held texture. Steady held output is N-periodic and can have pronounced
|
|
51
|
+
window-related amplitude modulation, even for an on-bin sinusoid. Off-bin input
|
|
52
|
+
is altered into a periodic texture. This is a deterministic bin-centered freeze,
|
|
53
|
+
not a claim to preserve a tone's instantaneous frequency or smooth envelope.
|
|
54
|
+
|
|
55
|
+
Both windows remain the periodic sqrt-Hann sin(pi*n/N), with WOLA gain 2H/N.
|
|
56
|
+
There is no extra transition ramp, fade parameter, dry path, limiter or level
|
|
57
|
+
normalization. Capture and release replace newly committed frames; already
|
|
58
|
+
scheduled overlap remains. The transition settles within N samples of the
|
|
59
|
+
committed edge, and the edge itself is delayed by less than H samples from a
|
|
60
|
+
persistent control request. Smear, ringing, periodic beating and altered peaks
|
|
61
|
+
are expected. WOLA overlap alone is not an artifact-free-transition guarantee.
|
|
62
|
+
|
|
63
|
+
The live reference alignment is N samples. Processed onset is frame-dependent;
|
|
64
|
+
N is not a universal first-nonzero delay for held content. There is no finite
|
|
65
|
+
zero-input drain while held. After freeze is kept low and the source ends,
|
|
66
|
+
provide at least 2N zero samples from the later of source end and release request.
|
|
67
|
+
This conservatively includes the next sampled release and outstanding overlap.
|
|
68
|
+
|
|
69
|
+
## Reset, snapshots and execution
|
|
70
|
+
|
|
71
|
+
Reset silences output immediately and discards that input sample. It invalidates
|
|
72
|
+
the hold and clears its integer phase immediately, without rephasing the absolute
|
|
73
|
+
frame clock. High freeze can capture again at the next non-reset committed frame.
|
|
74
|
+
Held reset prevents capture. Captured buffer bytes are logically invalidated,
|
|
75
|
+
not physically scrubbed; a later capture overwrites every stored bin before use.
|
|
76
|
+
The framing's existing pending reset discards prior history and overlap.
|
|
77
|
+
|
|
78
|
+
Persistent state contains the existing history/overlap/cursor/reset state, two
|
|
79
|
+
(N/2+1)-element f64 capture planes, the hold flag, held phase and a private
|
|
80
|
+
modulo-H sample clock. The latter mirrors the reviewed framing cursor%H; neither
|
|
81
|
+
clock changes phase on reset. Frame controls and capture decisions are transient.
|
|
82
|
+
Full snapshots continue exactly for the identical graph, configuration, rate
|
|
83
|
+
and quantum.
|
|
84
|
+
Tests cover inactive, frozen, post-reset and release/rearm states at every
|
|
85
|
+
native block-aligned snapshot offset class, including non-128 boundaries.
|
|
86
|
+
No cross-rate, cross-quantum or cross-configuration migration promise is added.
|
|
87
|
+
|
|
88
|
+
The shared STFT dispatch runs every `gcd(H, 128, renderQuantumSize)` samples.
|
|
89
|
+
The FFT, hook and IFFT execute eagerly, but only true t=mH frames capture,
|
|
90
|
+
advance the held phase or commit overlap. These extra hook calls do not change
|
|
91
|
+
persistent capture/hold phase. No CPU saving is implied by a select; smaller or
|
|
92
|
+
coprime quanta increase workload as documented in the [spectral contract](spectral.md).
|
|
93
|
+
|
|
94
|
+
## Headroom derivation
|
|
95
|
+
|
|
96
|
+
In ideal real arithmetic, every input analysis frame has Euclidean norm at most
|
|
97
|
+
sqrt(N), because |input[n]|<=1 and |window[n]|<=1. A complex DFT followed by its
|
|
98
|
+
properly scaled inverse preserves this norm; multiplying each bin by a unit
|
|
99
|
+
phase factor preserves it too. Every live or held inverse-frame sample is thus
|
|
100
|
+
bounded by sqrt(N). At an output sample, at most N/H synthesis frames overlap,
|
|
101
|
+
each with |window|<=1 and scale 2H/N. The triangle inequality gives
|
|
102
|
+
(N/H)*(2H/N)*sqrt(N)=2*sqrt(N), at most64 for N1024. The same argument covers
|
|
103
|
+
capture/release overlap between different frames. A tighter bound is possible
|
|
104
|
+
using the actual window energy; this deliberately conservative bound suffices.
|
|
105
|
+
|
|
106
|
+
This is a mathematical headroom bound for normalized inputs, not a numerical
|
|
107
|
+
roundoff theorem. Finite-f64 FFT error and final f32 rounding are separately
|
|
108
|
+
measured against independent oracles; no limiter enforces the bound. Unexpected
|
|
109
|
+
nonfinite or out-of-range audio input is outside the contract.
|
|
110
|
+
|
|
111
|
+
## Validation requirements
|
|
112
|
+
|
|
113
|
+
Independent dense DFT/positive-phase rotation/IFFT/time-indexed WOLA, plus the
|
|
114
|
+
separate circular-left-shift oracle; DC/Nyquist, coherent/off-bin tones, capture
|
|
115
|
+
exclusion of current input, frame-edge controls, both legal hops, all small and
|
|
116
|
+
large reset offsets, held reset, snapshot phases, subnormals, normalized extrema,
|
|
117
|
+
held sustain and release drain at44.1/48/96kHz. Existing no-hook graph/schema/PCM/
|
|
118
|
+
snapshot compatibility remains unchanged. An isolated installed public-package
|
|
119
|
+
consumer must typecheck, render, restore, report memory/cold/warm costs and build
|
|
120
|
+
its Vite worklet before package acceptance. Audio evidence is CANDIDATE, with
|
|
121
|
+
source/package/audio hashes and no per-channel normalization.
|
|
122
|
+
|
|
123
|
+
## Initial focused evidence
|
|
124
|
+
|
|
125
|
+
The first typecheck and25-test run passed:19 freeze tests,3 exhaustive large-reset
|
|
126
|
+
cases and3 unchanged no-hook identity compatibility cases across44.1/48/96kHz.
|
|
127
|
+
It completed in45.4seconds with718MB observed process-group RSS under a180second/
|
|
128
|
+
2GiB guard. The native DSP, dense-DFT WOLA and circular-shift WOLA agree within
|
|
129
|
+
2e-7 absolute normalized amplitude; tiny-input assertions scale the tolerance down
|
|
130
|
+
to one f32 subnormal ULP. Live identity PCM and full-snapshot continuation are
|
|
131
|
+
bit exact. The reviewed shared FFT/STFT files were not modified. This focused
|
|
132
|
+
pass does not substitute for independent review or public-package validation.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Framewise spectral gate entry proposal
|
|
2
|
+
|
|
3
|
+
Status: root-approved bounded entry; implementation validation is separate. Source scope: catalog group
|
|
4
|
+
11, [Pitch / time / STFT / spectral engine](https://app.notion.com/p/3ef449b4e81d817db1acf1fff48779b3).
|
|
5
|
+
This is one bounded spectral effect built on the independently reviewed
|
|
6
|
+
`4be1c9991459ea559102a5fa8fd76fb3d52a94b9` STFT framing.
|
|
7
|
+
|
|
8
|
+
## API and meaning
|
|
9
|
+
|
|
10
|
+
- `spectralGate({ size, hopSize, renderQuantumSize: ctx.renderQuantumSize }).tick(input, threshold, floor, reset, everyNSamples)`
|
|
11
|
+
- Construction-fixed power-of-two N=8..1024 and H=N/2 or N/4.
|
|
12
|
+
- Input: finite normalized f32 audio in [-1,1]. Controls: f32. Threshold is clamped to [0,max-f32], floor to [0,1];
|
|
13
|
+
infinities saturate, NaN threshold becomes 0 and NaN floor becomes 1 (all-pass).
|
|
14
|
+
- A committed frame at sample t=mH consumes [t-N,t). It samples threshold and
|
|
15
|
+
floor at t. The current input sample is not part of that frame. Between-frame
|
|
16
|
+
control values do not retroactively modify already scheduled overlap audio.
|
|
17
|
+
- With w[n]=sin(pi*n/N), W=sum(w), and forward unnormalized DFT X, the gate level
|
|
18
|
+
is |X[k]|/W for DC/Nyquist and 2|X[k]|/W for other nonnegative bins. These are
|
|
19
|
+
linear, coherent-window-calibrated single-sided bin amplitude units. They are
|
|
20
|
+
neither RMS, dB, noise power density nor an exact off-bin sinusoid amplitude.
|
|
21
|
+
- Computed level >= threshold gives gain 1; otherwise gain=floor. One decision
|
|
22
|
+
applies to the conjugate pair. DC/Nyquist imaginary components are zero.
|
|
23
|
+
Floating roundoff can determine either result exactly at a threshold boundary.
|
|
24
|
+
- No attack/release, hysteresis, adaptive noise profile, gain interpolation,
|
|
25
|
+
makeup normalization, limiter, dry path or bypass scheduler. Threshold 0 or
|
|
26
|
+
floor 1 is the all-pass limit. Low bins may contain positive/negative-frequency
|
|
27
|
+
window-lobe interference; threshold units remain the stated bin formula.
|
|
28
|
+
|
|
29
|
+
## Framing, state and headroom
|
|
30
|
+
|
|
31
|
+
Retain the original periodic sqrt-Hann analysis/synthesis windows, normalization
|
|
32
|
+
2H/N, reset discard semantics and persistent cursor. Dispatch now follows
|
|
33
|
+
`gcd(H, 128, renderQuantumSize)` to align every native snapshot boundary; only
|
|
34
|
+
cursor%H==0 commits frames. Off-phase FFT/IFFT work is eager. See the shared
|
|
35
|
+
[spectral contract](spectral.md) for increased costs at small or coprime quanta;
|
|
36
|
+
there is no H-rate CPU-saving claim.
|
|
37
|
+
|
|
38
|
+
The WOLA alignment/reference delay is N samples; a processed transient can spread
|
|
39
|
+
across its synthesis frame [t,t+N), so its first nonzero sample need not occur at
|
|
40
|
+
input onset+N. The all-pass limit retains exact N-sample delay. Drain at least 2N
|
|
41
|
+
zero input samples after the source ends. Spectral processing can spread the last
|
|
42
|
+
input through the rest of its analysis frame and then through a synthesis frame;
|
|
43
|
+
the identity-only N-sample drain is insufficient for the gate. Reset silences immediately, discards that input sample, clears
|
|
44
|
+
old history/overlap and leaves absolute frame phase unchanged.
|
|
45
|
+
|
|
46
|
+
Only the existing history, overlap, cursor, valid history and pending reset need
|
|
47
|
+
persistent storage. Frame controls and spectrum work are transient and overwritten
|
|
48
|
+
before use. Identical graph/rate/quantum full-snapshot continuation remains in scope.
|
|
49
|
+
|
|
50
|
+
Attenuating bins can increase individual time-domain peaks. For normalized input,
|
|
51
|
+
an ideal contraction bound per frame and the overlapping windows give the
|
|
52
|
+
conservative output bound 2*sqrt(N), at most64 for N1024. This is headroom guidance,
|
|
53
|
+
not a limiter or loudness promise; measured peaks accompany candidate evidence.
|
|
54
|
+
|
|
55
|
+
## Implementation boundary and verification
|
|
56
|
+
|
|
57
|
+
The original gate entry added a narrow optional internal construction-time
|
|
58
|
+
spectrum-copy hook to the
|
|
59
|
+
existing STFT function. The identity branch must produce the same graph, schema,
|
|
60
|
+
PCM and full snapshots as its frozen predecessor. The 0.2.0 compatibility
|
|
61
|
+
migration separately replaces fixed-quantum FFT loops and extends snapshot-safe
|
|
62
|
+
dispatch; its numerical and continuation assertions preserve this gate behavior. The public gate wrapper is separate; shared
|
|
63
|
+
exports, CI and site belong to integration.
|
|
64
|
+
|
|
65
|
+
Required tests: independent direct DFT/bin-mask/inverse-DFT/time-indexed WOLA;
|
|
66
|
+
DC, Nyquist, on/off-bin tones, mixed strong/weak bins, threshold units and equality
|
|
67
|
+
limits, conjugate symmetry, frame-sampled controls around hop/block boundaries,
|
|
68
|
+
all reset phases, held reset, tiny f32 signals, finite normalized extrema and
|
|
69
|
+
headroom, all quantum-offset snapshot phases, and 44.1/48/96kHz renders. A clean
|
|
70
|
+
installed public package must typecheck, render, restore and build a Vite worklet.
|
|
71
|
+
Document fresh compile/cold/warm costs, fixed memory and zero scrub observations.
|
|
72
|
+
|
|
73
|
+
Expected artifacts include musical noise from hard decisions, ringing, transient
|
|
74
|
+
smear, pumping under threshold edits and changed peaks. Neither identity proof nor
|
|
75
|
+
numerical correctness means artifact-free sound. CANDIDATE only: no human sound,
|
|
76
|
+
browser realtime, pitch shifting, phase vocoder, long-IR or den-kit claim.
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Internal numeric details
|
|
80
|
+
|
|
81
|
+
History and FFT spectra retain the existing exact factor 2^128. The gate compares
|
|
82
|
+
`factor*(Re(Xscaled)^2+Im(Xscaled)^2)` against `(threshold*W*2^128)^2`, where factor
|
|
83
|
+
is 1 for DC/Nyquist and 4 for the other bins. Double precision safely contains
|
|
84
|
+
both sides over the full finite f32 control range. The comparison needs no
|
|
85
|
+
runtime square root or dB conversion. Floor gains are stored multiplied by
|
|
86
|
+
2^128 so even the smallest positive f32 floor is not silently flushed by the
|
|
87
|
+
existing buffer-write policy. One gain is calculated and stored for each
|
|
88
|
+
nonnegative bin; the negative bin is reconstructed from that canonical member.
|
|
89
|
+
|
|
90
|
+
Threshold 0 and floor 1 explicitly choose the original unmodified spectrum.
|
|
91
|
+
This preserves identity PCM exactly, rather than relying on re-mirrored FFT
|
|
92
|
+
roundoff to round identically. No FFT work is skipped by these eager selects.
|
|
93
|
+
The internal hook exists only during graph construction and is not an additional
|
|
94
|
+
public `stftIdentity.tick` argument or an arbitrary runtime callback.
|
|
95
|
+
|
|
96
|
+
## Focused validation
|
|
97
|
+
|
|
98
|
+
The first bounded implementation run passed all49 tests in the seven spectral,
|
|
99
|
+
convolution, gate and hook-compatibility files at44.1/48/96kHz. It completed in
|
|
100
|
+
72.9seconds with549MB observed process-group RSS under a120second/2GiB guard.
|
|
101
|
+
Existing FFT/STFT/convolution tests and numerical tolerances were unchanged.
|
|
102
|
+
|
|
103
|
+
The no-hook construction produced byte-identical WASM and schema, exact PCM and
|
|
104
|
+
full snapshots against the frozen4be1c99 STFT at N8/64/256/512/1024 and both large
|
|
105
|
+
hop classes. Bidirectional restoration was also exact. Gate tests separately
|
|
106
|
+
cover every large reset phase, every quantum-offset snapshot phase, held reset,
|
|
107
|
+
frame-sampled edits, DC/Nyquist and off-bin cases, NaN/infinite controls,
|
|
108
|
+
subnormal audio and controls, and actual transient spread/peak overshoot.
|
|
109
|
+
|
|
110
|
+
The isolated packed consumer records its exact source/package/audio hashes,
|
|
111
|
+
three-rate DFT/WOLA errors, restore equality, fixed memory and fresh/warm local
|
|
112
|
+
Node costs. It also emits a CANDIDATE48kHz three-channel WAV: original source,
|
|
113
|
+
N-aligned reference, and gate output, with no per-channel normalization. This is
|
|
114
|
+
an audition artifact, not a human-approved golden. Its Vite build is compilation
|
|
115
|
+
evidence, not browser audio or realtime clearance.
|
|
116
|
+
|
|
117
|
+
A follow-on21-test gate run extends active tiny-control, normalized-extreme
|
|
118
|
+
headroom and strong/weak partial separation checks to all three rates. It passed
|
|
119
|
+
in16.4seconds with506MB observed process-group RSS. The DSP did not change between
|
|
120
|
+
these runs; the unchanged seven-file suite plus these additions contains56 tests.
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
## Processed-tail correction
|
|
124
|
+
|
|
125
|
+
Independent review retained a concrete counterexample to the earlier N-zero
|
|
126
|
+
drain statement: N64/H16, an impulse at sample127, threshold0.035 and floor0.
|
|
127
|
+
An N-zero drain ends before sample192, but processed output continues through
|
|
128
|
+
sample223, with a post-cutoff peak around0.02336. This is expected spectral
|
|
129
|
+
spreading, not a framing-state defect. The conservative public drain requirement
|
|
130
|
+
is now2N zero-input samples. A regression checks that the N cutoff loses nonzero
|
|
131
|
+
output and that output is exactly zero after the2N cutoff at all three rates.
|
|
132
|
+
The corrected audition preserves its original source samples and appends another
|
|
133
|
+
N zeros, providing a complete2N drain. Prior truncated audition artifacts must
|
|
134
|
+
not be used as the completed effect candidate.
|