@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,163 @@
1
+ # Rolling live sample buffer candidate
2
+
3
+ Status: **CANDIDATE / NOT_CLEARED**. Catalog §3 calls out fixed-capacity live
4
+ history, its age and read/write competition. This module provides that native
5
+ storage/read contract. It adds no microphone/device capture, host transport,
6
+ streaming, overdub, sample loader, grain scheduler, normalization or limiter.
7
+ Audio and builds produced by its tests are not listening-approved goldens.
8
+
9
+ ## Entry and coordinates
10
+
11
+ Import `liveSampleBuffer`, `LiveSampleBufferConfig`, `LiveSampleBufferControls`,
12
+ `LiveSampleBufferRead`, `LiveSampleBufferStatus` and `LiveSampleBuffer` from
13
+ `@denaudio/den/live-buffer`. Instantiate once in a native processor:
14
+
15
+ ```ts
16
+ const history = instantiate(liveSampleBuffer,
17
+ { capacity: 65536, sampleRate: ctx.sampleRate }, { name: 'history' });
18
+ // Within forSample, choose the order deliberately:
19
+ const previous = history.readAge(f64(0));
20
+ const status = history.tick({ input, record, reset });
21
+ const current = history.readAge(f64(0));
22
+ ```
23
+
24
+ Capacity is an immutable integer in 1–65,536 mono frames. `sampleRate` is the
25
+ incoming processor rate in [8,000,192,000] Hz, without resampling. At capacity
26
+ 65,536, continuous recording retains approximately 1.486 s at 44.1 kHz, 1.365 s
27
+ at 48 kHz or 0.683 s at 96 kHz. Rate conversion, stereo pairing and routing are
28
+ the caller's composition. There is no automatic input-monitor output.
29
+
30
+ `readAge(ageFrames: Node<'f64'>)` returns `{output,available}`. Ages count
31
+ **accepted writes**, not elapsed processor frames or wall-clock time:
32
+
33
+ - Age 0 is the newest accepted write at this call's statement position; age 1
34
+ is the write immediately before it. Fractional age interpolates toward older
35
+ writes. For history [10,20,30] in chronological order, age .25 returns 27.5.
36
+ - A finite age in inclusive [0,length−1] is available. At an integer age, the
37
+ exact recorded frame is returned. At the oldest valid integer, the older tap
38
+ is held at that frame. Capacity one therefore supports only age 0.
39
+ - Negative ages, NaN, infinities, unfilled ages and ages beyond retained history
40
+ return zero with `available:false`. No endpoint clamping, zero-padded blend,
41
+ extrapolation or wrap from oldest to newest is applied to unavailable ages.
42
+ - The circular storage address wraps, but the age coordinate does not. An age
43
+ just above length−1 is unavailable even if stale bytes exist at that address.
44
+ Fractional reads never interpolate into overwritten or unfilled history.
45
+ - Pausing freezes all ages and bytes. It does not insert silent frames. A fixed
46
+ age is a moving delay tap while recording, then a frozen sample while paused.
47
+ After another write, a surviving sample's age increases by one; at capacity,
48
+ the former oldest sample expires.
49
+
50
+ This interface intentionally is **not `ResidentSample`**. It has no `load`,
51
+ chronological `read`, or `sourceSampleRate` member, and must not be passed to
52
+ existing `samplePlayer`, `multisamplePlayer` or `granularSource` through a type
53
+ cast. Those readers interpret stable asset positions; rotating chronological
54
+ indexes would silently change an active grain's meaning. Stable captured takes
55
+ remain the separate `residentTakeRecorder` contract. The separate
56
+ [`liveGranularSource`](live-granular.md) reader explicitly tracks accepted-write
57
+ motion and expiry, with writer-before-reader order and a shared reset.
58
+
59
+ ## Writer, ordering and revision
60
+
61
+ Call `tick({input,record,reset})` exactly once per incoming sample:
62
+
63
+ - Initially length, head and revision are zero; every read is unavailable.
64
+ - When record is true and reset false, accept one input frame. Before capacity,
65
+ length grows by one. At capacity, length stays fixed and the oldest frame is
66
+ overwritten. A false record pauses without modifying exposed history.
67
+ - Reset is level-sensitive and wins over record. It sets head and length to
68
+ zero and accepts no input. A held reset stays empty. Still-high record resumes
69
+ on the first non-reset tick, beginning a new history at frame zero.
70
+ - The returned `{written,full,length}` describes the operation's result.
71
+ `written` includes accepting zero or sanitizing invalid input to zero; `full`
72
+ becomes true on the write that first fills capacity and remains true through
73
+ later overwrites or pauses until reset.
74
+ - Native statement order is the visibility boundary. A read before `tick` sees
75
+ the previous history, including before reset; a read afterward sees the
76
+ complete write or empty reset. PCM is written before head and length advance.
77
+ Calling several readers between writes is supported. This is not a concurrent
78
+ host writer or cross-thread consistency contract.
79
+ - `length()` returns the current retained count. `revision()` is a wrapping i32
80
+ mutation serial incremented once for each accepted write or reset tick.
81
+ Pause leaves it unchanged. Reset+record increments once, not twice. Compare
82
+ revisions for equality/inequality, never numeric ordering; 2^32 mutations can
83
+ revisit the same value. Revision is neither an absolute timestamp nor a stable
84
+ frame identity, and no compatibility with resident asset revisions is implied.
85
+
86
+ Read-before-write age 0 is the last accepted frame, not the current input.
87
+ Read-after-write age 0 is current input when accepted, including at ring wrap.
88
+ This order matters for zero-delay and feedback composition. No implicit delay,
89
+ feedback loop, read/write safety margin or click-free transition is supplied.
90
+
91
+ ## Amplitude, storage and snapshots
92
+
93
+ All finite f32 input is supported, including values beyond ±1, full f32 extrema
94
+ and subnormals. NaN and ±Infinity are recorded as zero, without depending on the
95
+ output scrubber. Input signed zero records as positive zero. Linear reads use
96
+ f64 intermediate arithmetic, so opposite full-range taps do not overflow their
97
+ difference. Interpolation is not anti-alias filtering; rapid age changes can
98
+ click or alias, and downstream gain or feedback can overflow independently.
99
+
100
+ Core 0.4.1 scalar stores flush magnitudes below 1e-30. The native persistent
101
+ buffer therefore stores `f64(input) * 2^128`, decoding by the same exact binary
102
+ factor. This is a storage encoding, not signal gain or normalization. Every
103
+ finite nonzero f32 is above the store floor and far below f64 overflow when
104
+ encoded. The transient age cache is likewise scaled by 2^192 to retain tiny
105
+ fractions that can make a representable f32 difference against full-range PCM.
106
+
107
+ Storage is one fixed C+1 f64 buffer: exactly `8*(C+1)` bytes, or 524,296 bytes at
108
+ capacity 65,536, plus three persistent i32 states (head/length/revision), a
109
+ transient f64 read cache and normal runtime/IO overhead. The extra cell is an
110
+ unreachable sentinel. Paused/reset ticks write zero only there because native
111
+ `select` evaluates both branches. Processing allocates no growing history or
112
+ new per-sample buffers; writes and each read use fixed bounded work.
113
+
114
+ Reset is **logical invalidation, not secure erasure**. Old history remains in
115
+ snapshot bytes until overwritten but cannot be read through `readAge`. Snapshot
116
+ state includes the buffer, head, length and revision; the read cache is excluded.
117
+ Exact continuation applies only to the same graph, configuration and sample
118
+ rate with the same subsequent input/control sequence. No new serializer or
119
+ cross-configuration migration is provided. Core 0.4.1 live state restore and
120
+ host AudioParam restore are not atomic; this module does not fix that boundary.
121
+ Establish safe host controls before restoring a live writing graph. Offline
122
+ continuation evidence is distinct from an arbitrary live-control restore claim.
123
+
124
+ ## Verification and limits
125
+
126
+ `tests/live-buffer.spec.ts` compares all pre/post-read, availability and writer
127
+ status samples to an independent chronological-array reference at 44.1/48/96
128
+ kHz. It covers capacity one/two/17, every physical wrap position, fractional ages
129
+ adjacent to newest/oldest retained samples, invalid/unfilled/expired reads,
130
+ pause, held reset, tiny/full-range PCM, tiny f64 ages, revision wrap, snapshots
131
+ while partial/full/paused/reset, stale-byte exclusion, and maximum fixed storage.
132
+ The reference does not use a circular buffer or production addressing helpers.
133
+
134
+ `tests/live-buffer-packed.test.mjs` installs the actual tarball into a locked
135
+ isolated consumer, checks the public types, renders all-sample references and
136
+ same-schema continuation at three rates, then builds actual Vite worklet/WASM
137
+ assets. Its 65,536-frame memory diagnostic actively records through two wraps,
138
+ checks the oldest-age output and confirms constant native memory. Source,
139
+ tarball, lock, audio and build hashes are retained with failure phases.
140
+
141
+ These are bounded offline/build checks, not browser execution, device capture,
142
+ real-time deadline or listening approval. There is no streaming, long-form
143
+ recording, live granular playback, overdub or fixed-latency host integration
144
+ claim. Audio candidates preserve raw gain; no normalization or limiter masks
145
+ their output.
146
+
147
+ ## Small actual-browser composition
148
+
149
+ The separate `tests/live-buffer-browser.test.mjs` requires an actual 48 kHz
150
+ worklet. A native fixture counter gates exactly 3, then 20, accepted writes into
151
+ capacity 8, independently of host timer delays. This counter is test composition,
152
+ not a capacity stop in the rolling-buffer API. An exact binary ramp exposes both
153
+ wraps, every retained sample, fractional/expired reads, pause and reset/resume.
154
+ Native accumulated pre/post-read sums verify statement order during the brief
155
+ writing intervals rather than inspecting only a later silent/paused window.
156
+
157
+ For in-place state restoration, the fixture saves a full wrapped positive
158
+ history and replaces it with a short negative one. It first renders recording
159
+ off, then renders all saved controls and proves the negative mutation remains.
160
+ Restore must recover the original PCM, length, cursor-dependent continuation,
161
+ revision and accumulated sums; every retained age is read back. No microphone,
162
+ host transport, arbitrary live-control atomicity or hardware continuity claim
163
+ is involved. Three-rate native chronological references precede browser execution.
@@ -0,0 +1,179 @@
1
+ # Live history granular reader candidate
2
+
3
+ Status: **CANDIDATE / NOT_CLEARED**. This bounded catalog §3 reader consumes the
4
+ native rolling history from `@denaudio/den/live-buffer`. It supplies a fixed grain
5
+ pool and windows, without an input device, loader, density scheduler, random
6
+ jitter, host transport or streaming contract. Existing resident sample readers
7
+ remain separate; do not cast rolling history to `ResidentSample`.
8
+
9
+ ## Public composition and statement order
10
+
11
+ Import `liveGranularSource`, `LiveGranularConfig`, `LiveGranularControls`,
12
+ `LiveGranularResult` and `LiveGranularSource` from `@denaudio/den/live-granular`.
13
+ Construction accepts `{history: LiveSampleBuffer, maxGrains: number}`. `maxGrains`
14
+ is an integer in 1–8; the history has fixed capacity 1–65,536 and a finite processor
15
+ sample rate in [8,000,192,000] Hz. The reader adds no PCM buffer. Instantiate once:
16
+
17
+ ```ts
18
+ const history = instantiate(liveSampleBuffer,
19
+ { capacity: 65536, sampleRate: ctx.sampleRate }, { name: 'history' });
20
+ const grains = instantiate(liveGranularSource,
21
+ { history, maxGrains: 8 }, { name: 'grains' });
22
+ // Within every stride-1 forSample iteration:
23
+ const status = history.tick({ input, record, reset });
24
+ const result = grains.tick({
25
+ written: status.written, reset, trigger,
26
+ ageFrames, rate, durationSeconds,
27
+ });
28
+ output.write(result.output);
29
+ ```
30
+
31
+ Call the writer exactly once, then each reader exactly once per sample. Pass the
32
+ writer's actual `status.written` and the **same reset signal**. Several readers can
33
+ share that result; no intervening write/reset is allowed. `record` is not a
34
+ substitute for `written`: reset suppresses a write, while an accepted invalid
35
+ input still writes sanitized zero. The history sample rate must match the
36
+ processor. No source-rate conversion is performed. Skipping calls, mismatching
37
+ reset, or supplying a fabricated write status is unsupported.
38
+
39
+ ## Launch controls and pool policy
40
+
41
+ `trigger` is level sensitive: each true non-reset sample requests one new grain.
42
+ A held trigger requests every sample. No gate, automatic onset, density clock or
43
+ queue is implied. The following f32 controls are latched only on successful launch:
44
+
45
+ - `ageFrames`: finite accepted-write age in [0,capacity−1], also currently within
46
+ [0,history.length()−1]. Zero means the newest frame after this sample's write.
47
+ - `rate`: finite signed history frames per output sample in [−16,16]. Positive
48
+ moves toward newer source material; negative moves toward older material.
49
+ - `durationSeconds`: finite [0,2]. Duration is
50
+ `clamp(floor(durationSeconds * history.sampleRate + .5),3,round(2*sampleRate))`
51
+ output samples. Zero is valid and produces a three-frame grain; negative,
52
+ NaN and infinite values are rejected.
53
+
54
+ Invalid controls or an unavailable onset set `rejected:true`. They do not clamp
55
+ to a source endpoint, steal a slot, or alter current grains. Internal eager
56
+ branches use safe operands before integer conversion, including on rejected
57
+ NaN/infinite requests. Valid requests use the first free slot in stable index
58
+ order. A full pool sets `dropped:true`; no stealing, restart or delayed retry
59
+ occurs. `launched` means allocation succeeded. `dropped` and `rejected` are mutually
60
+ exclusive; all three are false without a request or during reset.
61
+
62
+ An existing unavailable grain is terminated before allocation, so its slot is
63
+ available for a new grain on that same sample. Changing age/rate/duration while a
64
+ grain runs only affects later requests. There is no implicit output gain control.
65
+
66
+ ## Accepted writes, movement and expiry
67
+
68
+ Let `a[n]` be a grain's read age at processor sample n. At launch it is exactly
69
+ `ageFrames`, measured **after the current write**, without adding that write again.
70
+ For a surviving grain on the next sample:
71
+
72
+ ```
73
+ a[n+1] = a[n] + ((written[n+1] ? 1 : 0) - latchedRate)
74
+ ```
75
+
76
+ The increment is formed before addition. Continuous writing at rate 1 therefore
77
+ preserves even a tiny representable onset fraction; computing `(tinyAge+1)-1`
78
+ would lose it. At rate 0, the grain stays at the same source identity, whose age
79
+ increases with accepted writes until overwritten. Under continuous writing, rate
80
+ 1 holds a fixed delay age. Pausing freezes history, **not** the grain clock or
81
+ source movement: a positive-rate grain moves toward the newest retained edge and
82
+ may expire. Resuming recording cannot revive a terminated grain.
83
+
84
+ Every read must be in the inclusive interval [0,currentLength−1]. The shared
85
+ reader interpolates toward older frames and holds the oldest valid integer tap.
86
+ There is no implicit newest/oldest safety margin, extrapolation, zero-padded blend
87
+ or wrap of the age coordinate. An out-of-range read immediately terminates that
88
+ grain and increments `expiredGrains`; it does not clamp or resume later. A valid
89
+ launch at age 0 while paused, with positive rate, can have only its silent onset
90
+ and expire next sample. A valid launch does not promise a full audible window.
91
+
92
+ Natural completion wins on the sample after the final window endpoint: a grain
93
+ whose duration ended is already inactive and is not reported as expired. Reset
94
+ has highest priority, immediately silences and clears all grain state, and zeros
95
+ all result flags/counts. It must also reset the shared writer. A held reset keeps
96
+ the graph empty; a still-high trigger requests again on the first non-reset sample.
97
+
98
+ ## Windows, gain and finite precision
99
+
100
+ A grain of N output samples has indices k=0…N−1 and triangular weight
101
+ `max(0,1-abs(2*k/(N-1)-1))`. Both endpoints are exactly zero. Odd lengths reach 1;
102
+ even lengths have a smaller peak. `activeGrains` counts valid reads on the current
103
+ sample, including the zero-weight endpoints and successful launches.
104
+
105
+ Each linearly interpolated f32 history read is weighted and summed in f64. The sum
106
+ is divided by **fixed maxGrains**, then converted once to f32. This is fixed pool
107
+ headroom, not active-count normalization, content normalization or a limiter. A
108
+ single grain in an eight-slot pool is attenuated by eight. Magnitude stays within
109
+ the largest active read magnitude, subject to rounding. Finite PCM above unity,
110
+ f32 extrema and subnormals remain supported; the writer sanitizes NaN/infinite
111
+ input to zero. No clipping masks the candidate audio.
112
+
113
+ Cursor and latched-rate stores use exact binary scaling by 2^192 to avoid core
114
+ 0.4.1's 1e−30 scalar-store floor. The scale is storage encoding, not gain. It does
115
+ not increase f64 precision: movement smaller than a large cursor's representable
116
+ spacing may round away. Linear interpolation is not anti-alias filtering. Window
117
+ cancellation, read-edge expiry, pauses and reset can change sound or truncate
118
+ transients. This is not pitch-perfect, time-transparent or streaming-continuity
119
+ processing, and there is no real-time/device/listening clearance.
120
+
121
+ ## State and verification
122
+
123
+ Each slot has five persistent scalars: active bool, window index i32, duration
124
+ i32, scaled read age f64 and scaled rate f64. The first-free allocation cache is
125
+ transient. The history retains its own fixed buffer/head/length/revision and
126
+ transient read cache. Work and storage are bounded by capacity and maxGrains;
127
+ processing creates no growing history or per-sample buffers.
128
+
129
+ Snapshots must include the writer **and every reader sharing it** together.
130
+ Continuation is exact only for the same graph/configuration/sample rate and
131
+ identical subsequent inputs/controls. Partial-graph restore is unsupported.
132
+ Native live-state restore and AudioParam restore are non-atomic; establish safe
133
+ host controls before restoring. The writer's reset is logical invalidation, not
134
+ secure PCM erasure. These constraints are unchanged by this reader.
135
+
136
+ `tests/live-granular.spec.ts` uses an independent chronological array plus
137
+ absolute rational source positions. It avoids both the ring addressing and the
138
+ production age recurrence; exact rational positions retain tiny fractions while
139
+ accepted-write and playback movements cancel. Evidence covers 44.1/48/96 kHz,
140
+ variable accepted writes, paused/resumed recording, first-free saturation,
141
+ same-sample expiry/relaunch, zero endpoint counts, natural completion, latching,
142
+ invalid eager operands, wrap/read margins, full finite/tiny PCM, snapshots,
143
+ capacity one and maximum capacity with eight slots. The maximum fixture remains
144
+ active across ring wrap and records through two wraps.
145
+
146
+ `tests/live-granular-packed.test.mjs` packages the actual public subpath, installs
147
+ an isolated consumer with pinned core 0.4.1, checks public types, renders the
148
+ independent three-rate reference and same-schema continuation, and builds actual
149
+ Vite worklet/WASM assets. Source/tarball/lock/audio/build hashes and failure phases
150
+ are retained. The runner defaults to a fresh `npm ci` consumer. An explicitly
151
+ approved local-only `DEN_LIVE_GRANULAR_REUSE_LOCKED_CONSUMER` path instead links
152
+ unchanged matching locked dependencies and extracts only den from the real new
153
+ tarball; it rejects this mode in CI, archives and verifies the original package,
154
+ tarball and lock, and records reuse honestly in provenance. These are offline/build candidates, not human-approved goldens or
155
+ browser/device/real-time evidence. A separately verified browser composition may
156
+ add bounded browser evidence without changing that limit.
157
+
158
+ ## Small actual-browser composition
159
+
160
+ `tests/live-granular-browser.test.mjs` uses a muted 48 kHz graph with one grain
161
+ and a 64-frame history. Native counters bound the writer and a three-sample
162
+ request burst, so a delayed host callback cannot change the expected fill or
163
+ drop counts. A paused original ramp supplies a known constant source identity.
164
+ Every observed output sample is compared to its independent triangle-window
165
+ value using a native frame/launch counter, rather than fitting output RMS.
166
+ Changing onset controls while that grain runs must preserve its latched motion
167
+ and duration.
168
+
169
+ The fixture saves a noninitial positive grain, clears the shared graph, and
170
+ creates a different negative history/grain. It renders recording and triggering
171
+ off with every saved control before restoring the entire graph; the negative
172
+ mutation must still be audible in the muted observation path until restore.
173
+ The recovered positive history and window phase must match the sample oracle.
174
+ Resumed writes then overwrite the original source identity and must permanently
175
+ expire the grain. Empty-history rejection and held reset remain separate checks.
176
+ An independent chronological/source-identity oracle exercises the composition
177
+ at three rates before the actual browser gate. This is bounded native behavior,
178
+ not device capture, scheduling infrastructure, runtime clearance or an acoustic
179
+ listening judgment.
@@ -0,0 +1,183 @@
1
+ # Lookahead limiter and three-band dynamics candidates
2
+
3
+ Status: **CANDIDATE**. These additive modules extend catalog section 9 with a
4
+ bounded sample-peak lookahead limiter and a phase-compensated three-band
5
+ composition. They reuse unworklet 0.4.1 and the existing reviewed peak follower,
6
+ LR4 crossover and dynamics. Generated audio is not a golden or human-approved
7
+ sound. Offline correctness is not browser or hardware real-time clearance.
8
+
9
+ ## Measured real-time boundary
10
+
11
+ The first clean packed proof (`a2f2b0e`, 2026-10-05) measured the full
12
+ 17-output three-band fixture at 44.1/48/96 kHz. It exceeded the local 128-frame
13
+ quantum budget in **28/512, 66/512 and 221/512 blocks**, respectively. P99 times
14
+ were 4.59/5.01/4.40 ms. At 96 kHz its median was 1.277 ms against a 1.333 ms
15
+ quantum. **This multiband graph is not real-time cleared.** The maximum-2048-sample
16
+ limiter had no budget exceedances in the same local diagnostic, which also does
17
+ not establish browser or device clearance.
18
+
19
+ These are local Node-driver wall times after 128 warm-up blocks, repeating the
20
+ first 128-frame input/control block, excluding audio I/O copies and browser
21
+ scheduling. They are useful negative evidence, not a universal capacity limit or
22
+ an acceptance benchmark. Stage materialization reduced the separate mixed-mode
23
+ comparison fixture from 6,752,682 to 153,584 WASM bytes while preserving all
24
+ 17 output channels byte-for-byte at three rates. That compile-size improvement
25
+ does not remove the measured runtime boundary.
26
+
27
+ ## Entry contracts
28
+
29
+ - `@denaudio/den/lookahead-limiter`: `lookaheadLimiter`,
30
+ `LookaheadLimiterConfig`, `LookaheadLimiterControls`.
31
+ - `@denaudio/den/multiband-dynamics`: `multibandDynamics`,
32
+ `MultibandDynamicsConfig`, `MultibandDynamicsControls`,
33
+ `MultibandDynamicsBandConfig`, `MultibandDynamicsBandControls`.
34
+
35
+ Instantiate with stable unique names and call each instance exactly once per
36
+ sample. `sampleRate` is an integer from 8000 through 192000 Hz and must equal the
37
+ enclosing processor's compile rate. All runtime numeric inputs are native f32
38
+ Nodes and reset is a native boolean Node. There is no alternate runtime,
39
+ parameter, snapshot or routing framework. State restoration requires the same
40
+ schema, construction configuration and rate.
41
+
42
+ ## Limiter: exact window and fixed latency
43
+
44
+ Configuration is `{sampleRate, lookaheadSamples}`. The delay is a required integer
45
+ in **0..2048**; it cannot be changed at runtime. Latency is exactly that many
46
+ samples, exposed as construction-time `latencySamples` and `latencySeconds`.
47
+ The capacity is a resource boundary, not a claim that every setting is real-time
48
+ safe. At 44.1/48/96 kHz, the maximum is about 46.44/42.67/21.33 milliseconds.
49
+
50
+ `tick(left, right, {ceilingDb, release, reset})` returns limited `left`/`right`,
51
+ latency-matched `dryLeft`/`dryRight`, and `windowPeak`, `envelope`, `gain` and
52
+ `ceiling` diagnostics. There is no implicit makeup, mix or bypass. Dry mixing or
53
+ another processor after the limiter can violate the ceiling.
54
+
55
+ Let N be the delay and p[n] be the larger absolute current L/R input. On sample n,
56
+ the detector target is the **inclusive maximum of p[n-N] through p[n]**, bounded
57
+ by the latest reset. The output program sample is input[n-N]. Processing order:
58
+
59
+ 1. If reset, invalidate all preceding audio and detector history.
60
+ 2. Insert current L/R and their peak in the ring and max tree.
61
+ 3. Recompute the tree path and obtain the inclusive window maximum.
62
+ 4. Update the existing peak follower with zero attack and requested release.
63
+ 5. Apply the current ceiling/envelope ratio to the N-sample-old program.
64
+ 6. Advance the ring cursor.
65
+
66
+ Attack is immediate. Lookahead conservatively attenuates audio up to N samples
67
+ before a transient emerges; it does not interpolate a gain ramp. The follower
68
+ release is the reviewed one-pole amplitude detector recurrence, not linear-gain
69
+ or dB-gain release: `c=1-exp(-1/max(1,release*sampleRate))` for positive release,
70
+ otherwise c=1. The envelope never falls below the current window maximum.
71
+ Release is clamped to 0..30 seconds, with nonfinite values falling back to zero.
72
+ Abrupt ceiling/release edits can cause discontinuities and pumping is possible.
73
+
74
+ `ceilingDb` is clamped to -120..0 dBFS, with nonfinite values falling back to zero.
75
+ Its f32 exponential amplitude is returned as `ceiling`. The exact discrete
76
+ contract is `abs(output[channel][n]) <= ceiling[n]`, not an infinite-precision dB
77
+ identity. A downward ceiling edit constrains **the output sample at that moment**,
78
+ including previously buffered audio; ceilings are not delayed with the program.
79
+ The gain is `min(1,ceiling/envelope)`, or one for a zero envelope. Multiplication
80
+ stays binary64 until final output conversion, so maximum finite f32 audio at a
81
+ -120 dB ceiling is not multiplied by a quantized subnormal f32 gain. Returned
82
+ `gain` is a rounded f32 diagnostic; it may underflow or have poor relative
83
+ precision at extreme inputs and must not substitute for the actual audio path.
84
+ The final output clamp guards floating-point ceiling rounding.
85
+
86
+ All finite f32 program inputs, including subnormals and above-full-scale values,
87
+ are preserved on the dry path. As in native unworklet state, negative zero is
88
+ normalized to positive zero. Nonfinite program samples become zero. This
89
+ limiter measures samples, not intersample reconstruction peaks. It performs no
90
+ oversampling, true-peak estimation or certification.
91
+
92
+ ### Reset and memory
93
+
94
+ Reset invalidates history before the current input. An N>0 limiter then emits N
95
+ silent samples while filling; N=0 processes the current sample immediately with
96
+ fresh detector history. Holding reset high thus keeps N>0 output silent, whereas
97
+ N=0 continues limiting each current sample independently. Reset is not a
98
+ click-free bypass operation. Dry and wet always share the same delay/reset rule.
99
+
100
+ There are N+1 stereo ring positions and a power-of-two max tree. Every sample
101
+ updates only one leaf and `ceil(log2(N+1))` ancestors. No data-dependent search,
102
+ allocation, epoch counter or reset-time full-buffer scan occurs. At maximum N,
103
+ three binary64 buffers occupy 98,320 bytes plus a fixed small scalar history.
104
+ All audio/peak buffer values use exact power-of-two scaling, preserving f32
105
+ subnormals through unworklet's less-than-1e-30 state-write flush.
106
+
107
+ The reset-validity invariant is an initially empty prefix of leaves. During
108
+ sequential refill, an included sibling subtree starts before the valid-prefix
109
+ end and has already been rebuilt since reset; a sibling starting at or beyond
110
+ that end is read as zero. The prefix expands by one leaf per sample. After full
111
+ refill, all real leaves remain valid through wraps, while padding leaves are
112
+ always excluded. This also holds at N=0, non-power-of-two window lengths,
113
+ first wrap, repeated resets and repeated wraps. No stale pre-reset peak can
114
+ reappear. Rings, tree, cursor, prefix count and follower state are persistent in
115
+ native snapshots.
116
+
117
+ ## Three-band phase-compensated dynamics
118
+
119
+ Configuration is `{sampleRate, bands:[lowConfig,midConfig,highConfig]}`. Each band
120
+ configuration has the existing `DynamicsConfig` mode, operation and optional
121
+ hysteresis, excluding sampleRate. Controls are `{lowCutoffHz,highCutoffHz,bands,
122
+ reset}`; each of the three control records is `DynamicsControls` without reset.
123
+ Each band uses its own filtered L/R program as a max-absolute linked stereo
124
+ sidechain. There is no external sidechain or lookahead in this composition.
125
+
126
+ Finite audio is passed without input clipping; nonfinite samples become zero.
127
+ The **caller headroom precondition for the tested boundary is |L|,|R| <=16**.
128
+ This is not an arbitrary-full-f32-input guarantee. Recombination and transients
129
+ can exceed input or full-scale peaks even when all dynamics gains are at most
130
+ one. This processor has no output ceiling. Apply the separate limiter afterward
131
+ if a discrete sample ceiling is needed.
132
+
133
+ Cutoffs use finite fallbacks of 200 and 2000 Hz respectively, clamp individually
134
+ to `[20,min(20000,0.45*sampleRate)]`, then sort ascending. Reversed controls are
135
+ therefore equivalent to sorted controls. Equal cutoffs are permitted; the middle
136
+ branch does not vanish merely because the cutoffs coincide. Each edit takes
137
+ effect on the current sample without clearing filter or dynamics history.
138
+
139
+ At fixed cutoffs, writing Lf/Hf for the existing LR4 low/high transfers and
140
+ Af=Lf+Hf for their positive-polarity allpass sum, the bands are:
141
+
142
+ - Low: `Llow * Ahigh` (the extra high-crossover sum compensates its phase).
143
+ - Middle: `Hlow * Lhigh`.
144
+ - High: `Hlow * Hhigh`.
145
+
146
+ At unity dynamics gains, the complete transfer is **Alow * Ahigh**, of unit
147
+ magnitude with frequency-dependent phase. The low-band compensation is essential;
148
+ naively splitting only the high branch a second time does not reconstruct this
149
+ transfer. No explicit integer sample delay is added, but IIR phase/group delay
150
+ is frequency dependent. This is not identity PCM or a linear-phase crossover.
151
+ Static transfer guarantees do not imply flat magnitude under moving cutoffs.
152
+
153
+ The results contain `left`, `right`, phase-matched `dryLeft`/`dryRight`, three
154
+ existing dynamics result records `low`/`mid`/`high`, and effective sorted cutoff
155
+ Nodes. The dry outputs are the unprocessed **allpass recombination**, not original
156
+ PCM. Dry/wet mixing can use this phase-matched reference, but dynamically unequal
157
+ band gains still change phase and magnitude. No implicit output normalization is
158
+ performed. All six crossover instances and three dynamics instances reset before
159
+ the current sample; their native state is independent and snapshot-persistent. Two fixed five-element
160
+ scaled binary64 scratch buffers materialize existing f32 crossover stage
161
+ boundaries once instead of re-expanding them at each consumer. They add 80 bytes
162
+ and are transient: every element is overwritten before use on each sample, so
163
+ they need no saved history. Exact scaling preserves nonzero f32 values and the
164
+ original stage rounding; native signed-zero normalization still applies.
165
+
166
+ ## Verification scope
167
+
168
+ The focused tests use an independent naive timeline maximum for the limiter,
169
+ independent exponential recurrence for its detector, RBJ direct-form-I filter
170
+ references for static bands, and complex transfer products for recombination,
171
+ including DC. They cover 44.1/48/96 kHz, capacity endpoints, first-fill and repeated
172
+ wraps, reset and held reset, current-output ceiling automation, malformed inputs,
173
+ full-range and subnormal limiter audio, per-band gain controls, cutoff sorting
174
+ and equality, same-schema snapshot continuation, finite caller headroom and zero
175
+ scrub diagnostics. A maximum-capacity compile/memory check is bounded evidence,
176
+ not a deadline assertion.
177
+
178
+ The isolated consumer installs the actual packed public exports, strict-checks
179
+ TypeScript and renders the same candidate surface with independent numerical
180
+ assertions. Its manifests record exact source/package hashes, rates, controls,
181
+ raw candidate audio and native diagnostics. Browser, external-device and
182
+ maximum-capacity real-time support are not established by offline evidence.
183
+ Independent review and exact-head aggregate CI remain required before merge.
@@ -0,0 +1,99 @@
1
+ # Crossfaded resident loop candidate
2
+
3
+ Status: **CANDIDATE**. This additive reader uses the existing resident PCM ingress,
4
+ native state and linear interpolation. It does not change `samplePlayer`.
5
+ No listening approval, seamless arbitrary-content claim, antialiasing, streaming,
6
+ loader, time-stretching or browser/realtime deadline proof is implied.
7
+
8
+ ## Entry and effective period
9
+
10
+ Import `crossfadedLoopPlayer` and `CrossfadedLoopConfig` from
11
+ `@denaudio/den/loop-crossfade`; import `residentSample` and the shared
12
+ `SamplePlayerControls` from `@denaudio/den/sample`.
13
+
14
+ Construction accepts `{ sampleRate, sample, startFrame = 0,
15
+ endFrame = sample.capacity, crossfadeFrames, releaseFrames = 0 }`.
16
+ The fixed slice is `[startFrame,endFrame)`; `crossfadeFrames` is an integer in
17
+ `[0,floor((endFrame-startFrame)/2)]`. Zero explicitly disables the overlap.
18
+ Resident and host rates follow the existing [8,000,192,000] Hz contract.
19
+ `releaseFrames` is an integer in `[0,ceil(sampleRate*10)]` output samples.
20
+
21
+ For the currently loaded asset define:
22
+
23
+ - `S = startFrame`, `E = min(endFrame, sample.length())`, `L = max(0,E-S)`
24
+ - `F = min(crossfadeFrames,floor(L/2))`
25
+ - `P = L-F`, the **effective loop period in source frames**
26
+
27
+ The playback phase `q` traverses `[0,P)`. The primary read position is
28
+ `x = S+F+q`. In the last `F` source frames of that cycle (`q >= P-F`),
29
+ read the tail at `x`, read the head at `S+q-(P-F)`, and linearly blend
30
+ `(1-w)*tail + w*head`, where `w = (q-(P-F))/F`. Outside the overlap,
31
+ read only the primary position. Overlap taps use nonwrapping resident linear
32
+ interpolation, which holds the last endpoint between `E-1` and `E`.
33
+ At the phase seam the head approaches `S+F`, the next primary start.
34
+ When `F=0`, use ordinary wrapping linear interpolation over the whole slice.
35
+
36
+ This makes the repeated cycle `L-F` source frames long, rather than `L`.
37
+ For fixed nonzero rate, the cycle lasts `P / abs(rate*sourceRate/hostRate)`
38
+ output frames (not necessarily an integer). At unit rate with equal rates,
39
+ an eight-frame slice with two overlap frames repeats every six samples.
40
+ The first `F` source frames appear only as the incoming overlap head;
41
+ forward triggering starts at `S+F`, not at the asset attack at `S`.
42
+ Do not use this reader when an unchanged loop duration or dry attack is required.
43
+
44
+ The law is unity-sum linear, not equal-power. It bounds the output peak by the
45
+ largest finite source tap before release gain. Correlated equal signals do not
46
+ receive a +3 dB bump. Different phases can cancel or comb; mismatched transients
47
+ can soften, repeat or smear. Even though the continuous interpolated overlap
48
+ meets at its endpoints, one-sample overlaps or large playback increments can
49
+ still produce large adjacent output steps. This is not a universal click-remover.
50
+
51
+ ## Timeline, controls and lifecycle
52
+
53
+ `tick({ gate, trigger, reset, rate })` returns
54
+ `{ output, active, position, phase, periodFrames, crossfadeFrames, missing }`.
55
+ `phase` and `position` describe the current sample before advancement.
56
+ `periodFrames` and returned `crossfadeFrames` report effective `P` and `F`.
57
+
58
+ - `rate` is signed, clamped to [-16,16], with NaN treated as zero and infinities
59
+ saturated. Advance by `rate * sample.sourceSampleRate / sampleRate`.
60
+ Zero holds the current phase. A change in direction changes only advancement.
61
+ - Gate-rise, or a true trigger while gate is high, starts/restarts. Forward/zero
62
+ starts at `q=0`; negative starts at `q=P-1` (primary position `E-1`). Reverse
63
+ traverses the **same blended periodic waveform backwards**, including its
64
+ overlap. It does not choose a different, direction-dependent fade waveform.
65
+ Holding trigger repeats that initial sample.
66
+ - Reset wins over trigger/gate, is silent, and resets phase to zero and gate
67
+ memory to low. Releasing reset with gate still high starts again.
68
+ - Gate-off release matches `samplePlayer`: the first off sample subtracts
69
+ `1/releaseFrames`; zero or one stops immediately. Release never exceeds one.
70
+ `active` describes the current emitted sample, even when the PCM value is zero.
71
+ Inactive playback freezes its phase; inactive output is zero.
72
+ - Each resident load, including an empty or identical replacement, invalidates
73
+ playback and resets its phase. Held gate does not restart the new asset.
74
+ A fresh gate-rise/trigger on that tick can start the newly loaded data.
75
+ - A shorter replacement clips both heads and reduces `F` and `P`. One available
76
+ frame gives `F=0`, `P=1` and a constant sample. Empty slice gives missing true,
77
+ silence, and both reported lengths zero. No stale PCM tail is read.
78
+ - `position` is the primary source position, not a unique representation of both
79
+ blended taps. While missing it reports `S`; phase is zero.
80
+
81
+ Use at most one `tick` per player instance per output sample; different named
82
+ players can share one resident and their returned values may be consumed after
83
+ other shared-resident reads. All playing state and resident PCM remain native
84
+ snapshot state. Source interpolation splits local integer/fractional coordinates before adding
85
+ slice/head offsets, preserving meaningful tiny-phase contributions against large
86
+ finite PCM. Phase accumulation uses f64 arithmetic; increments below the f64
87
+ resolution at the current phase can still be lost. Public phase/position
88
+ diagnostics are f32 and are not a higher-precision transport.
89
+ Same-graph/configuration/metadata/rate snapshots resume exactly;
90
+ there is no serializer, asset migration or cross-rate snapshot contract.
91
+ See [resident ingress and replacement constraints](sample.md) before loading.
92
+
93
+ ## Verification
94
+
95
+ Focused and isolated packed-package checks cover independent scalar timelines,
96
+ source/host conversion at 44.1/48/96 kHz, forward/reverse/fractional and large rates,
97
+ overlap endpoints, short and missing slices, headroom/cancellation/transients,
98
+ reset/release/retrigger, replacement, deferred shared reads and exact snapshots.
99
+ Generated WAV files are CANDIDATE evidence, never approved goldens.