@denaudio/den 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/LICENSE-APACHE +202 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +102 -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 +15 -0
  14. package/dist/convolution.js +81 -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 +22 -0
  86. package/dist/prepared-convolution.js +99 -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 +44 -0
  90. package/dist/resident-time-stretch.js +135 -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 +48 -0
  100. package/dist/spatial-chains.js +76 -0
  101. package/dist/spectral-fft.d.ts +21 -0
  102. package/dist/spectral-fft.js +74 -0
  103. package/dist/spectral-freeze.d.ts +22 -0
  104. package/dist/spectral-freeze.js +74 -0
  105. package/dist/spectral-gate.d.ts +21 -0
  106. package/dist/spectral-gate.js +58 -0
  107. package/dist/spectral-stft.d.ts +13 -0
  108. package/dist/spectral-stft.js +69 -0
  109. package/dist/spectral-texture.d.ts +42 -0
  110. package/dist/spectral-texture.js +141 -0
  111. package/dist/spectral.d.ts +16 -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 +431 -0
  133. package/docs/character-filter.md +139 -0
  134. package/docs/contracts.md +131 -0
  135. package/docs/control-modulation.md +194 -0
  136. package/docs/convolution.md +45 -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 +61 -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 +188 -0
  174. package/docs/realtime-audition.md +63 -0
  175. package/docs/resident-recorder.md +151 -0
  176. package/docs/resident-time-stretch.md +190 -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 +250 -0
  181. package/docs/spectral-freeze-entry.md +130 -0
  182. package/docs/spectral-gate-entry.md +130 -0
  183. package/docs/spectral-large-candidate.md +74 -0
  184. package/docs/spectral-texture-entry.md +174 -0
  185. package/docs/spectral.md +118 -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,151 @@
1
+ # Resident take recorder candidate
2
+
3
+ Status: **CANDIDATE / NOT_CLEARED**. Catalog §3 describes sample/live-buffer
4
+ sources, fixed-capacity storage, and read/write competition. This module adds a
5
+ bounded native take writer that composes with the existing sample readers.
6
+ It is not a microphone permission layer, recorder application, rolling history,
7
+ overdub looper, streaming engine, or realtime/listening approval.
8
+
9
+ ## Entry and scope
10
+
11
+ Import `residentTakeRecorder`, `ResidentTakeRecorderConfig`,
12
+ `ResidentTakeRecorderControls` and `ResidentTakeRecorder` from
13
+ `@denaudio/den/resident-recorder`. Instantiate once inside a native processor:
14
+
15
+ ```ts
16
+ const take = instantiate(residentTakeRecorder,
17
+ { capacity: 65536, sampleRate: ctx.sampleRate }, { name: 'take' });
18
+ const player = instantiate(samplePlayer,
19
+ { sampleRate: ctx.sampleRate, sample: take.sample, loop: true },
20
+ { name: 'player' });
21
+ // In forSample, call the writer once, then the readers, in the intended order.
22
+ const status = take.tick({ input, record, reset });
23
+ ```
24
+
25
+ Capacity is an integer in 1–65,536 mono source frames, immutable after graph
26
+ construction. `sampleRate` is the incoming processor rate, finite 8–192 kHz.
27
+ The maximum take lasts about 1.486 s at 44.1 kHz, 1.365 s at 48 kHz, or 0.683 s
28
+ at 96 kHz. Changing the configured rate does not resample input. There is no
29
+ hidden monitor output: route the original input separately if monitoring is
30
+ wanted. All recording and reading is graph-native unworklet 0.4.1.
31
+
32
+ ## Sample-accurate write contract
33
+
34
+ - Initially length and revision are zero, and reads are missing/silent.
35
+ - `record:true` appends exactly one frame if length is below capacity.
36
+ `record:false` pauses without changing any exposed PCM or length.
37
+ - At capacity, recording stops without wrap or overwrite. Dropping and raising
38
+ record does not start another take. Reset is needed to start at frame zero.
39
+ - `reset:true` has priority, sets length and loaded-prefix boundary to zero and
40
+ suppresses recording. It increments the wrapping i32 revision on every held
41
+ reset tick. A still-high record resumes on the first non-reset tick.
42
+ - `tick` returns `{written,full,length}` for this tick after the operation.
43
+ `written` includes recording a silent or sanitized frame. `full` is true on
44
+ the tick that writes the final frame; no extra input is accepted thereafter.
45
+ - Call `tick` exactly once per incoming sample. Native statement order controls
46
+ visibility: a read before `tick` sees the prior take, and a read after sees
47
+ the completed append/reset. PCM is stored before length is advanced; no
48
+ partially initialized frame becomes exposed. There is no inter-thread reader
49
+ or concurrent host-write contract.
50
+
51
+ There is no circular overwrite, so frame zero stays frame zero until reset/load.
52
+ The valid frame age can be computed from the current length; paused time does
53
+ not add frames or silence. This is a growing finite take, not a continuously
54
+ advancing live history with a moving read/write safety margin.
55
+
56
+ ## Existing sample reader compatibility
57
+
58
+ `take.sample` implements the unchanged `ResidentSample` interface. It can be
59
+ passed directly to `samplePlayer`, `multisamplePlayer` and `granularSource`.
60
+ The direct `read` has the existing bounded linear interpolation, slice, looping,
61
+ NaN-position and nonfinite-tap policies; the inaccessible sentinel cannot be
62
+ selected by a reader. Interpolation spans a loaded-prefix/recorded-suffix
63
+ boundary normally.
64
+
65
+ `sample.load(data)` is the existing native Float32Array event-boundary operation:
66
+ it replaces the take, truncates to capacity, sets length to that truncated
67
+ length, and increments revision. Empty data logically unloads. Subsequent
68
+ recording appends after the loaded prefix. If load and record occur in the same
69
+ quantum, native message dispatch installs the prefix before the audio ticks;
70
+ reset at the first tick can still empty it. No decoding, file/device I/O, new
71
+ transport, acknowledgement or serializer is added. Follow the input budgeting,
72
+ preload and callback-copy limitations in [sample.md](sample.md).
73
+
74
+ Appending does not change revision. Existing readers observe the growing length
75
+ on each call/tick rather than a frozen initial bound. In particular, loop period
76
+ grows, and grains whose positions move within the newly valid region can read
77
+ it. A one-shot that already became inactive does not revive just because length
78
+ grows. For stable loop/grain boundaries, stop recording before playback.
79
+ Reset/load changes revision and invalidates existing playback/grains under
80
+ their normal contract; a held player gate is not a fresh trigger. Trigger/gate
81
+ and read/write order remain explicit responsibilities of the composition.
82
+
83
+ ## Amplitude and bounded storage
84
+
85
+ Core 0.4.1 scalar buffer stores flush magnitudes below 1e-30. Direct f32 recording
86
+ would therefore discard some representable signal values. This candidate uses:
87
+
88
+ 1. `loadedPCM`: C f32 frames, populated only by native event `copyFrom`.
89
+ 2. `recordedPCM`: C+1 f64 frames, recording `f64(input) * 2^128`.
90
+ 3. `loadedPrefixLength`: the native i32 boundary selecting the loaded prefix or
91
+ recorded suffix. Reset makes it zero; load makes it the new take length.
92
+
93
+ Exact power-of-two scaling keeps every finite nonzero f32 input above the native
94
+ store floor and below f64 overflow. Readers divide by the same factor. It is a
95
+ storage encoding, not audio gain/normalization: decoded finite values retain
96
+ their amplitude, including maximum f32 and the smallest f32 subnormal. Signed
97
+ zero records as positive zero. NaN and ±Infinity record as zero, without relying
98
+ on the output scrubber. Event-loaded invalid values are independently sanitized
99
+ at read taps, matching the original resident sample.
100
+
101
+ The extra recorded frame is an unreachable sentinel. Paused/full/reset ticks
102
+ write only zero there. Native `select` evaluates both branches, so using a
103
+ sentinel avoids accidentally rewriting any exposed sample. Both buffers are
104
+ persistent; buffer storage is exactly `12*C + 8` bytes, 786,440 bytes at maximum
105
+ capacity, plus three i32 persistent states, transient read position, normal
106
+ runtime/IO overhead, and any caller-created ingress ring. Both read branches
107
+ are evaluated; the split costs two bounded memory reads per interpolation tap.
108
+
109
+ Reset and short/empty replacement are **logical invalidation, not secure
110
+ erasure**. Unreachable loaded/recorded tails remain in snapshots until later
111
+ overwritten. No growing allocation or clearing pass occurs per reset. Native
112
+ snapshots include both buffers, length, loaded-prefix boundary and revision;
113
+ the transient read-position cache is excluded. Exact continuation is promised
114
+ only for the same graph, configuration and sample rate. It is not a portable
115
+ audio-file format or an arbitrary live-restore guarantee.
116
+
117
+ ## Evidence and limits
118
+
119
+ `tests/resident-recorder.spec.ts` compares every output sample against an
120
+ independent scalar oracle at 44.1/48/96 kHz. Coverage includes pause/full/held
121
+ reset, pre/post-write reads, prefix-to-suffix interpolation, short/empty/oversize
122
+ replacement, capacity one/65,536, f32 extremes/subnormals/nonfinite input,
123
+ inaccessible sentinel and unchanged loaded bytes, growing player/granular
124
+ composition, wrapping revision and exact snapshots.
125
+
126
+ `tests/resident-recorder-packed.test.mjs` installs the real tarball into an
127
+ isolated locked consumer, checks public TypeScript, renders native DSP against
128
+ the independent oracle, verifies same-schema snapshot continuation, and builds
129
+ the actual Vite worklet/WASM at 48 kHz. Manifests retain exact source, tarball,
130
+ consumer-lock, rendered audio and build hashes. The consumer audio is original
131
+ synthetic signal, **CANDIDATE**, without normalization or a limiter.
132
+
133
+ The separate `tests/capture-freeze-browser.test.mjs` requires actual 48 kHz
134
+ worklet recording, deterministic native capture limits, pause/full-stop,
135
+ existing-player readback, prefix/suffix interpolation and native PCM/history
136
+ restoration. It uses synthetic graph input; no microphone or device is opened.
137
+ This browser stage must pass on the exact final head before merge.
138
+
139
+ Offline multirate success, worklet build and actual browser execution are separate
140
+ evidence. There is no device capture, listening approval, realtime deadline,
141
+ seamless take transition, antialiasing, long-form recording or streaming claim.
142
+ Core's non-48-kHz browser loader restriction remains unchanged.
143
+
144
+ Live core 0.4.1 restoration copies DSP state before restoring host AudioParam
145
+ values. Old record/reset/player controls can therefore act on restored state
146
+ before the saved controls arrive. The browser fixture first renders the saved
147
+ record, play, input, limit, position and reset controls, then restores the take.
148
+ The earlier already-full-buffer pass masked the write hazard and is not general
149
+ atomic-restore evidence. Browser playback checks are functional PCM/readback
150
+ checks; phase-aligned quantum continuation is separately tested offline. See the
151
+ [observed frozen-tail restore boundary](freeze-reverb.md#live-browser-restore-ordering).
@@ -0,0 +1,190 @@
1
+ # Bounded resident WSOLA time stretching
2
+
3
+ Status: **CANDIDATE**, catalog §11. `residentTimeStretch` from
4
+ `@denaudio/den/resident-time-stretch` changes resident mono duration independently
5
+ of grain-local pitch. It is a waveform-similarity overlap-add (WSOLA) variant with
6
+ fixed sparse comparison points. This is not a renamed two-head live pitch delay,
7
+ a phase vocoder, formant preservation or a transparent general-purpose stretcher.
8
+
9
+ ## Entry, native ownership and cost
10
+
11
+ Instantiate with `sampleRate: ctx.sampleRate` (integer 8000..192000), an existing
12
+ `ResidentSample`, `hopSamples: 128 | 256` (default 256) and
13
+ `searchFrames: 0 | 8 | 16 | 32 | 64 | 128` (default 64). H is the synthesis hop in
14
+ output frames; the two overlapping source grains each span 2 H output frames.
15
+ S is the search radius in source frames. Neither scales automatically with rate.
16
+ At 48 kHz the default window spans 10.67 ms and search±64 spans±1.33 ms; these are short
17
+ creative windows, especially for bass, speech and complex/polyphonic material.
18
+
19
+ The asset remains bounded to 1..65536 mono f32 source frames with a fixed
20
+ integer source rate 8000..192000 Hz, using the existing native resident ingress. See [sample.md](sample.md)
21
+ for delivery, truncation, revision, RAM and callback-copy caveats. Decode and
22
+ validate PCM outside audio processing, preload with audible playback gated off,
23
+ and establish the delivery boundary before starting. There is no new loader,
24
+ streaming path, JS-rendered audio, external WASM or unworklet modification.
25
+
26
+ Call `tick(controls, everyNSamples)` once per sample inside stride-1 native
27
+ `forSample((i, everyNSamples) => ...)`. This module allocates 64 f64 transient
28
+ comparison values (512 bytes), fixed scalar state and no length-dependent output
29
+ buffer. Resident PCM and ingress allocation are shared, not copied per instance.
30
+ Sixteen maximum ingress slots reserve 4 MiB, plus 256 KiB resident PCM.
31
+
32
+ The graph uses bounded native counted loops, not expanded code per PCM frame or
33
+ search candidate. Every 128 output samples it evaluates 64 reference reads and
34
+ (2 S+1)×64 candidate comparisons; each comparison uses zero-padded linear sampling.
35
+ H256 commits only alternate evaluations. This eager 128 scheduling, including
36
+ when stopped, is deliberate: unworklet 0.4.1's native hop counters are not snapshot
37
+ state, whereas the module's persistent H clock is. Muting/gate-off does not remove
38
+ search cost. S0 still evaluates the zero-offset baseline. Maximum S128 means
39
+ 16,448 comparisons per 128-sample quantum; there is no average-cost CPU-saving claim.
40
+
41
+ ## Controls, launch and transitions
42
+
43
+ Controls are `{ gate, trigger, reset, durationScale, pitchRatio }`.
44
+
45
+ - Gate rise or true trigger requests a launch at the next global H boundary,
46
+ with 0..H−1 samples of request waiting. Controls latch at actual launch, not at
47
+ the request sample. Gate must remain high. A held trigger restarts every hop.
48
+ - `durationScale` and `pitchRatio` must each be finite f32 in [0.5,2]. Both latch
49
+ for the whole run; edits alone do nothing. Invalid controls reject that launch,
50
+ clear its request and leave any already-running playback intact. There is no
51
+ implicit clamp, smoothing or fallback transposition.
52
+ - A launch resets source/output time to 0. Its first H-sample grain is direct,
53
+ without an artificial fade-in. Later boundaries use overlap. Launch/retrigger
54
+ can click. The resident is read with zero padding on both ends; no loop or
55
+ reverse mode is supplied.
56
+ - Gate-off cancels immediately without release/tail. Reset cancels and silences
57
+ its current sample and clears pending launch, EOF and rejection. Held reset
58
+ stays silent; a still-high gate requests a new launch after release.
59
+ - Reset does not rephase the global hop. A resident revision immediately cancels
60
+ playback and pending launch. A gate already held high does not silently start
61
+ the replacement, although a new trigger in the same sample can request it.
62
+ Empty/unloaded assets are missing/silent.
63
+
64
+ ## Exact time, search and sample law
65
+
66
+ Let L be the loaded source length, R the output rate, Rs the fixed source rate,
67
+ D the accepted durationScale and P the accepted pitchRatio. A launch has exactly
68
+ T=ceil(L×R/Rs×D) active output samples, n=0..T−1. The logical source position is
69
+ n×L/T. Thus requested duration rounds upward by less than one output frame, and
70
+ pitch never changes T. The largest permitted T is 3,145,728 output frames.
71
+ Fractional resident source rates are rejected by this module, without changing the residentSample API.
72
+
73
+ Duration is an exact rational ceil for integer rates and the accepted f32 D.
74
+ The implementation splits L×R×(D×2^24) into exact small integer products and
75
+ compares their integer part/remainder to a nearby quotient, avoiding a pre-rounded
76
+ rate quotient or an epsilon adjustment. The independent scalar oracle uses
77
+ BigInt rational arithmetic. The regression L=147 / Rs=44100 / R=48000 has exactly
78
+ 80/160/320 samples at D=0.5/1/2; the original pre-rounded-rate calculation wrongly
79
+ produced 81/161/321 and is preserved in the initial review evidence.
80
+
81
+ Grain-local sampling advances q=P×Rs/R source frames per output sample. At each
82
+ later synthesis hop the new nominal grain start is n×L/T, independent of P. The
83
+ previous grain continues from its selected start plus Hq. Alignment compares that
84
+ previous tail with candidate starts nominal+d, where integer d is 0,±1,…,±S.
85
+ Each candidate's score is the sum of squared differences at 64 positions
86
+ j×H/64, j=0..63, using the same q. Baseline d=0 is first, then +1,−1,+2,−2,…;
87
+ strictly smaller cost wins, so exact ties prefer the smallest absolute offset,
88
+ then the positive offset. S0 is ordinary overlap-add without alignment.
89
+
90
+ At local output index k=0..H−1, output is
91
+ (1−k/H)×previousTail[k] + (k/H)×newHead[k]. First-hop output is newHead[k]. Each
92
+ endpoint outside [0,L) is zero, including fractional intervals [−1,0) and [L−1,L).
93
+ Indices are explicitly clamped before resident access; padding is not endpoint
94
+ hold, accidental index wrap or stale PCM reuse. EOF outputs silence exactly from
95
+ n=T onward, with no post-EOF overlap tail. A late grain may encounter padding
96
+ before EOF, so active duration does not imply nonzero PCM at every active sample.
97
+ For the 8192-frame, 1/128-cycles/source-frame cosine fixture at D=2/P=2, a late
98
+ 256-frame region peaks near 0.309 instead of the 0.5 interior amplitude. This is
99
+ retained edge attenuation, not corrected by hidden normalization.
100
+
101
+ Interpolation and overlap weights are convex. Peak is bounded by the largest
102
+ absolute finite resident PCM value (within floating rounding), and supported
103
+ constant interiors retain unity gain. There is no loudness/peak normalization,
104
+ limiter, clipping or implicit dry mix. Nonfinite PCM taps become zero through the
105
+ resident contract. Internal exact binary scaling by 2^192 avoids the native tiny
106
+ scalar-store scrub during sample/error accumulation. Even the 64-term score for
107
+ opposite finite f32 extrema is below 2^649, safely finite in f64; this scaling does
108
+ not alter audio gain or relative candidate scores.
109
+
110
+ Return values are `{ output, active, position, ended, pending, rejected, missing,
111
+ selectedOffset }`. `active` refers to the current output sample, including zeros.
112
+ `position` is the current logical n×L/T, held at L after EOF; it is not either
113
+ aligned readhead's position. `ended` becomes true on the first EOF sample and
114
+ stays true until reset/replacement/new launch. Early gate-off does not set EOF.
115
+ `rejected` retains the latest launch result until another attempted launch/reset.
116
+ `selectedOffset` is the last committed hop's integer d. Rejected and selectedOffset
117
+ are diagnostics, not proof that any output was audible.
118
+
119
+ All launch/clock/time/grain/EOF and resident state use native persistent snapshots;
120
+ comparison, score and output scratch are transient. Restoration is bit-identical
121
+ only for the same graph, configuration, resident metadata and rate, at the native
122
+ quantum boundaries. In particular, H256 halfway-hop and pending launches must
123
+ continue exactly after a 128-aligned snapshot. No serializer or migration layer is
124
+ introduced.
125
+
126
+ ## Audible and acceptance limits
127
+
128
+ Linear interpolation is not bandlimited. Pitch-up aliases original source
129
+ frequencies above R/(2 P) Hz, or R/(2 P Rs) cycles per source frame.
130
+ Interpolation images/attenuation remain below that limit. All pitch/formants move
131
+ together, and input frequencies above the destination Nyquist can alias even at
132
+ P1 when source/output rates differ.
133
+
134
+ Sparse waveform matching can choose an unsuitable periodic alignment. Its 64
135
+ comparison points do not represent every output sample. Limited S cannot align
136
+ long periods or all components of a polyphonic signal. Linear crossfades can
137
+ cancel, amplitude-modulate and color tones, especially without alignment; grains
138
+ can duplicate, omit or smear transients. Short input and both padded edges receive
139
+ only partial windows, and EOF may truncate a window/transient abruptly. No
140
+ speech/consonant preservation, stereo coherence or artifact-free claim follows.
141
+ Use a separate envelope/crossfade if a click-free articulation is required.
142
+
143
+ The algorithm is an original bounded implementation of the waveform-alignment
144
+ idea described in Driedger and Müller, [A Review of Time-Scale Modification of
145
+ Music Signals](https://www.audiolabs-erlangen.de/content/resources/MIR/00_PCD_AudioLabs/2016_DriedgerMueller_TSMOverview_AppliedSciences_ePrint.pdf),
146
+ 2016, §4. Their descriptions explain the method/tradeoffs, not this implementation's
147
+ quality or performance. No third-party implementation was copied.
148
+
149
+ Verification covers independent scalar source-time/search results and independent
150
+ analytic duration/carrier checks at 44.1/48/96 kHz, arbitrary-PCM unity, mismatched
151
+ source/output rates, short and zero-padded boundaries, invalid controls/nonfinite
152
+ PCM, finite extremes/subnormals, launch/reload/reset/EOF and same-schema exact
153
+ snapshot continuation. Packed public import/types, actual native ingress/render,
154
+ fixed memory, graph/WASM cost and candidate audio are separate evidence. Every
155
+ render is CANDIDATE; no approved golden is replaced. There is no human-listening,
156
+ browser/hardware realtime deadline, host integration or full catalog acceptance.
157
+
158
+ Initial max-size feasibility (65536 frames/H256/S128, local Node 24) measured
159
+ 296,328 graph bytes, 50,051 WASM bytes, 4,521,984 native bytes and 640 ms compile.
160
+ Twenty unloaded quanta observed 43.3 ms cold and 2.42 ms warm maximum. These include
161
+ an eagerly evaluated search even though the resident was unloaded; they do not
162
+ prove loaded/device performance. The cold quantum greatly exceeds the tested audio
163
+ budget, and 2.42 ms already exceeds the 1.33 ms budget at 96 kHz. Later packed diagnostics
164
+ must be read alongside this retained observation, not substituted for a guarantee.
165
+
166
+ Independent review retained a dominant-frequency counterexample: a 997.13 Hz
167
+ source at 32000 Hz, output 44100 Hz, L=16384, H128/S128 and D=1.75/P=1.25 produces
168
+ an interior Hann-window spectral peak near 1248.1625 Hz instead of the nominal
169
+ 1246.4125 Hz, a +1.75 Hz deviation. Sparse, integer-offset waveform alignment can
170
+ shift the dominant output frequency as well as color its amplitude. The separate
171
+ 437.3 Hz test's <=0.75 Hz result describes only its measured rate/control matrix;
172
+ it is not a universal tuning tolerance. P specifies the exact local grain read
173
+ slope, not an artifact-free global spectral-peak guarantee.
174
+
175
+
176
+ Further independent measurements strengthen these limits. The same 997.13 Hz
177
+ source/control case at 96000 Hz output has a dominant peak near 1249.4125 Hz,
178
+ +3 Hz from the nominal 1246.4125 Hz. The spectral peak is not guaranteed to track
179
+ P to a universal tuning tolerance.
180
+
181
+ For the 48000 Hz, L=8192, period-128 cosine at D=2/P=2 and H256/S64, the intended
182
+ 750 Hz Hann-window carrier amplitude over output frames [15360,16128) is about
183
+ 0.00145034, versus about 0.499999997 in the interior. The last nonzero output is
184
+ frame 16159 although the active duration ends at 16384. This is padded-edge
185
+ cancellation/distortion and loss of intended-carrier energy, not merely a
186
+ uniform gain reduction. The earlier late-region peak near 0.309 therefore must
187
+ not be read as preserved pitch/tone quality at the edge. No gain compensation
188
+ is applied. Exact logical duration and local read slope do not guarantee
189
+ transparent duration/pitch output, preserved tail content or completed
190
+ catalog time-stretch quality.
package/docs/sample.md ADDED
@@ -0,0 +1,191 @@
1
+ # Resident sample, multisample and granular candidates
2
+
3
+ Status: **CANDIDATE**. These are bounded DSP building blocks for catalog §3.
4
+ No listening approval, bandlimited claim, browser/host delivery proof, streaming,
5
+ asset loader, hardware latency or real-time deadline guarantee is implied.
6
+
7
+ ## Public entry and ownership
8
+
9
+ Import `residentSample`, `samplePlayer`, `multisamplePlayer`, `granularSource`
10
+ and their configuration/control types from `@denaudio/den/sample`. Create named
11
+ subgraph instances with native `instantiate`. One resident sample can be shared
12
+ by multiple readers; player/grain state is private to each instance.
13
+
14
+ A resident is **mono**, has a fixed capacity of 1–65,536 f32 frames and a fixed
15
+ source rate in [8,000,192,000] Hz. Its current loaded length is independent of
16
+ capacity. Each player's `sampleRate` is its processor's `ctx.sampleRate`.
17
+ Decoding, channel selection/downmixing and finite-PCM validation belong in the
18
+ caller, outside audio processing. Channel/rate changes require a newly configured
19
+ resident; there is no embedded file format or mutable sample-rate metadata.
20
+
21
+ Use the existing native ingress, for example:
22
+
23
+ ```ts
24
+ const sample = instantiate(residentSample,
25
+ { capacity: 65536, sourceSampleRate: 48000 }, { name: 'sample' });
26
+ const load = event<{ data: Float32Array }>({
27
+ from: 'main', name: 'load', capacity: CAPACITY_16,
28
+ payloadCapacity: sample.capacity * Float32Array.BYTES_PER_ELEMENT,
29
+ });
30
+ load.onReceive(({ data }) => sample.load(data));
31
+ ```
32
+
33
+ Validate bytes/frames on the sending side. Send at most the declared capacity,
34
+ and handle native transport overflow/rejection. In the verified native offline
35
+ path, a typed-array payload larger than the per-slot budget is silently truncated
36
+ before `load` sees it; `length()` then reflects that truncated input. Do not rely
37
+ on transport rejection to validate a full asset. The DSP `load` itself truncates
38
+ an input longer than the resident to capacity; an empty input unloads. A shorter
39
+ replacement leaves unreachable bytes in the fixed buffer but all readers clamp
40
+ to the new length, so old data cannot leak into playback. The unused tail remains in native snapshots
41
+ until overwritten; replacement/unload is logical invalidation, **not secure erasure**.
42
+
43
+ **Loading is not callback-free.** `Buffer.copyFrom` is a bounded native
44
+ `memory.copy` in the process message-dispatch phase, before that quantum's audio
45
+ samples. Prepare/decode outside the callback, preload with playback gated off,
46
+ and start audible playback only after the delivery boundary is established by
47
+ the host. There is no new loader or acknowledgement protocol here. The
48
+ postMessage fallback can allocate on the audio thread; offline tests do not
49
+ establish live transport safety.
50
+
51
+ Specify `CAPACITY_16` explicitly. Core 0.4.1 allocates the payload budget once per
52
+ ring slot: at the maximum resident size, 16 × 65,536 × 4 = 4 MiB for the payload
53
+ ring, plus 256 KiB PCM and small ring/state overhead. Multiple ingress/resident
54
+ pairs multiply this budget. Sixteen distinct maximum-capacity residents, each
55
+ with its own 16-slot ingress, reserve 64 MiB payload + 4 MiB PCM before ring/state
56
+ overhead; that bank is materially more expensive than 16 zones sharing one resident
57
+ (4 MiB payload + 256 KiB PCM). Simultaneously queued full loads also copy 4 MiB
58
+ into the bank at a boundary and are unsuitable for an unmeasured audible reload.
59
+ The native default ring has 256 slots and would use
60
+ 64 MiB payload space for that same capacity. Capacity is not an allocation-free
61
+ loading claim or a recommended live reload size.
62
+
63
+ PCM, loaded length and revision are persistent native snapshot state. The revision
64
+ increments as a wrapping i32 on every load, including empty input. Readers compare
65
+ for inequality, not ordering, so signed/unsigned wrap preserves invalidation.
66
+ The bounded 16-message ring cannot wrap the whole 32-bit revision between adjacent
67
+ audio ticks. Snapshots promise exact continuation only for the **same graph,
68
+ configuration, resident metadata and rate**; they are not a portable sample bank
69
+ format or cross-configuration migration contract.
70
+
71
+ ## Playback and slices
72
+
73
+ `samplePlayer({ sampleRate, sample, startFrame?, endFrame?, loop?, releaseFrames? })`
74
+ accepts `tick({ gate, trigger, reset, rate })` and returns
75
+ `{ output, active, position, missing }`.
76
+
77
+ - Slice boundaries are construction-time frame indices `[startFrame,endFrame)`.
78
+ The end intersects the loaded length; an empty intersection is missing/silent.
79
+ - `rate` is a signed ratio clamped to [-16,16]. The increment is
80
+ `rate * sourceSampleRate / sampleRate`. Zero holds; negative starts at `end-1`.
81
+ Rate changes while playing change direction/speed without repositioning.
82
+ - Gate-rise or a true trigger while gate is high starts/restarts. Trigger is
83
+ level-sensitive, so holding it repeats the initial sample. Reset has priority;
84
+ held reset is silent, then a still-high gate restarts on the next tick.
85
+ - Linear interpolation explicitly wraps the second tap at the loop boundary.
86
+ One-shot interpolation holds the final frame until the phase exits `[start,end)`.
87
+ No index relies on unworklet's automatic wrap. Length 0/1 is defined and bounded.
88
+ - Gate-off applies a linear `releaseFrames` ramp; the first off sample subtracts
89
+ `1/releaseFrames`, so release 1 stops immediately. Default 0 stops immediately.
90
+ Natural one-shot exhaustion stops independently. Use an envelope for a richer
91
+ articulation law. `active` describes the current emitted sample, including a
92
+ zero PCM value; it does not predict whether the next sample is active.
93
+ - Replacement invalidates the current playback. A gate held high does not
94
+ silently restart a new asset; a fresh gate-rise/trigger can start it. A load and
95
+ fresh trigger in the same quantum use the newly copied asset.
96
+ - Nonfinite rates use bounded fallback/saturation; NaN means zero speed.
97
+ Nonfinite PCM taps become zero individually, without poisoning reader state.
98
+ Fractional position/control state uses exact binary scaling internally to avoid
99
+ the native scalar-store flush below 1e-30; finite extreme PCM is not reduced to
100
+ a ±1-only assumption. Tiny/subnormal controls are tested against full finite PCM.
101
+ - Public resident `read(position,start,end,loop)` also intersects end with loaded
102
+ length. Non-loop out-of-range positions hold the nearest valid endpoint; it is
103
+ not a zero-padded reader. Direct frame positions saturate to the signed i32 index range, with NaN
104
+ selecting start; start/end are i32 nodes. The player/granular methods maintain
105
+ their own tighter phase/control bounds. Player/grain activity masks enforce their own silence.
106
+
107
+ Loop interpolation smooths fractional lookup but **does not make a discontinuous
108
+ loop seamless**. For an explicit tail/head overlap, use the separate
109
+ [crossfaded loop candidate](loop-crossfade.md), whose effective period is shortened
110
+ from `L` to `L-F`. `samplePlayer` itself is unchanged. Automatic zero-crossing search,
111
+ time-stretching, bandlimited rate conversion and seamless dynamic asset replacement
112
+ are not provided. A high-frequency sine played at double rate demonstrably aliases.
113
+
114
+ ## Multisample mapping
115
+
116
+ `multisamplePlayer({ sampleRate, zones, releaseFrames? })` accepts
117
+ `tick({ key, velocity, gate, trigger, reset, rate })` and returns
118
+ `{ output, active, zoneIndex, missing }`.
119
+
120
+ There are 1–16 fixed zones. Each specifies `sample`, integer `keyLow/keyHigh`,
121
+ normalized `velocityLow/velocityHigh`, integer `rootKey`, optional `tuneCents`
122
+ (±2,400), `gain` (±4), and optional slice/loop configuration. All endpoints are
123
+ inclusive; the first matching zone wins overlaps. Inputs clamp to MIDI 0–127 and
124
+ velocity 0–1; fractional keys are supported for transposition inside a zone.
125
+
126
+ Zone, key and velocity latch at note-on. Root-key tuning multiplies the rate by
127
+ `2 ** ((key-rootKey)/12 + tuneCents/1200)`, then the player limits the final signed
128
+ ratio to ±16. Output gain includes latched velocity × zone gain. No match returns
129
+ zone -1/missing/silence. A matching but unloaded sample remains the selected missing
130
+ zone; there is no hidden fallback. A new note resets other zone players, so this is
131
+ a **monophonic selector**, not a polyphonic allocator or release-tail mixer.
132
+ Clipping/limiting is not implicit; zone gain can exceed unity. Preserve f32
133
+ headroom when applying gain: extreme finite PCM multiplied by a gain above one
134
+ can overflow the output type; the raw-resident finite-range proof is not an
135
+ unbounded-gain guarantee.
136
+
137
+ ## Granular source
138
+
139
+ `granularSource({ sampleRate, sample, maxGrains, seed?, loop? })` accepts
140
+ `tick({ gate, reset, positionFrames, jitterFrames, rate, durationSeconds, densityHz })`
141
+ and returns `{ output, activeGrains, onset, dropped, missing }`.
142
+
143
+ - Fixed pool of 1–32 grains. First free slot wins; a full pool drops the onset
144
+ instead of stealing a grain or allocating memory. Density is limited to
145
+ 0–2,000 Hz. Gate-rise creates an initial grain even at zero density.
146
+ - Position and uniform signed jitter are in source frames. Jittered start clamps
147
+ into the loaded asset. Signed pitch rate and source/host conversion match playback.
148
+ Position, rate and duration latch at onset; later controls affect new grains.
149
+ - Duration rounds to the nearest output frame, bounded to 3 frames–2 seconds.
150
+ The triangular window is `max(0,1-abs(2*age/(duration-1)-1))`, exactly zero at
151
+ both endpoints. This is not a Hann window or a constant-overlap-add guarantee.
152
+ - `seed` is an integer 1–2,147,483,646. Park–Miller 48,271 modulo 2,147,483,647
153
+ advances for every scheduled onset, including a dropped onset. Reset restores
154
+ the seed; snapshot restore preserves it and every grain. Repeatability is for
155
+ identical controls/rate/configuration. The oscillator/noise PRNG is not reused.
156
+ - Scheduling phase is f64. Nominal onset times can round by one output sample at
157
+ nonintegral periods; this candidate is not a host musical clock or event output.
158
+ - `loop:true` wraps each grain within the whole resident. `loop:false` masks
159
+ reads outside the asset to zero. Gate-off stops scheduling while existing
160
+ windows finish; reset or asset replacement cancels all existing grains.
161
+ - Sum is divided by configured `maxGrains`, not active count. This gives a simple
162
+ overlap bound for finite bounded PCM and avoids changing gain as grains appear,
163
+ but sparse pools are correspondingly quieter. It is not loudness normalization.
164
+
165
+ Bounded [take recording](resident-recorder.md), [rolling history](live-buffer.md),
166
+ [live grains with explicit write-age/expiry rules](live-granular.md), and
167
+ [loop crossfades](loop-crossfade.md) are separate public candidates. The live
168
+ history is not a ResidentSample and must not be cast into this reader. Stereo
169
+ grains, broader spectral sources and host streaming remain outside this contract.
170
+
171
+ ## Evidence and acceptance
172
+
173
+ The native-ingress probe and `tests/sample.spec.ts` use synthetic original
174
+ impulse/ramp/sine PCM, independent numeric references, 44.1/48/96 kHz playback,
175
+ missing/short/empty replacement, finite controls, exact snapshot continuation,
176
+ zone boundaries, seeded grain onset/window/drop and explicit alias evidence.
177
+ Maximum resident/ring allocation and maximum pool/zone checks are separate from
178
+ a hardware performance claim. Packed-consumer evidence is produced by
179
+ `tests/sample-packed.test.mjs`; its manifests and audio are always CANDIDATE.
180
+ No existing approved audio baseline is replaced.
181
+
182
+ Maximum capacity is a functional offline bound, not a recommended live preset.
183
+ `makeProcessor({ zoneCount: 1, grainCount: 4 })` in the isolated consumer is a
184
+ modest composition for separate browser acceptance; its graph holds the fixture
185
+ clock/gates until native loaded length is nonzero, so delayed delivery cannot
186
+ consume the audition before PCM arrives. The maximum fixture uses
187
+ 16 zones and 32 grains. Per-module 1/4/32-grain and 1/16-zone measurements are
188
+ reported separately. The public compile driver cannot inject load messages, so
189
+ these timing diagnostics explicitly start unloaded and must not be presented as
190
+ audible-pool/device performance. The actual loaded offline renders separately
191
+ prove maximum grain activity, PCM, onset/drop and continuation behavior.
@@ -0,0 +1,103 @@
1
+ # Integrated sound audition: numerical headroom
2
+
3
+ Status: **CANDIDATE**. Offline numerical checks do not clear the real-time gate,
4
+ constitute human listening approval, or authorize production deployment.
5
+
6
+ ## Scope and reproduction
7
+
8
+ `tests/integration-consumer/sound-matrix-render.mjs` runs in the isolated consumer
9
+ installed from `npm pack`, using public `@denaudio/den/instrument` and
10
+ `@denaudio/den/delay-fx` imports. `tests/integration.test.mjs` invokes it after the
11
+ existing diagnostic renderer, which remains unchanged. No package DSP, public
12
+ sound settings, gains, or normalization are altered by this audition.
13
+
14
+ At 48 kHz it renders all four instruments through diagnostic delay, Chorus, and
15
+ Rhythmic Delay. Each source runs through the FX defaults, dry mix, and maximum
16
+ wet mix with feedback 0.5. The ten source traces produce 90 stereo FX rows:
17
+
18
+ - Every displayed note at MIDI velocity 127, followed by note-off, retrigger
19
+ during release, and verified exact source silence after the final release.
20
+ - The main hold button for every instrument, including each polyphonic chord.
21
+ - Overlapping notes and last-held-note return for the mono instruments.
22
+ - Four coherent, same-pitch voices at **every displayed pitch** for Diagnostic
23
+ and Pad, followed by release. This is stronger than the UI's velocity 100.
24
+ - Pad holds exceed its 0.8 s attack + 1.2 s decay + one complete 8.333... s LFO
25
+ period. This applies separately to the chord and every coherent pitch.
26
+ - Live FX history between register steps and six additional seconds after each
27
+ complete trace, exposing accumulated echoes and release tails.
28
+
29
+ The traces contain 205.848 seconds of source audio. The initial standalone run
30
+ completed in about 93 seconds on the cloud executor. This is test duration, not
31
+ real-time performance evidence.
32
+
33
+ The script writes `integration-sound-matrix.json`, including all settings, MIDI
34
+ events, time windows, source/output peak and RMS, output headroom, release/end-tail
35
+ measurements, scrub/nonfinite/clipping counts, reference errors, and bounds. It
36
+ also writes twelve `sound-matrix-{instrument}-{effect}.wav` files: main-button
37
+ holds at each FX's defaults, unnormalized at Master 1, with SHA-256 hashes.
38
+ These files are listening **candidates**, never expected audio or golden files.
39
+
40
+ ## Independent checks and bound
41
+
42
+ Each FX sample is compared with an independent unbounded-timeline delay oracle.
43
+ The oracle uses a direct-form bilinear biquad rather than the production
44
+ circular buffers and state-variable-filter recurrence, and evaluates the
45
+ Chorus modulation and stereo phase separately. Maximum sample error is limited
46
+ to `6e-6`; the observed worst error was `2.1909363568e-6`.
47
+
48
+ Every render must have zero scrubbed samples, zero nonfinite output, and zero
49
+ samples at or above full scale. Dry output must be bit-identical to its source.
50
+ Deliberately clipped, nonfinite, and above-ceiling fixtures verify that the
51
+ numerical guards reject invalid material.
52
+
53
+ The test first enforces explicit per-instrument **source regression ceilings**:
54
+
55
+ | Instrument | Source ceiling | Rationale |
56
+ | --- | ---: | --- |
57
+ | Diagnostic | 0.20001 | Four 0.05-gain voices, small numerical margin |
58
+ | Bass | 0.24 | Twice the 0.12 raw gain allows resonant/transient overshoot |
59
+ | Percussion | 0.16001 | One 0.16-gain voice, small numerical margin |
60
+ | Pad | 0.18001 | Four 0.045-gain voices, small numerical margin |
61
+
62
+ These are independently chosen numerical guards, not a theorem about every
63
+ possible MIDI schedule or every time-varying instrument filter. Bass actually
64
+ reaches 0.12384, demonstrating why raw gain alone is not a sufficient bound.
65
+
66
+ For a recorded source peak M, mix m, and feedback f, the FX bound is:
67
+
68
+ `M * ((1 - m) + m / (1 - f)) * Master + 1e-6`
69
+
70
+ This bound is independent of the rendered FX peak. All three feedback paths
71
+ are flat or fixed Q=0.5 low-pass; their nonnegative impulse responses have unit
72
+ L1 norm at the candidate cutoffs. Interpolated delay, including Chorus's moving
73
+ readheads, cannot increase the supremum. Therefore f <= 0.5 permits at most a
74
+ factor of two. The oracle asserts valid timing and unclipped modulation.
75
+
76
+ With **Master range 0–1, default 1**, the largest fixed source ceiling yields
77
+ an FX ceiling below 0.480001. Every output is also required to stay below the
78
+ separate **0.5 ceiling**, preserving at least 6.02 dB to full scale for tested
79
+ material. This explicitly replaces reliance on the prior diagnostic-only 0.8
80
+ limit. No limiter or normalization is inserted.
81
+
82
+ ## Measured maxima
83
+
84
+ Maximum absolute sample across all scenarios and controls at Master 1:
85
+
86
+ | Instrument | Raw source | Diagnostic delay | Chorus | Rhythmic Delay |
87
+ | --- | ---: | ---: | ---: | ---: |
88
+ | Diagnostic | 0.187219307 | 0.276219070 | 0.368202865 | 0.326295972 |
89
+ | Bass | 0.123841561 | 0.163044527 | 0.205227017 | 0.176587388 |
90
+ | Percussion | 0.158914849 | 0.213010132 | 0.240000606 | 0.219704628 |
91
+ | Pad | 0.168394074 | 0.208122611 | 0.312585205 | 0.228740290 |
92
+
93
+ All 90 rows passed. The worst output, **0.368202865**, is Diagnostic → Chorus,
94
+ four coherent voices across the displayed register, wet mix 1 and feedback 0.5.
95
+ It leaves **8.678 dB** of measured headroom. Every render had zero scrubbed,
96
+ nonfinite, or clipped samples.
97
+
98
+ Master 2 would not clip these particular recorded samples, but would exceed the
99
+ chosen 0.5 regression ceiling and surrender 6.02 dB of output margin. The
100
+ integrated candidate therefore keeps Master 0–1 without changing public sound
101
+ parameters. The matrix does not establish a universal arbitrary-MIDI safety
102
+ proof, real-time capacity, device playback behavior, or physical listening
103
+ approval; those remain separate checks.