@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,191 @@
1
+ # Instrument performance investigation
2
+
3
+ Correctness follow-up: the former oscillator/filter and amplitude caches also
4
+ flushed recoverable tiny values, independently of the depth-only optimization.
5
+ Those three paths now compose returned Nodes directly. A failing-before/passing-
6
+ after regression checks tiny oscillator → large custom-filter gain, large
7
+ oscillator → tiny amplitude envelope, tiny custom-filter output, smallest f32
8
+ subnormal and negative-zero sign recovery. Private DSL internals are not used.
9
+ Compilation remains possible at 1/4 voices; WASM changes from 804852/1050090 to
10
+ 818400/1104282 bytes. Measurements below predate this correctness correction
11
+ unless explicitly labeled otherwise, and are retained as historical evidence.
12
+ The corrected composition passes 124 numerical/state tests and five packed
13
+ consumers. Its local packed four-voice capture has hot wall/CPU medians 445/436
14
+ µs, 26 wall and 32 CPU threshold exceedances, zero frame gaps and raw/release
15
+ residuals 6.140e-9/8.892e-9. This is a correctness fix with observed performance
16
+ cost, not an optimization or a portable real-time guarantee. Underrun/overrun
17
+ counts require separating CPU from non-CPU intervals and observer/host effects;
18
+ they do not alone identify a product defect.
19
+
20
+ Status: incomplete. Numerical continuity is not a real-time readiness verdict.
21
+ Baseline source is `9a75f9c07e89d0dd77d598bf3e35ee51af1eb26e`, unworklet
22
+ 0.4.1, Chromium 151.0.7922.173, 48000 Hz and 128-frame quanta. All audio is
23
+ CANDIDATE. No hardware loopback or listening approval is claimed.
24
+
25
+ Manual reproductions are in `tests/instrument-performance/`. Each invocation
26
+ creates a separate directory under `artifacts/instrument-investigation/` with
27
+ source copies/hashes, WASM, raw samples, per-block timings and a manifest.
28
+ Preserve all trials, outliers and failed preflights; do not retry in place.
29
+
30
+ ## Cost controls
31
+
32
+ The direct Node 24 trials retain wall time and **process-wide** CPU, which is
33
+ not render-thread CPU. Chrome trace `dur` and `tdur` measure the enclosing
34
+ Render event, including graph/observer work. They are not instrument-only cost.
35
+ The nominal quantum budget is 2666.667 microseconds.
36
+
37
+ | Configuration | Node median, microseconds | Chrome median, microseconds |
38
+ | --- | ---: | ---: |
39
+ | 1 voice capacity | 228 | 110–113 |
40
+ | 4 voice capacity, four sounding | 1047–1049 | 351–357 |
41
+ | 4 voice capacity, none sounding | 1064–1072 | 352–364 |
42
+ | 16 voice capacity | 33053–33602 | 1507–1521 |
43
+
44
+ These are construction capacities, not product limits. Inactive slots currently
45
+ execute DSP. The dynamic voice-policy-only control costs approximately 6, 7 and
46
+ 13 microseconds at capacities 4, 8 and 16; it does not explain whole-engine cost.
47
+ Node and Chrome differ substantially. Yielding between blocks did not resolve
48
+ Node's 16-voice cost, so tier installation is not an established explanation.
49
+
50
+ Materializing the instrument's combined modulation depths and final stereo mix
51
+ reduces WASM from 1083626 to 1049832 bytes and Node median from roughly
52
+ 1015–1063 to 570–659 microseconds. Three captured scenarios are bit-identical,
53
+ including modulation, sample-varying cutoff/Q and release. Chrome medians remain
54
+ 359–366 microseconds versus baseline 358–360; this is **not** a demonstrated
55
+ browser deadline fix. The candidate remains detached from production source.
56
+
57
+ ## Frame metadata counterexample
58
+
59
+ Run `2026-10-04T13-37-51.511Z-browser`, trial `trial0-four-active4-poll1`,
60
+ reproduces currentFrame values 131840, 131840, 132096. Entry and exit values
61
+ agree within each callback; currentTime repeats too. All input blocks contain
62
+ 128 samples. The complete raw stream matches the independent 440 Hz sinusoid
63
+ with maximum residual 6.1391e-9.
64
+
65
+ At anomalous quantum 820, normal continuation has maximum residual 5.4479e-9.
66
+ An independently predicted repeated or skipped 128-frame block instead differs
67
+ by 0.06946. This reproduction shows a frame-clock metadata anomaly without
68
+ duplicated/skipped audio in the captured graph. It does not establish hardware
69
+ output continuity, or retroactively explain the old CI failure whose raw data
70
+ was not retained. CI used Chromium 148; this reproduction uses Chromium 151.
71
+
72
+ The packed test now writes raw/metrics/manifest before asserting frame gaps.
73
+ The CI integration owner should also upload failure artifacts (`always()`),
74
+ because success-only upload loses the evidence needed to diagnose failures.
75
+
76
+ ## Underrun controls without trace or observation worklet
77
+
78
+ Run `2026-10-04T13-48-23.859Z-browser` uses native Analyser capture of the final
79
+ 32768 samples only. This is not full-stream continuity evidence. Chrome tracing
80
+ and the capture AudioWorklet are both absent. Native playbackStats counters are
81
+ sampled at startup, after one audio second, and at the end; differences below
82
+ exclude startup. All three trials per condition are retained.
83
+
84
+ | Condition | Steady underrun events, trials 0 / 1 / 2 | Duration, seconds |
85
+ | --- | --- | --- |
86
+ | Native AudioBufferSource | 0 / 0 / 0 | 0 / 0 / 0 |
87
+ | Four-voice instrument | 42 / 0 / 81 | 0.42084 / 0 / 0.81162 |
88
+
89
+ These browser-internal counters are not hardware-loopback measurements. They
90
+ nevertheless prevent dismissing all observed failures as tracing or capture-AW
91
+ overhead. No-trace trials have no per-quantum duration measurement; an empty
92
+ trace must not be interpreted as zero deadline overruns. Aggregate CDP process
93
+ CPU is retained separately and includes initialization and other browser work.
94
+
95
+ Further work must correlate full raw capture, frame metadata, playbackStats and
96
+ trace/no-trace conditions, then validate any instrument change at the same head.
97
+ Shared filter changes are outside this investigation's production ownership.
98
+
99
+ Full raw, no-trace follow-up `2026-10-04T13-50-17.498Z-browser` retains one trial
100
+ of each setting: native without timestamp polling, native with polling, four-slot
101
+ instrument silent, and four-slot instrument sounding. Post-warmup underrun
102
+ counts are respectively 12, 2, 101 and 61; durations are 0.12024, 0.02004,
103
+ 1.01202 and 0.61122 seconds. Native sample counters have zero breaks. Sounding
104
+ instrument PCM residual is 6.145e-9. The silent instrument has two frame metadata
105
+ anomalies; silence cannot establish whether audio frames were lost. These controls
106
+ show both observation overhead and instrument-associated failures. Full raw graph
107
+ continuity and browser output underrun counters describe different boundaries.
108
+
109
+ ## Instrument-only depth materialization
110
+
111
+ `2026-10-04T13-56-04.747Z-owned` compares the original source, depth-only
112
+ materialization, and the earlier depth-plus-final-mix candidate. The latter is
113
+ rejected: a valid `gain=1e-35` output becomes zero at the new state write. The
114
+ regression checks its independently expected nonzero value and downstream gain.
115
+ The upstream f32/f64 state and buffer stores flush magnitudes below 1e-30;
116
+ audio output stores do not. No upstream source was changed.
117
+
118
+ Only combined pitch/cutoff exponents are cached in the selected change. Such
119
+ exponents below 1e-30 already produce an exactly-one rounded octave ratio in
120
+ the existing polynomial. The stored expression is already f32, so other finite
121
+ values gain no extra rounding. Final mix remains an expression. Cache entries
122
+ are overwritten before every read, including reset and restoration.
123
+
124
+ All 42 combinations of 1/4 voices, 44100/48000/96000 Hz and seven scenarios
125
+ (normal, dynamic, reset, cold-node restore, custom parts, tiny output, tiny
126
+ depth) are bit-identical for the depth-only variant, with zero scrubbed samples.
127
+ The six tiny-output comparisons reject the final-mix variant. Raw PCM and
128
+ source copies are preserved for each variant, not just passing cases.
129
+
130
+ | Capacity | Variant | WASM bytes | First process, ms | Hot median, µs |
131
+ | --- | --- | ---: | ---: | ---: |
132
+ | 1 | baseline | 813236 | 66.28 | 231.20 |
133
+ | 1 | depth cache | 804852 | 60.43 | 151.61 |
134
+ | 4 | baseline | 1083626 | 120.00 | 1203.85 |
135
+ | 4 | depth cache | 1050090 | 124.10 | 676.40 |
136
+
137
+ These are single Node timing trials (2500 quanta, hot after 500), with inactive
138
+ voices, held capacity 128 and zero-filled native driver parameter buffers. Every block's wall/process
139
+ CPU is retained. Compile times are 860/858 ms for 1 voice and 962/1012 ms for
140
+ 4 voices; instantiate times are 4.21/3.24 and 5.89/5.56 ms. The first process
141
+ includes lazy compilation. No uniform cold-time improvement is established.
142
+
143
+ Chrome no-trace comparison `2026-10-04T13-58-07.908Z-browser` uses two full-raw
144
+ 12-second trials per variant/capacity, all with held capacity 128. All raw
145
+ sinusoid checks pass and frame metadata gaps are zero. The source snapshots
146
+ contain the exact detached baseline/candidate; production changes during the
147
+ run do not affect those compiled variants.
148
+
149
+ | Capacity / variant | createNode, ms (two trials) | Underrun events | Underrun seconds |
150
+ | --- | --- | --- | --- |
151
+ | 1 baseline | 43.49 / 42.38 | 17 / 25 | 0.17034 / 0.25050 |
152
+ | 1 depth cache | 35.48 / 37.69 | 20 / 40 | 0.20040 / 0.40080 |
153
+ | 4 baseline | 53.42 / 63.84 | 53 / 102 | 0.53106 / 1.02204 |
154
+ | 4 depth cache | 43.96 / 40.62 | 69 / 71 | 0.69138 / 0.71142 |
155
+
156
+ Average reported latency is 31.29 ms in all eight trials. createNode includes
157
+ module/node setup, not the first audio process. Its reduction is not proof of a
158
+ deadline improvement. These trials do **not** demonstrate an underrun fix.
159
+ Although counters are subtracted at the fixture's warm marker, playbackStats
160
+ `totalDuration` is only 0.002666 seconds there. Unlike the unobserved controls,
161
+ these full-capture deltas can include startup and must not be labeled strictly
162
+ steady-state counts. Trace-based per-quantum hot measurements remain separate.
163
+
164
+ Trace follow-up `2026-10-04T14-01-35.795Z-browser` (one trial each) gives:
165
+
166
+ | Capacity / variant | Hot wall median / p99, µs | Wall overruns | CPU overruns | playbackStats underruns |
167
+ | --- | --- | ---: | ---: | ---: |
168
+ | 1 baseline | 111 / 898 | 7 | 8 | 16 |
169
+ | 1 depth cache | 109 / 537 | 3 | 3 | 41 |
170
+ | 4 baseline | 354 / 2189 | 23 | 24 | 53 |
171
+ | 4 depth cache | 356 / 1910 | 13 | 14 | 165 |
172
+
173
+ All raw continuity checks pass; frame gaps are zero. The four-voice candidate
174
+ still has a 39288 µs maximum hot Render event. Median CPU is 103/101 µs for
175
+ one voice and 346/347 µs for four. Neither the smaller tail count in this single
176
+ trace nor the successful PCM checks justify real-time readiness. Browser median
177
+ cost is unchanged and underruns persist. The change is a Node execution and
178
+ generated-size improvement, not a demonstrated browser performance fix.
179
+
180
+ Raw-preserving Node repeat `2026-10-04T14-03-30.314Z-owned-timing` records
181
+ 2500 inactive quanta per variant, PCM and every CPU/wall sample. Hot medians
182
+ are 245/152 µs (one voice) and 1129/617 µs (four), baseline/cache. First-block
183
+ times are 79/91 and 137/112 ms. The original timing-only subtrial retained
184
+ timings but no PCM; it remains preserved as such rather than relabeled complete.
185
+
186
+ To reproduce the selected comparison, run `node tests/instrument-performance/owned.mjs`,
187
+ then supply its evidence directory as `PROPOSALS_DIR` to `benchmark-owned.mjs`
188
+ or to `browser.mjs` with `CONTROL_MODE=owned`. `NO_TRACE=1` separates playback
189
+ counters from tracing, `TRIALS` controls repetitions, and `CHROMIUM_PATH` selects
190
+ the browser executable. Original artifacts and source snapshots are immutable;
191
+ new invocations produce new timestamped directories.
@@ -0,0 +1,130 @@
1
+ # Bass / Percussion / Pad candidates (GEN-619)
2
+
3
+ These are three editable settings of `createInstrument`, using the existing
4
+ stock oscillator, filter, envelopes, LFO and voice policy. Every setting and
5
+ render is **CANDIDATE**. There is no approved golden or physical listening
6
+ acceptance. Runtime remains **NOT_CLEARED**; earlier frame gaps and underruns
7
+ recorded in [PR #26](https://github.com/yuichkun/den/pull/26) are not erased by a
8
+ successful run here. This change is stacked on that unmerged integration
9
+ candidate at `a1a6fd768f7f350f1072babefe00859f1330bfe6`, which includes the
10
+ unmerged instrument composition from PR #22 and main's envelope staging fix.
11
+
12
+ ## Use the existing APIs
13
+
14
+ ```ts
15
+ // processor.ts, compiled by @unworklet/unplugin as usual
16
+ import { createInstrument, bassConfig } from '@denaudio/den/instrument';
17
+ export const processor = createInstrument(bassConfig);
18
+ ```
19
+
20
+ ```ts
21
+ // Browser host, after the normal user-gesture audio startup
22
+ import { createNode } from '@unworklet/core';
23
+ import { bassParameters } from '@denaudio/den/instrument';
24
+ import processor from './processor.ts?worklet';
25
+ const node = await createNode(context, processor, { initial: bassParameters });
26
+ ```
27
+
28
+ `percussionConfig` / `percussionParameters` and `padConfig` / `padParameters`
29
+ work the same way. All 22 native parameters are explicitly present in each
30
+ object; the complete values live in `src/instrument-settings.ts`. Edit those
31
+ ordinary values or the existing node AudioParams. There is no new preset,
32
+ serialization, MIDI, routing or loader framework, no new export-map entry and
33
+ no changed engine defaults. Browser compilation remains 48 kHz only on
34
+ unworklet 0.4.1; 44.1/96 kHz coverage is offline.
35
+
36
+ - Bass: one saw voice, last-held mono legato, gain .12, 5 ms amplitude attack,
37
+ 120 ms decay/release, .7 sustain. The filter rises briefly from 300 to 2400 Hz
38
+ then settles near 455 Hz. Overlapping held notes preserve the transient;
39
+ this does not implement portamento.
40
+ - Percussion: one retriggering sine voice, gain .16, 1 ms amplitude attack,
41
+ 220 ms decay to zero, 40 ms release. Pitch falls two octaves over 60 ms;
42
+ note 36 is about 261.6 Hz at onset, settling to 65.4 Hz. Zero attack enters
43
+ the first decay step, so the first pitch is slightly below that peak. It is
44
+ tonal kick/tom-like percussion, with no noise generator.
45
+ - Pad: four saw voices, gain .045, 800 ms attack, 1.2 s decay to .7 sustain,
46
+ 2.4 s release. The shared .12 Hz LFO has an 8.333 s cycle, ±3-cent pitch
47
+ depth, .08 tremolo depth and .2-octave filter depth. The dry output is dual
48
+ mono; no stereo spread or unison is claimed.
49
+
50
+ All envelopes are linear. Release and retrigger begin at the previously emitted
51
+ level. Hard reset/retrigger/stealing can be discontinuous; this is not a
52
+ click-free claim. Sustain zero does not free a held MIDI identity. Send the
53
+ balanced note-offs supplied by the fixtures. There is no CC64 sustain.
54
+
55
+ Use native `snapshot()` / `restore()` for the **same processor/schema/rate**.
56
+ Construction changes are not portable preset migration. Restoring a new node
57
+ must not revive held notes. For a cold in-place restore, restore, emit `reset`,
58
+ render at least one quantum, then send fresh notes. Reset and MIDI in the same
59
+ quantum follow the reset-wins rule.
60
+
61
+ ## Reproduction and evidence
62
+
63
+ ```sh
64
+ npm ci
65
+ npm run check
66
+ npm test
67
+ # Focused packed candidate check with an installed Chromium:
68
+ CHROMIUM_PATH=/usr/bin/chromium node --test tests/sound-candidates-packed.test.mjs
69
+ ```
70
+
71
+ The regular test command discovers both `tests/instrument-settings.spec.ts`
72
+ and `tests/sound-candidates-packed.test.mjs`. The latter packs den into a clean
73
+ locked consumer, checks the public import and TypeScript declarations, compiles
74
+ all three processors through Vite/unworklet, and runs the muted real Chromium
75
+ MIDI, parameter, release/reset/bypass and snapshot probes at 48 kHz. It does
76
+ not publish a site or change the integration preview.
77
+
78
+ `tests/sound-candidates-consumer/fixtures.mjs` fixes the FIFO MIDI inputs. The
79
+ Bass and Percussion phrases are 262144 frames at 48 kHz. Pad is 768000 frames
80
+ (16 seconds), with note-offs at 8, 9, 10 and 11 seconds. The last 2.6 seconds
81
+ cover silence following the final release. Lower and higher offline rates
82
+ preserve elapsed time by rounding each event to the nearest corresponding
83
+ 128-frame quantum; expanded timestamps and frame lengths are retained.
84
+
85
+ Separate maximum-velocity inputs cover Bass legato and Percussion release
86
+ retrigger; Pad's maximum case uses four coherent A4 notes. Pad also has a
87
+ released-slot reuse / oldest-held steal case and an obsolete note-off that
88
+ must not release the replacement. These stress renders remain separate from
89
+ the three listening phrases.
90
+
91
+ Each run writes a new `artifacts/sound-candidates/<timestamp>/` directory,
92
+ preserving previous failures. Raw stereo float WAVs are unnormalized and dry;
93
+ static min/max waveforms have fixed ±1 scale. The manifest binds the source
94
+ commit/dirty bit and SHA-256 source hashes, tarball/lock hashes, dependency
95
+ versions, WASM size/hashes, verification results and candidate status. The
96
+ render evidence binds every construction option, parameter, expanded MIDI
97
+ array, rate, channels, seed (null), frame length, PCM/WAV hashes, exact-silence
98
+ boundary and peak/RMS/headroom/clipped/nonfinite measurements. Phrase renders
99
+ are repeated and their full PCM hashes must agree. Full-render and named-window
100
+ measurements are both retained.
101
+
102
+ Headroom is measured for these settings and inputs. The full-scale gate rejects
103
+ peak ≥1, nonfinite PCM, or scrubbed samples; it is also tested against deliberate
104
+ wrong signals. **The integration diagnostic's .8 analytic bound does not apply
105
+ to these saw/modulated settings.** No limiter or hidden normalization is added.
106
+ Arbitrary edits, resonances, pitches or voice counts are not covered by a
107
+ measurement for these fixed fixtures.
108
+
109
+ The independent numerical tests use literal candidate values, closed-form
110
+ linear envelope timing and replacement DC/frequency/cutoff probes. Existing
111
+ sine/static-biquad, saw-property, voice-policy and wrong-answer tests remain in
112
+ place. An audio-rate direct-form biquad is not treated as an exact dynamic SVF
113
+ reference. Headless signal checks are not device playback or listening approval.
114
+
115
+ ## Listening and promotion boundary
116
+
117
+ Review the three canonical 48 kHz `*-phrase-48000.wav` files together, at a
118
+ comfortable user-controlled playback level:
119
+
120
+ 1. Bass: low core and upper-register audibility; brief bright transient;
121
+ held-note transition/return without restarting that transient.
122
+ 2. Percussion: purposeful short pitch drop, velocity contrast, ringing/chirp
123
+ quality; the burst intentionally truncates decay and retriggers in release.
124
+ 3. Pad: smooth growth, clear chord and subtle slow motion; staggered independent
125
+ long releases, with voice stealing assessed separately.
126
+
127
+ Any sound-affecting change requires regenerating the raw audio, waveform and
128
+ manifest. Only explicit human approval tied to those hashes, followed by a
129
+ separate reviewed change, can promote a candidate to a golden. This initial
130
+ change does not merge the integration candidate or clear its runtime gate.
@@ -0,0 +1,204 @@
1
+ # MIDI instrument composition
2
+
3
+ `src/instrument.ts` composes the existing voice policy, oscillator, filter,
4
+ Envelope and LFO. `createInstrument(config)` returns an unworklet processor;
5
+ `instrument` is the diagnostic polyphonic default. There is no new MIDI,
6
+ parameter, routing, snapshot, loader or test framework.
7
+
8
+ The initial sound is **CANDIDATE**, not an approved golden or one of the eventual
9
+ bass/percussion/pad presets. Those sounds will use this same engine.
10
+
11
+ ## Construction and replacement
12
+
13
+ Pass a required configuration to `createInstrument`:
14
+
15
+ - `mode`: `mono` or `poly`; `legato`, `capacity`, `heldCapacity` follow the voice
16
+ policy. Mono uses one voice; poly defaults to 4. Engine capacity is currently
17
+ limited to 16, held capacity to the underlying policy's 256. These are fixed
18
+ construction/compilation bounds, not approved product-wide polyphony limits.
19
+ - `waveform`: `sine` (default) or `saw`, using the existing oscillator.
20
+ - `oscillator` and `filter`: optional compatible unworklet `defineSubgraph`
21
+ declarations, with the same required config and `tick` signatures as the stock
22
+ modules. Each is instantiated with separate named state for every voice.
23
+
24
+ `src/instrument-example.ts` demonstrates replacing both parts with custom
25
+ unworklet subgraphs: a half-level sine and backward-Euler one-pole low-pass.
26
+ The example is intentionally sine-only and its filter ignores resonance; it
27
+ illustrates a concrete replacement, not another full instrument or a quality
28
+ upgrade. Its amplitude/response is checked against independent formulas.
29
+
30
+ The processor uses unworklet's compile-time sample rate. Offline coverage is
31
+ 44.1/48/96 kHz; the published 0.4.1 bundler/browser boundary remains 48 kHz.
32
+ The complete 16-voice graph compiles, but the initial real-time trace found
33
+ frequent deadline overruns at that capacity on the validation host. The engine
34
+ therefore starts at four voices; higher capacities require a device-specific
35
+ real-time budget check. A 32-voice probe produced a
36
+ 11,614,744-byte process function before completion-expression materialization, exceeding WebAssembly's 7,654,321-byte function
37
+ limit. The first engine keeps a tested ceiling of 16 at construction;
38
+ 16 voices with 256 held identities are tested. These checks establish compilation
39
+ and bounded rendering, not a mobile-device real-time CPU guarantee.
40
+
41
+ ## Ports and musical controls
42
+
43
+ Use native `node.midi.midi.send(event)` for MIDI, `node.params.<name>` for
44
+ AudioParams, and `node.events.reset.emit({value:1})` for panic. Output `main` is
45
+ two identical channels. Mixing is linear, with no limiter, normalization or
46
+ voice-count attenuation; coherent voices can exceed unity. The diagnostic gain
47
+ is 0.15, and the browser fixture remains muted.
48
+
49
+ `diagnosticInstrumentParameters` is an ordinary initial AudioParam value map,
50
+ usable with unworklet's existing `createNode(..., {initial: ...})`. It is not a
51
+ separate preset/state format. All continuous controls are a-rate:
52
+
53
+ | Controls | Units / bounds |
54
+ | --- | --- |
55
+ | `gain` | Linear [0,1] |
56
+ | `ampAttack`, `ampDecay`, `ampRelease` | Seconds [0,30] |
57
+ | `ampSustain` | [0,1] |
58
+ | `pitchAttack`, `pitchDecay`, `pitchRelease`, `pitchSustain` | Same ADSR units |
59
+ | `filterAttack`, `filterDecay`, `filterRelease`, `filterSustain` | Same ADSR units |
60
+ | `cutoff`, `resonance` | Hz [20,20000], Q [0.5,10]; actual cutoff limited to 0.45×sample rate |
61
+ | `pitchEnvelopeDepth`, `lfoPitchDepth` | Semitones [-24,24] |
62
+ | `filterEnvelopeDepth`, `lfoFilterDepth` | Octaves [-8,8] |
63
+ | `lfoRate`, `lfoAmpDepth` | Hz [0,20], tremolo depth [0,1] |
64
+ | `bypass` | Values >=0.5 mute output |
65
+
66
+ Inputs must be finite and within their declared AudioParam domains. Automation
67
+ and any host smoothing use unworklet/Web Audio, not a den wrapper.
68
+
69
+ Each voice has three instances of the same linear ADSR. The amp envelope governs
70
+ voice lifetime: zero sustain does not free a held voice; release completion does.
71
+ Audio ends on the exact envelope sample. The allocator consumes completion once
72
+ at block end, before the next block-boundary MIDI dispatch; it does not perform
73
+ quadratic allocation-rank maintenance on every audio sample.
74
+ Pitch/filter envelopes may finish earlier or later but cannot keep an amp-silent
75
+ voice allocated. Retrigger/release starts from each envelope's current level.
76
+ Duration quantization, segment latching and zero-duration behavior are those of
77
+ `envelope.ts`; edits affect the next segment except sustain, which tracks while
78
+ held in the sustain stage.
79
+
80
+ One shared free-running LFO drives finite amp/pitch/filter destinations, with no
81
+ implicit note reset. For voice i:
82
+
83
+ - Frequency = MIDI equal-tempered A4=440 Hz × 2^(semitones/12), clamped to
84
+ [0,0.45×sampleRate]. Semitones = pitch-envelope level × envelope depth + LFO ×
85
+ LFO pitch depth, with the combined amount clamped to [-24,24].
86
+ - Cutoff = base cutoff × 2^octaves, then clamped to the filter bounds. Octaves =
87
+ filter-envelope level × envelope depth + LFO × LFO filter depth; the combined
88
+ amount is clamped to [-8,8].
89
+ - Amp = amp-envelope level × normalized MIDI velocity ×
90
+ (1 - tremoloDepth × (1 - LFO)/2). Depth zero is unity; depth one spans [0,1].
91
+
92
+ Both pitch and filter depths are combined in their musical units **before** the
93
+ single final destination clamp. A constant 128-note tuning table supplies MIDI
94
+ base pitches; transcendental pitch ratios use the reviewed modulation module.
95
+
96
+ ## Event order, reset and state
97
+
98
+ MIDI dispatch remains quantum-boundary FIFO in unworklet 0.4.1. Do not claim
99
+ sample-accurate MIDI. On/off pairs in one block can coalesce to no sound.
100
+ Duplicate identities, velocity-zero note-on, steal/release reuse and mono
101
+ last-held-note priority follow `voice-policy.ts`; CC64 is not implemented.
102
+
103
+ - A note/retrigger resets its oscillator phase and filter history, while its
104
+ envelopes retrigger from their current levels. Mono legato leaves those states
105
+ continuous. Hard stealing/retrigger can click; no listening approval implied.
106
+ - CC123 starts release for that channel. CC120 immediately frees its voices and
107
+ clears their DSP states. If another note reuses a cleared slot in the same
108
+ quantum, the clearing sample is silent and its attack begins next sample.
109
+ - `reset` wins over every MIDI event dispatched in the same quantum. It clears
110
+ the held ledger, voice allocation, pending retriggers, envelopes, oscillators,
111
+ filters and shared LFO. Output is immediately silent. Fresh notes in later
112
+ quanta can start normally.
113
+ - Bypass mutes only the output. MIDI, phase, modulation and release continue; a
114
+ held note reappears when unbypassed, while an ended release remains silent.
115
+ - Inactive slots clear their DSP histories. A new note cannot inherit a completed
116
+ or panicked voice's tail. Live held-note state is transient: same-schema
117
+ snapshots restored into a new node restore parameters/shared LFO but do not
118
+ revive held voices. Inactive
119
+ histories are cleared on the first rendered sample, including when a new note
120
+ arrives in the first quantum after restore. That clearing sample is silent;
121
+ the new note begins at phase zero on the next sample. A pristine instance
122
+ needs no extra clearing sample. Native 0.4.1 **in-place** restore only overlays
123
+ persistent slots: it does not initialize transient live voices. For a cold
124
+ in-place restore, send the existing `reset` after restoring, render at least
125
+ one quantum, then send fresh notes. Sending reset and fresh notes in the same
126
+ quantum follows the documented reset-wins policy. No restore wrapper is added.
127
+ Follow the existing
128
+ rendered-parameter snapshot constraint; schema migration is not added.
129
+
130
+ ## Verification and integration
131
+
132
+ `tests/instrument.spec.ts` uses an independent direct-form biquad plus analytic
133
+ sines for the complete signal path, direct closed-form envelopes for per-voice
134
+ release/retrigger, and frequency/cutoff probe replacements for modulation units.
135
+ It tests wrong-answer rejection, stereo equality, zero scrubbed samples,
136
+ polyphony, mono legato, steals, channel panic, reset, edits, bypass and snapshots.
137
+
138
+ The isolated packed fixture checks declarations/build, renders all three offline
139
+ rates, compiles the replacement example, and drives real 48 kHz browser MIDI and
140
+ AudioParams. It produces raw stereo float WAV, a fixed-scale waveform and a
141
+ manifest binding the exact source hashes/commit, package/lock, all diagnostic
142
+ parameters, MIDI, rates, seed and verification. No audio matcher/golden update or
143
+ FFT is used.
144
+
145
+ The public subpaths are `@denaudio/den/instrument` and
146
+ `@denaudio/den/instrument-example`. The isolated packed fixture imports the
147
+ replacement oscillator/filter example through that public boundary and renders
148
+ it at 48 kHz (the stock instrument is rendered at all three offline rates). It does not reach into package internals.
149
+
150
+ ## Sustained real-time gate
151
+
152
+ The instrument-specific browser observer records twelve seconds of actual graph
153
+ output at 48 kHz, including four held A4 voices, note-offs and a one-second
154
+ release. Its fixed capture buffer is test instrumentation only. A sinusoid fit
155
+ from the initial steady window predicts the entire steady interval; a separate
156
+ first-difference bound detects discontinuities and the known release law
157
+ predicts the tail. A separate native `AudioBufferSourceNode` supplies an exactly
158
+ representable reference ramp to the observer's second input. The test requires
159
+ 4,500 complete 128-sample blocks and exact ramp progression through all 576,000
160
+ samples, including the silent tail. Duplicate, dropped, reordered, zeroed and
161
+ short-block counterexamples verify the observation contract.
162
+
163
+ Published `currentFrame` entry/exit values and output timestamps remain in the
164
+ raw evidence. Repeated/skipped published frame values are reported separately
165
+ as `CLOCK_METADATA_ANOMALY_REQUIRES_REVIEW`: a hosted failing and passing capture
166
+ had byte-identical complete WAVs despite different frame metadata. Chromium's
167
+ worklet-scope clock publication can skip a lock-contended update; this mechanism
168
+ does not by itself establish the cause of an individual anomaly. Native ramp
169
+ progression and the independent audio oracle establish the captured graph's
170
+ sequence, not hardware continuity. The artifact retains `NOT_CLEARED`, the raw
171
+ WAV/reference data, waveform, native errors and timestamps. The expected 0.4.1
172
+ `sab-unavailable` notification is recorded; traps and queue/length errors fail.
173
+
174
+ Chrome audio trace is also collected to inspect per-quantum render durations.
175
+ Raw graph continuity alone cannot establish that the hardware output met its
176
+ real-time deadline. Neither this fixture nor offline correctness substitutes for
177
+ hardware-loopback/device testing or listening approval.
178
+
179
+ The trace report includes generated WASM sizes/hashes, wall and CPU render
180
+ durations, and counts above the 128-frame budget (2.667 ms). It excludes the
181
+ first second when reporting steady timing and tests the detector with an
182
+ explicit over-budget counterexample. Any observed steady wall-time overrun
183
+ sets `QUANTUM_OVERRUNS_REQUIRE_REVIEW`; passing numerical assertions does not
184
+ override this readiness finding. A four-voice measurement on the validation
185
+ host had median 0.344 ms, p99 0.992 ms, maximum 14.752 ms and 9 steady wall-time
186
+ overruns. Sixteen voices had median 1.447 ms, p99 4.583 ms and 221 overruns.
187
+ These are host-specific observations, not portable capacity guarantees. The
188
+ raw trace and each run's exact figures are preserved with the candidate WAV.
189
+
190
+ Bounded pitch/cutoff modulation signals and control predicates use existing
191
+ transient unworklet state, because expression reuse alone can expand the graph.
192
+ Oscillator output, filter output and amplitude-envelope level stay expressions:
193
+ native floating-point state writes flush magnitudes below 1e-30, which would
194
+ destroy finite tiny values recoverable by a later custom filter or gain.
195
+ No private memoization API, altered flush threshold or custom-value restriction
196
+ is used. Regression cases include subnormal input, large oscillator/tiny amp,
197
+ tiny filter output and signed zero observed by a replacement filter.
198
+ Transient layout changes are not a migration for snapshots from older revisions;
199
+ the snapshot contract remains identical processor/schema/rate only.
200
+ Voice completion is committed once per block after
201
+ sample processing; this avoids running allocator rank maintenance at audio rate
202
+ while preserving exact sample silence and availability at the next MIDI drain.
203
+ Shared DSP modules remain unchanged; the common expression-expansion/runtime
204
+ performance investigation is still outstanding.
@@ -0,0 +1,114 @@
1
+ # Mobile integration candidate
2
+
3
+ The initial integration candidate is based on reviewed candidate
4
+ `196a1c3ef0e8eda9a007a2474c9d06f7f654acfa` (PR26, including PR28/PR29)
5
+ and main `9fd730bf9afb786edaae808d41208904fb3c844d`. This integration completes
6
+ public subpath access and the permanent packed-site build without changing DSP,
7
+ settings or dependencies. It is not a registry release or a real-time clearance.
8
+
9
+ The owner supplied positive feedback on the three dry sounds and the integrated
10
+ sound/FX preview on 2026-10-05. That candidate listening feedback is recorded in
11
+ [the acceptance record](initial-acceptance.md). No identical listening request is
12
+ pending. Exact-hash golden promotion remains separate; general physical-device
13
+ stability and the instrument runtime gate remain NOT_CLEARED.
14
+
15
+ The isolated `tests/integration-consumer` imports the public `./instrument` and
16
+ `./delay-fx` and `./delay-settings` subpaths from the installed package. The instrument is a processor;
17
+ the delay uses a small processor adapter around the existing subgraph. Its stereo
18
+ Web Audio path is MIDI → instrument → delay → master gain → analyser → destination.
19
+ MIDI remains the existing unworklet quantum-boundary interface. No new MIDI,
20
+ parameter, snapshot, serialization, routing or preset framework is introduced.
21
+
22
+ The sound selector offers Diagnostic, Bass, Percussion and Pad. The three sound
23
+ candidates are the unchanged public `createInstrument` configurations and all 22
24
+ native parameters from PR28, merged into this candidate branch at
25
+ `e87e51f25052d0ad474efe1a928f80908cf3bcaf`. Source SHA-256 checks pin their audio
26
+ identity. These remain sound candidates: no golden promotion or general runtime clearance
27
+ is recorded by this integration. Existing candidate listening feedback is retained
28
+ separately from machine-generated evidence.
29
+
30
+ Diagnostic retains four sine voices, gain .05, fixed 1 kHz/Q=.5 low-pass and
31
+ 10 ms attack/200 ms release. Bass is mono last-held legato and Percussion mono
32
+ retriggered; both show low-register keys and a single C2 hold/trigger button.
33
+ Their controls do not promise a chord. Pad has four saw voices with a slow
34
+ attack/release, and C3/G3/C4/E4 keys plus their chord. Every selector is disabled
35
+ during loading, playback and teardown. Active synthetic changes are ignored,
36
+ without resetting held notes, parameters or the graph. Stopped sound selection
37
+ updates the labels/notes only; Start creates the selected public processor with
38
+ its exact native initial parameters. FX selection restores that FX's mix and
39
+ feedback defaults.
40
+
41
+ The stereo effects remain Diagnostic delay, Chorus and Rhythmic delay.
42
+ Diagnostic taps are 125/187.5 ms with one-second allocation. Chorus and rhythmic
43
+ use the exported GEN-621 constants: 18 ms chorus taps with .65 Hz/3 ms modulation
44
+ and feedback 0/mix .45; rhythmic uses 120 BPM dotted-eighth/quarter taps, 3.2 kHz
45
+ feedback low-pass and feedback .48/mix .35. Both allocate two seconds. Feedback
46
+ is limited to 0–.5 and wet mix to 0–1. No effect was added.
47
+
48
+ Output gain now spans 0–1, default 1. The previous diagnostic-only 2× ceiling
49
+ was reduced visibly in the UI because its .8 bound cannot be transferred to
50
+ saw/modulated sources. All raw instrument parameters and gain values remain
51
+ unchanged; there is no normalization, limiter or hidden per-sound compensation.
52
+ See [the sound/FX headroom evidence](sound-audition-headroom.md) for the distinct
53
+ bounds and measured 4×3 matrix. Both stereo channels must be finite, unscrubbed,
54
+ unclipped and within the explicit fixture bounds. The analyser display remains
55
+ a mono monitor with fixed −1 to +1 scale.
56
+
57
+ Start unlocks a fresh 48 kHz AudioContext in a real gesture. Holds may begin during
58
+ asynchronous loading and retain their latest intent. Release sends note-offs and
59
+ allows the delay tail to decay. Clear sends the existing instrument reset and a
60
+ delay-adapter reset event. Stop fades master gain for 20 ms, disposes both nodes,
61
+ and closes the context. Partial startup failure/cancellation cannot connect a
62
+ late node. Hidden-page teardown and touch/keyboard cancellation are retained.
63
+
64
+ Run `CHROMIUM_PATH=/usr/bin/chromium node --test tests/integration.test.mjs` on a
65
+ host with that browser, or omit the variable for the pinned Playwright browser.
66
+ The fixture uses the existing pack/install/build helper, a separate locked
67
+ consumer, independent analytic/direct-form references, and browser taps of the
68
+ actual connected instrument/FX outputs. It retains candidate WAV, settings,
69
+ source/package/WASM hashes, every numerical peak, browser results and failures
70
+ under `artifacts/integration`. Generated evidence is never an approved golden.
71
+ `npm run build:integration` produces a standalone integration-only local build.
72
+ The previous public UI has been removed. `npm run build:site` now clears the
73
+ public output and writes only a minimal rebuilding notice, with no old page or
74
+ audio asset retained. Historical combined pages are staged only by
75
+ `tests/site-fixture-build.mjs` in a temporary directory for automated checks.
76
+ Their locked package integrity/collision guards remain unchanged.
77
+
78
+ `tests/site-fixture.test.mjs` retains the former selected sound/FX, silent
79
+ snapshot, audition, navigation and asset assertions. The full matrix and
80
+ interrupted/repeated lifecycle checks remain in `tests/integration.test.mjs`.
81
+ `tests/site.test.mjs` separately verifies the public deletion and removed-route
82
+ 404s. No production branch/project, authentication, access or header setting
83
+ change is needed. Removal publication follows independent review and exact-head
84
+ CI; verification fixtures do not retain the former UI as a product.
85
+
86
+ The packed and realtime browser gates use Vite preview with `configFile:false`,
87
+ without COOP/COEP headers. The integration manifest records response isolation
88
+ headers, `crossOriginIsolated`, SharedArrayBuffer availability, and the actual
89
+ transport passed to each native AudioWorkletNode. Both instrument and delay are
90
+ verified using `postMessage`. `vercel.json` does not configure isolation headers;
91
+ production browser observation reported the same fallback warning. Direct public
92
+ response-header inspection from this workspace was blocked by the network proxy
93
+ (CONNECT 403), so upstream hosting/proxy headers are not independently verified.
94
+ This establishes fallback coverage, not identical phone scheduling or physical
95
+ audio behavior. No hosting/header settings are changed.
96
+
97
+ ## Verification coverage
98
+
99
+ The packed numerical matrix covers all four sounds and three FX at dry, default,
100
+ and maximum wet/feedback. Poly cases include coherent four-voice stress; Pad
101
+ runs beyond a full slow-LFO cycle. Existing independent diagnostic oscillator,
102
+ filter and delay references remain, and the new matrix compares each FX against
103
+ an independent per-sample reference using its actual input.
104
+
105
+ Packed browser checks exercise all twelve selected graphs, all 22 native initial
106
+ parameters, mono/poly note labels, stopped-only selectors, no autoplay, mobile
107
+ layout, default/dry/max-wet-feedback stereo peaks, mute, release, reset and clean
108
+ restart. Existing startup-failure, partial-load cancellation, touch cancellation,
109
+ keyboard renewal, context cleanup, and 15 ms target/20 ms Stop-fade checks remain.
110
+ Screenshots and measurement JSON are retained with candidate artifacts. Browser
111
+ CI is required; a local numerical pass is not a browser pass. Runtime remains
112
+ NOT_CLEARED and generated evidence remains CANDIDATE. Production branch/project
113
+ settings are unchanged; merging this reviewed build configuration deploys the
114
+ initial candidate through the existing Vercel integration.