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