@denaudio/den 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/LICENSE-APACHE +202 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +107 -2
  5. package/dist/biquad-eq.d.ts +11 -0
  6. package/dist/biquad-eq.js +35 -0
  7. package/dist/catalog-filter-math.d.ts +4 -0
  8. package/dist/catalog-filter-math.js +30 -0
  9. package/dist/character-filter.d.ts +13 -0
  10. package/dist/character-filter.js +53 -0
  11. package/dist/convolution-spectrum.d.ts +25 -0
  12. package/dist/convolution-spectrum.js +82 -0
  13. package/dist/convolution.d.ts +17 -0
  14. package/dist/convolution.js +87 -0
  15. package/dist/crossover.d.ts +11 -0
  16. package/dist/crossover.js +17 -0
  17. package/dist/curve-shaper.d.ts +26 -0
  18. package/dist/curve-shaper.js +73 -0
  19. package/dist/delay-fx.d.ts +37 -0
  20. package/dist/delay-fx.js +84 -0
  21. package/dist/delay-readhead.d.ts +19 -0
  22. package/dist/delay-readhead.js +54 -0
  23. package/dist/delay-settings.d.ts +55 -0
  24. package/dist/delay-settings.js +20 -0
  25. package/dist/drive.d.ts +24 -0
  26. package/dist/drive.js +116 -0
  27. package/dist/dual-head-delay.d.ts +28 -0
  28. package/dist/dual-head-delay.js +77 -0
  29. package/dist/dynamics.d.ts +61 -0
  30. package/dist/dynamics.js +130 -0
  31. package/dist/envelope.d.ts +34 -0
  32. package/dist/envelope.js +62 -0
  33. package/dist/filter.d.ts +9 -0
  34. package/dist/filter.js +34 -0
  35. package/dist/filterbank-vocoder.d.ts +35 -0
  36. package/dist/filterbank-vocoder.js +52 -0
  37. package/dist/formant-bank.d.ts +15 -0
  38. package/dist/formant-bank.js +28 -0
  39. package/dist/freeze-reverb.d.ts +35 -0
  40. package/dist/freeze-reverb.js +61 -0
  41. package/dist/frequency-shifter.d.ts +24 -0
  42. package/dist/frequency-shifter.js +57 -0
  43. package/dist/gate.d.ts +2 -0
  44. package/dist/gate.js +12 -0
  45. package/dist/index.d.ts +7 -0
  46. package/dist/index.js +12 -0
  47. package/dist/instrument-example.d.ts +12 -0
  48. package/dist/instrument-example.js +29 -0
  49. package/dist/instrument-settings.d.ts +95 -0
  50. package/dist/instrument-settings.js +36 -0
  51. package/dist/instrument.d.ts +42 -0
  52. package/dist/instrument.js +166 -0
  53. package/dist/lfo.d.ts +19 -0
  54. package/dist/lfo.js +55 -0
  55. package/dist/live-buffer.d.ts +39 -0
  56. package/dist/live-buffer.js +52 -0
  57. package/dist/live-granular.d.ts +41 -0
  58. package/dist/live-granular.js +64 -0
  59. package/dist/lookahead-limiter.d.ts +31 -0
  60. package/dist/lookahead-limiter.js +67 -0
  61. package/dist/loop-crossfade.d.ts +28 -0
  62. package/dist/loop-crossfade.js +72 -0
  63. package/dist/modulation-fx.d.ts +47 -0
  64. package/dist/modulation-fx.js +58 -0
  65. package/dist/modulation.d.ts +132 -0
  66. package/dist/modulation.js +228 -0
  67. package/dist/mpe-expression.d.ts +36 -0
  68. package/dist/mpe-expression.js +73 -0
  69. package/dist/multiband-dynamics.d.ts +50 -0
  70. package/dist/multiband-dynamics.js +60 -0
  71. package/dist/musical-controls.d.ts +76 -0
  72. package/dist/musical-controls.js +125 -0
  73. package/dist/musical-examples.d.ts +21 -0
  74. package/dist/musical-examples.js +142 -0
  75. package/dist/musical-materials.d.ts +110 -0
  76. package/dist/musical-materials.js +56 -0
  77. package/dist/oscillator.d.ts +10 -0
  78. package/dist/oscillator.js +35 -0
  79. package/dist/oversampled-drive.d.ts +19 -0
  80. package/dist/oversampled-drive.js +99 -0
  81. package/dist/performance.d.ts +61 -0
  82. package/dist/performance.js +241 -0
  83. package/dist/pitch-quantizer.d.ts +26 -0
  84. package/dist/pitch-quantizer.js +88 -0
  85. package/dist/prepared-convolution.d.ts +26 -0
  86. package/dist/prepared-convolution.js +104 -0
  87. package/dist/resident-recorder.d.ts +28 -0
  88. package/dist/resident-recorder.js +68 -0
  89. package/dist/resident-time-stretch.d.ts +46 -0
  90. package/dist/resident-time-stretch.js +136 -0
  91. package/dist/resonator.d.ts +32 -0
  92. package/dist/resonator.js +63 -0
  93. package/dist/reverb.d.ts +27 -0
  94. package/dist/reverb.js +52 -0
  95. package/dist/sample.d.ts +113 -0
  96. package/dist/sample.js +212 -0
  97. package/dist/source.d.ts +58 -0
  98. package/dist/source.js +113 -0
  99. package/dist/spatial-chains.d.ts +50 -0
  100. package/dist/spatial-chains.js +76 -0
  101. package/dist/spectral-fft.d.ts +27 -0
  102. package/dist/spectral-fft.js +84 -0
  103. package/dist/spectral-freeze.d.ts +24 -0
  104. package/dist/spectral-freeze.js +74 -0
  105. package/dist/spectral-gate.d.ts +23 -0
  106. package/dist/spectral-gate.js +58 -0
  107. package/dist/spectral-stft.d.ts +14 -0
  108. package/dist/spectral-stft.js +70 -0
  109. package/dist/spectral-texture.d.ts +46 -0
  110. package/dist/spectral-texture.js +141 -0
  111. package/dist/spectral.d.ts +18 -0
  112. package/dist/spectral.js +10 -0
  113. package/dist/state-variable-filter.d.ts +17 -0
  114. package/dist/state-variable-filter.js +25 -0
  115. package/dist/stereo-delay.d.ts +45 -0
  116. package/dist/stereo-delay.js +70 -0
  117. package/dist/virtual-analog.d.ts +26 -0
  118. package/dist/virtual-analog.js +66 -0
  119. package/dist/voice-policy.d.ts +37 -0
  120. package/dist/voice-policy.js +245 -0
  121. package/dist/wavetable-bands.d.ts +37 -0
  122. package/dist/wavetable-bands.js +145 -0
  123. package/dist/wavetable-read.d.ts +6 -0
  124. package/dist/wavetable-read.js +11 -0
  125. package/dist/wavetable.d.ts +29 -0
  126. package/dist/wavetable.js +49 -0
  127. package/dist/windowed-pitch-shift.d.ts +34 -0
  128. package/dist/windowed-pitch-shift.js +51 -0
  129. package/docs/catalog-audition.md +76 -0
  130. package/docs/catalog-filters.md +125 -0
  131. package/docs/catalog-modulation-fx.md +220 -0
  132. package/docs/catalog-status.md +433 -0
  133. package/docs/character-filter.md +139 -0
  134. package/docs/contracts.md +141 -0
  135. package/docs/control-modulation.md +194 -0
  136. package/docs/convolution.md +50 -0
  137. package/docs/curve-shaper.md +117 -0
  138. package/docs/delay-fx-capture-findings.json +134 -0
  139. package/docs/delay-fx.md +202 -0
  140. package/docs/delay-readhead.md +121 -0
  141. package/docs/delay-settings-candidate.json +333 -0
  142. package/docs/delay-settings.md +111 -0
  143. package/docs/dependency-findings.md +58 -0
  144. package/docs/deployment-status.md +37 -0
  145. package/docs/drive.md +152 -0
  146. package/docs/dual-head-delay.md +131 -0
  147. package/docs/dynamics.md +180 -0
  148. package/docs/envelope-integer-staging.md +64 -0
  149. package/docs/filter-materialization-results.json +71 -0
  150. package/docs/filter-materialization.md +135 -0
  151. package/docs/filter-oscillator.md +126 -0
  152. package/docs/filterbank-vocoder-entry.md +58 -0
  153. package/docs/freeze-reverb.md +179 -0
  154. package/docs/frequency-shifter.md +133 -0
  155. package/docs/frontend-audition.md +22 -0
  156. package/docs/gain-schedule-verification.md +53 -0
  157. package/docs/initial-acceptance.md +79 -0
  158. package/docs/instrument-performance.md +191 -0
  159. package/docs/instrument-settings.md +130 -0
  160. package/docs/instrument.md +204 -0
  161. package/docs/integration-candidate.md +114 -0
  162. package/docs/live-buffer.md +163 -0
  163. package/docs/live-granular.md +179 -0
  164. package/docs/lookahead-multiband.md +183 -0
  165. package/docs/loop-crossfade.md +99 -0
  166. package/docs/mpe-expression.md +65 -0
  167. package/docs/musical-clock-seek-boundary.md +31 -0
  168. package/docs/musical-controls-contract.md +144 -0
  169. package/docs/musical-examples.md +255 -0
  170. package/docs/oversampled-drive.md +153 -0
  171. package/docs/performance.md +53 -0
  172. package/docs/pitch-quantizer.md +55 -0
  173. package/docs/prepared-convolution.md +197 -0
  174. package/docs/realtime-audition.md +63 -0
  175. package/docs/resident-recorder.md +151 -0
  176. package/docs/resident-time-stretch.md +196 -0
  177. package/docs/sample.md +191 -0
  178. package/docs/sound-audition-headroom.md +103 -0
  179. package/docs/sources.md +81 -0
  180. package/docs/spatial-chains-entry.md +251 -0
  181. package/docs/spectral-freeze-entry.md +132 -0
  182. package/docs/spectral-gate-entry.md +134 -0
  183. package/docs/spectral-large-candidate.md +81 -0
  184. package/docs/spectral-texture-entry.md +178 -0
  185. package/docs/spectral.md +127 -0
  186. package/docs/virtual-analog.md +77 -0
  187. package/docs/voice-policy.md +38 -0
  188. package/docs/wavetable-bands.md +138 -0
  189. package/docs/wavetable.md +101 -0
  190. package/docs/windowed-pitch-shift.md +112 -0
  191. package/package.json +269 -4
@@ -0,0 +1,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.