@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,111 @@
1
+ # Chorus and rhythmic delay candidates
2
+
3
+ Two plain configuration/value objects use the existing `delayFx` engine. They add
4
+ no DSP, serializer, parameter registry, runtime, or UI. `chorusSettings` and
5
+ `rhythmicDelaySettings` are exported by `@denaudio/den/delay-settings`. Values map directly to existing engine inputs / AudioParams. Supply
6
+ `sampleRate` from the processor context, and spread the config to select a different
7
+ fixed capacity before construction. Two seconds is a candidate allocation, not a
8
+ product limit. Tone mode and stereo phase are construction configuration.
9
+
10
+ | Setting | Time | Motion | Feedback / tone | Linear wet mix |
11
+ | --- | --- | --- | --- | --- |
12
+ | Chorus | 18 ms both channels | 0.65 Hz, ±3 ms; opposite phase | 0 / flat | 0.45 |
13
+ | Rhythmic delay | 120 BPM; dotted eighth left (375 ms), quarter right (500 ms) | disabled | 0.48 / 3200 Hz low-pass in feedback, Q=0.5 | 0.35 |
14
+
15
+ Chorus aims for slow stereo widening with audible pitch motion and dry/wet comb
16
+ coloration. Mono sources are explicitly duplicated; averaging the stereo output
17
+ can alter that coloration. Rhythmic delay aims for interleaved repeats that darken
18
+ with each pass. The first echo is unfiltered. Neither candidate has listening
19
+ approval. Changing source or controls uses the engine's existing inputs; changing
20
+ config requires reconstruction. Settings objects are compile-time readonly data,
21
+ not runtime-frozen objects or saved state.
22
+
23
+ ## Acoustic behavior
24
+
25
+ Times and tempo jump immediately: a moving readhead can click and change pitch;
26
+ there is no crossfade or smoothing. Continuous chorus modulation deliberately
27
+ changes pitch. Rate zero holds phase; depth zero removes modulation. The 15–21 ms
28
+ chorus range fits the proposed capacity. At 30 BPM the rhythmic right tap reaches
29
+ the two-second boundary exactly. Requests beyond configured capacity or BPM
30
+ 30–300 are rejected as unity dry, not silently changed into another rhythm.
31
+ Modulation is bounded to the engine limits, with clipping reported separately.
32
+
33
+ Stopping input preserves repeats. Bypass emits unity dry, blocks new input to
34
+ history, and lets the internal tail decay; unbypass can expose it. Reset clears
35
+ history/filter/phase immediately and may click. No output normalization or limiter
36
+ is applied. Use headroom when feeding correlated dry and delayed signals.
37
+
38
+ ## Verification and reproduction
39
+
40
+ Run `npm ci`, `npm run check`, `npm test`, then
41
+ `node scripts/render-delay-settings.mjs` after building. Candidate plotting uses
42
+ Python with NumPy and Matplotlib (no package/lock change); its versions are recorded.
43
+ Generated files remain under ignored `artifacts/delay-settings/`. Run generation
44
+ from the exact reviewed commit; `sourceDirty` and hashes disclose provisional runs.
45
+
46
+ Dedicated tests cover both settings at 44.1/48/96 kHz with an unbounded written
47
+ sample timeline and independent direct-form bilinear biquad using `Math.tan`.
48
+ They verify mono duplication/opposite phase, zero-depth/same-phase collapse,
49
+ chorus quarter-cycle rate/depth with a closed-form ramp, rhythmic impulse positions
50
+ and integrated second-echo gain, tail, abrupt edits, capacity/tempo rejection,
51
+ modulation clipping, bypass/reset and silent-instance isolation. A shifted oracle
52
+ must fail. The 44.1-kHz left tap is fractional; the second repeat starts at twice
53
+ the earlier interpolation tap, not floor(twice the ideal delay).
54
+
55
+ Normal candidate renders use a 5e-6 absolute sample tolerance and exact status bits.
56
+ The 50-ms-depth stress case uses a separately calculated float-time rounding
57
+ budget: two time ULPs times sample rate, the input's maximum adjacent slope and
58
+ wet mix, divided by `(1-feedback)^2`, plus 2e-6. This accounts for the bounded sine
59
+ approximation and ideal sine straddling rounding ties; it is not a golden update.
60
+ The independent oracle rounds the 50-ms clamp constant to float32. Engine source
61
+ is unchanged. Packed settings are rendered in an isolated installed tarball at
62
+ all three offline rates. The existing engine browser gate remains 48 kHz only.
63
+
64
+ ## Listening packet
65
+
66
+ Four short candidates: each setting's base sound and an edited version. Each has
67
+ raw float32 stereo input/output WAVs and a static waveform with the same time and
68
+ amplitude axes. The WAV encoder/decoder must round-trip samples exactly. Source
69
+ stops at two seconds; the rest exposes the tail. Edited clips change rate/depth
70
+ or tempo/feedback at 1.5 s, bypass at 3 s, unbypass at 3.5 s, and reset at 4.5 s.
71
+ The manifest contains every setting, sample-indexed edit, deterministic input
72
+ formula and actual input file, source/dependency hashes, rate/length, raw audio
73
+ hashes, waveform hashes, peak values and independent numerical errors. There is
74
+ no MIDI or random source. No loudness-matched derivative is included.
75
+
76
+ These are **initial reference CANDIDATES**, with no approved old audio or golden.
77
+ Human approval must name the source/settings/audio hashes in a separate record;
78
+ any sound-affecting update requires regeneration. Code checks alone do not approve
79
+ tone, widening, repeat balance, edit clicks or bypass/unbypass feel. The shared
80
+ frontend now exposes both settings in the integrated sound/FX candidate. Existing
81
+ owner feedback is recorded in [initial acceptance](initial-acceptance.md), separately
82
+ from the generated manifests and exact-hash golden approval.
83
+
84
+ The checked-in `delay-settings-candidate.json` is the small provenance manifest
85
+ for source commit `ace4deafaf41f8c4336df7fdb532bbac10a6be42`. Audio and images are
86
+ excluded from Git. Regenerate them with the command above into `artifacts/`, the
87
+ repository's existing CI artifact-upload path. The recorded audio remains
88
+ CANDIDATE, even when the mechanical checks pass. A documentation-only commit does
89
+ not invalidate the recorded source/file hashes.
90
+
91
+ For focused review, after `npm ci && npm run build` run:
92
+
93
+ ```sh
94
+ npm run check
95
+ npx vitest run tests/delay-settings.spec.ts
96
+ node --test tests/delay-settings-packed.test.mjs
97
+ ```
98
+
99
+ The dedicated packed check installs the tarball into a separate locked consumer
100
+ and tests both settings at 44.1/48/96 kHz. Its package and verification files are
101
+ also written under `artifacts/delay-settings/`. Candidate audio generation is an
102
+ explicit local step; the current workflow does not automatically run the plotting
103
+ script. Publishing the listening packet through CI requires the integration
104
+ owner's existing artifact workflow; no preview/deployment settings are changed here.
105
+
106
+ After integrating main `91c45c5c58ea4c2cb0a30bcbdf84dc56097ed0cd`, both settings
107
+ and their numerical tests remain unchanged. The shared filter update is inherited
108
+ from main. All four regenerated input WAVs, raw output WAVs and static waveform
109
+ images match the previous candidate hashes exactly; this is reproducibility
110
+ evidence, not listening approval. The manifest records the new source commit and
111
+ filter source hash.
@@ -0,0 +1,58 @@
1
+ # unworklet compatibility findings
2
+
3
+ The current checks use registry-installed **0.6.0** packages and the checked-in
4
+ locks. The initial 0.4.1 findings are retained below as historical context; fixes
5
+ are checked against their correct behavior rather than preserving upstream bugs.
6
+ No unworklet source or package internals are patched.
7
+
8
+ | Issue | 0.4.1 observation | 0.6.0 regression check and remaining constraint |
9
+ | --- | --- | --- |
10
+ | [#78](https://github.com/yuichkun/unworklet/issues/78) | A pre-render snapshot stored gain 0 instead of initial 0.5; suspended edits stored the last rendered value. | Real Chromium snapshots retain initial 0.5 and a suspended edit to 0.25. Rendering verifies 0.5 → 0.25 → restored 0.5 at 44.1/48/96 kHz. |
11
+ | [#79](https://github.com/yuichkun/unworklet/issues/79) | Restoring f32(1) into a same-name i32 slot yielded the reinterpreted bits 1065353216. | The incompatible slot is skipped and keeps its initial i32 value 7. den promises identical-schema/rate continuation; deliberate type changes still need a reviewed migration. |
12
+ | [#96](https://github.com/yuichkun/unworklet/issues/96) | duration=896/48000 executed 8 blocks instead of 7. | Exact 896- and 256-sample durations execute 7 and 2 default quanta. |
13
+ | [#98](https://github.com/yuichkun/unworklet/issues/98) | Unity DC measured 6.021 dB. | Unity DC measures 0 dB; the helper rejects the doubled-gain expectation. Direct sample comparisons remain independent checks. |
14
+ | [#104](https://github.com/yuichkun/unworklet/issues/104) | A default config `{name:'default'}` swallowed `{name:'x'}`, producing 7 instead of 1. | **Still reproduced.** Required config plus a separate explicit instance name renders 1; den continues to use this form. |
15
+ | [#106](https://github.com/yuichkun/unworklet/issues/106) | A lang type import under TS 5.9.3 ESNext failed with Volar TS2416. | Both ES2023 and ESNext pass strict checking with `skipLibCheck:false`. The consumer keeps ES2023 without a declaration shim. |
16
+
17
+ The 0.5.0 and 0.6.0 [upstream changelog](https://github.com/yuichkun/unworklet/blob/v0.6.0/CHANGELOG.md)
18
+ describes the snapshot, duration, gain, declaration and compilation changes.
19
+ `tests/dependency.spec.ts` and the packed browser entry test independently check
20
+ the paths above. `expectAudioMatches` accepts an independently calculated
21
+ one-sample memory and rejects an intentionally incorrect answer. No audio golden
22
+ is regenerated or approved by these tests.
23
+
24
+ Reviewed but not independently reproduced here: #95 (snapshot matcher name reuse)
25
+ and #97 (integer WAV golden scale). This gate uses float WAV output and direct
26
+ sample assertions. Build/HMR reuse (#100/#101/#102), arbitrary schema migrations,
27
+ SIMD, DevTools and other APIs remain outside this fixture's coverage.
28
+
29
+ ## Browser sample rate and render quantum
30
+
31
+ The packed entry consumer renders at **44100, 48000 and 96000 Hz** through the
32
+ public `?worklet` import and `createNode`. At non-default rates, unworklet 0.6.0
33
+ compiles in its generated Web Worker for the actual AudioContext. The test checks
34
+ real output, live gain changes and snapshot restoration at each rate, and records
35
+ the actual rate and quantum in the browser evidence. Offline sample-rate coverage
36
+ is recorded separately; it is not substituted for browser evidence.
37
+
38
+ A processor compiled directly, including the playground's compiler worker, must
39
+ receive `{ sampleRate: context.sampleRate, renderQuantumSize:
40
+ context.renderQuantumSize ?? 128 }`. Its runtime metadata and schema hash must
41
+ come from that compile result. The playground browser gate separately checks
42
+ 44.1/48/96 kHz with 32/128/256-sample quanta. Fixed-length DSP buffer traversal uses
43
+ `forIndex`; FFT lengths and sample-based hop periods are not render quanta.
44
+ See the [spectral contract](spectral.md) for snapshot dispatch behavior.
45
+
46
+ These tests establish the listed contexts and operations, not arbitrary device
47
+ performance or realtime deadline clearance. Cross-rate snapshot continuation and
48
+ human sound approval remain outside this gate.
49
+
50
+ ## Tooling security
51
+
52
+ Vite remains pinned to **7.3.6** in the package and isolated consumers. The former
53
+ 7.3.1 pin was affected by
54
+ [GHSA-p9ff-h696-f583](https://github.com/vitejs/vite/security/advisories/GHSA-p9ff-h696-f583)
55
+ and other reported advisories. The gate serves a production preview on
56
+ `127.0.0.1`; it does not expose a development server to the network. Locks retain
57
+ the patched esbuild dependency. Root and consumer audit results are checked after
58
+ installation.
@@ -0,0 +1,37 @@
1
+ # Code playground deployment
2
+
3
+ The previous public UI was deleted and its production routes and asset URLs
4
+ verified absent before this replacement was started. The new root is an
5
+ English, code-first playground. It is a work-in-progress Preview until the
6
+ replacement PR's exact-head browser, review and CI gates are accepted.
7
+
8
+ The production command remains `npm run build:site`, with output `site-dist/`
9
+ and no Vercel project, access, authentication, domain or security-setting
10
+ changes. The builder packs the current repository source, installs the isolated
11
+ `playground/` consumer from its lockfile, checks its types and exhaustive public
12
+ export/example coverage, and replaces the public output with its built files.
13
+ Actual package, declaration and example hashes are emitted in `provenance.json`.
14
+
15
+ `/catalog.html`, `/audition.html`, `/diagnostics.html`, `/playground.html` and
16
+ former asset paths remain absent. No old product page is redirected or hidden
17
+ behind the editor. The only public application route is `/`, with `?module=`
18
+ selecting a bundled example without autoplay.
19
+
20
+ Library/DSP sources, exports and history are preserved. Historical browser
21
+ surfaces remain test fixtures only. `tests/site-fixture-build.mjs` builds them
22
+ outside public output, preserving all earlier numerical, native state, MIDI,
23
+ keyboard/touch and interrupted-audio assertions. The production build never
24
+ calls that fixture helper.
25
+
26
+ `tests/site-removal.test.mjs` poisons the public directory with old routes and
27
+ assets and verifies they are physically removed. `tests/site.test.mjs` tests the
28
+ actual packed replacement: Monaco assistance, editable sources, native audio,
29
+ Run/Stop/retry/cancellation, navigation/search and mobile layout. Its dedicated
30
+ Preview job runs a bounded generator/effect slice for early feedback; full
31
+ entry runs every example. Screenshots and manifests retain the exact source
32
+ and packed-package identity. Browser checks and listening approval are separate.
33
+
34
+ All development commands (`build:consumer`, `build:integration`, `build:catalog`
35
+ and the manual realtime probe) use isolated temporary output.
36
+ `buildConsumer({stageSite: true})` is rejected before packing/installing.
37
+ Only `build:site` owns public `site-dist`.
package/docs/drive.md ADDED
@@ -0,0 +1,152 @@
1
+ # Drive, fixed waveshapers, and reduction
2
+
3
+ Status: **CANDIDATE**. This implements catalog §7's fixed curves and a separate
4
+ intentional reduction effect. It is not an approved sound or a claim of matching
5
+ another product. Import `drive` and `reduction` from `@denaudio/den/drive`.
6
+
7
+ ## Contract
8
+
9
+ Both are mono `defineSubgraph` modules, instantiated with an immutable config
10
+ containing the enclosing processor's `sampleRate` in [8000, 192000] Hz. Call each
11
+ instance exactly once per audio sample. Their state is fixed, independently named,
12
+ and handled by unworklet 0.4.1's existing snapshot mechanism. No parameter,
13
+ snapshot, routing, loader, or test framework is introduced.
14
+
15
+ `drive` config additionally requires `curve: 'hard' | 'soft' | 'asymmetric' |
16
+ 'fold'`. `quality: 'direct' | 'adaa'` defaults to `'adaa'`. `dcBlockHz` defaults to
17
+ 0 (off); its finite range is [0, 200] Hz. Invalid config throws at construction.
18
+
19
+ `tick(input, gain, mix, reset)` takes three f32 nodes and a bool reset. Input is
20
+ clamped to [-8,8], gain is a **linear multiplier** in [0,32], and mix is [0,1].
21
+ There is no automatic loudness compensation. NaNs become 0; infinities saturate
22
+ to the appropriate bound. These policies also apply to the dry path. Nonzero wet
23
+ gain is not a promise that output loudness matches the input.
24
+
25
+ Reset clears histories **before processing the current sample**. Construction
26
+ also starts with zero histories. In ADAA mode, the first interval is therefore
27
+ from zero to the current driven input. Quality and curve are construction choices,
28
+ not runtime switching controls; same-schema snapshot continuation is tested.
29
+ Changing quality, curve, or DC configuration and migrating a snapshot is not
30
+ supported by this module's contract. The host must construct a fresh graph and
31
+ choose its own audible transition if changing those choices.
32
+
33
+ All audio-rate gain changes are interpreted as changes in the driven signal
34
+ `x[n] = gain[n] * input[n]`; ADAA integrates between consecutive driven values.
35
+ There is no hidden gain smoothing. Arbitrary fast modulation can still generate
36
+ sidebands/aliasing. Mix is audio-rate and immediate.
37
+
38
+ ## Transfer curves and their antiderivatives
39
+
40
+ The driven input is bounded to [-256,256]. Let `S(x)` be the clipped cubic:
41
+
42
+ - For |x| <= 1: `S(x) = 1.5*x - 0.5*x^3`
43
+ - Outside: `S(x) = sign(x)`
44
+
45
+ Its continuous first antiderivative, with F(0)=0, is `0.75*x^2 - 0.125*x^4`
46
+ inside [-1,1], and `|x| - 0.375` outside.
47
+
48
+ - `hard`: clamp(x,-1,1); primitive `x^2/2` inside and `|x|-1/2` outside.
49
+ - `soft`: S(x); unity positive endpoint, small-signal slope 1.5.
50
+ - `asymmetric`: S(x) for x >= 0, and S(2*x)/2 for x < 0. Its primitive is F(x)
51
+ on the positive side and F(2*x)/4 on the negative side. Output range [-0.5,1].
52
+ This curve can create DC from a zero-mean input; the optional blocker is useful.
53
+ - `fold`: triangular folding with period 4 and values 0,1,0,-1,0 at inputs
54
+ 0,1,2,3,4. Let t=(x+1) modulo 4 in [0,4). Output is `1-|t-2|`; its continuous,
55
+ periodic primitive is `t^2/2-t+1/2` for t <= 2 and `3*t-t^2/2-7/2` otherwise.
56
+
57
+ Direct quality samples these curves without AA. ADAA computes
58
+ `(F(x[n])-F(x[n-1]))/(x[n]-x[n-1])` using f64 arithmetic. For a difference <=1e-5,
59
+ it uses the curve at the interval midpoint, protecting even the unselected
60
+ division because unworklet selects evaluate both branches. The maximum slope
61
+ of these curves is 1.5, so the midpoint replacement error is bounded by
62
+ `1.5*|difference|/4 <= 3.75e-6`, including a knot crossing. Smooth cubic interiors
63
+ have a second-order error. This is a difference criterion, not an exclusion of
64
+ quiet audio. Histories use exact power-of-two scaling to retain recoverable f32
65
+ subnormals through unworklet's <1e-30 state-write flush.
66
+
67
+ ### AA and dry-path tradeoff
68
+
69
+ First-order ADAA is appropriate here because these are **fixed memoryless curves**
70
+ with continuous analytic primitives. It is not a generic wrapper around arbitrary
71
+ nonlinear/stateful circuits, oversampling, or a guarantee of bandlimited output.
72
+ No claim is made about an inner oversampled state/sample-rate API.
73
+
74
+ For a linear portion, ADAA is a two-sample average: half-sample group delay and
75
+ gain `cos(pi*f/sampleRate)` below Nyquist, with a zero at Nyquist. Its dry path is
76
+ the same two-sample average, preventing a dry/wet mismatch in the linear region.
77
+ It is **not transparent bypass** at mix=0. Direct quality has an undelayed dry path.
78
+ There is no integer lookahead or block buffering. Nonlinear high-frequency
79
+ behavior is not equivalent to a constant fractional delay; heavy folding can
80
+ lose substantial fundamental energy. The tests record both absolute folded
81
+ energy and fundamental amplitude, without loudness normalization hiding this.
82
+
83
+ ### Optional DC blocker
84
+
85
+ The wet path only uses `y[n]=w[n]-w[n-1]+p*y[n-1]`, where
86
+ `p=exp(-2*pi*dcBlockHz/sampleRate)`. Zero disables its state and processing.
87
+ This adds phase shift and a slightly above-unity high-frequency gain; it is not
88
+ an alias-removal filter. Mix=0 stays on the mode's dry path. Reset clears both
89
+ blocker histories. Step decay, actual digital corner response, and snapshot
90
+ continuation are tested. Sudden resets/mix changes are not click-free promises.
91
+
92
+ ## Intentional bit/sample reduction
93
+
94
+ `reduction.tick(input, bits, holdSamples, mix, reset)` clamps input to [-1,1]. Bits
95
+ are floored in [2,24], and holdSamples are floored in [1,4096]. NaN defaults are
96
+ input=0, bits=24, holdSamples=1, mix=0; infinities saturate. These are fixed-size
97
+ state controls, not allocations.
98
+
99
+ With L=2^(bits-1), quantization is nearest signed PCM with exactly 2^bits levels:
100
+ `clamp(floor(x*L+0.5),-L,L-1)/L`. Ties round toward positive infinity; the maximum
101
+ is 1-1/L, the minimum is -1, and silence is exact zero. The positive/negative
102
+ endpoint asymmetry is intentional and documented rather than a gain bug.
103
+
104
+ Capture happens on the first sample, reset sample, or when the current hold ends.
105
+ Bits and period changes take effect **at the next capture**, not mid-hold. A
106
+ period of N emits that captured value for exactly N samples. The dry path has
107
+ no delay and mix changes immediately. There is no antialias filter: quantization
108
+ and sample holding intentionally add distortion and aliasing. It is unsuitable
109
+ for transparent sample-rate conversion. Same-schema snapshots retain both held
110
+ audio and remaining count.
111
+
112
+ ## Evidence and limits
113
+
114
+ `tests/drive.spec.ts` covers 44.1/48/96 kHz with independent scalar curves and
115
+ segmented Simpson quadrature (not copied primitives), both sides of curve knots,
116
+ near-equal input, extreme drive, signed quantizer levels, hold timing/control
117
+ changes, impulses, reset boundaries, independent instances, tiny state, graph
118
+ NaN/infinity controls without output scrubbing, and exact snapshot continuation.
119
+ It also checks cubic THD/IMD coefficients, optional DC step/corner response, and
120
+ the mode's dry-path phase/gain behavior. A bypass counterexample fails the oracle.
121
+
122
+ Alias fixtures are 8192-frame coherent sines at bins 997 and 1709, amplitude 3,
123
+ after a full warmup period. Projection removes DC and only harmonics below
124
+ Nyquist. Across the four curves the observed first-order ADAA reduction in
125
+ remaining folded RMS is **7.96–9.47 dB** relative to direct quality, exceeding
126
+ the unchanged >=6 dB test requirement. Rates use the same normalized frequencies;
127
+ these are specific synthetic fixtures, not universal audible-quality guarantees.
128
+ At bin 1709, heavy folding's fundamental falls from approximately 0.5015 to 0.0939
129
+ while folded RMS falls from 0.5535 to 0.1889. Choose direct quality when that
130
+ intentional aggressive high-frequency behavior is wanted; compare candidates.
131
+
132
+ `tests/drive-packed.test.mjs` installs a tarball into a locked isolated consumer,
133
+ checks public `/drive` declarations, renders all curves/qualities and reduction,
134
+ applies independent numerical checks, and records source/audio/package hashes.
135
+ All generated audio and manifests remain **CANDIDATE**, requiring separate human
136
+ approval. The lane has no local browser/realtime performance proof.
137
+
138
+ Bounded user-editable piecewise-linear curves are now a separate
139
+ [curve-shaper candidate](curve-shaper.md). Remaining work: higher-order ADAA, verified
140
+ oversampling wrappers, configurable multistage coloration engines, performance
141
+ and listening comparison. Composing several current instances is possible but
142
+ does not establish an oversampling or multistage-quality guarantee.
143
+
144
+ Method reference: Werner & Azelborn, [Antialiasing Piecewise Polynomial
145
+ Waveshapers, DAFx 2023](https://www.dafx.de/paper-archive/2023/DAFx23_paper_61.pdf).
146
+ This implementation derives the four simple primitives above and validates them
147
+ independently; no reference implementation or vendor audio was copied.
148
+
149
+ The separate [fixed oversampled drive](oversampled-drive.md) candidate now covers
150
+ a bounded 2×/4× memoryless composition. Its filtered dry path, FIR precursors,
151
+ 32-sample bulk delay and high-treble loss are explicit; this does not add general
152
+ arbitrary-graph oversampling or higher-order ADAA to this module.
@@ -0,0 +1,131 @@
1
+ # Dual-head delay-time transitions — candidate contract
2
+
3
+ `dualHeadDelay` at `@denaudio/den/dual-head-delay` is an additive mono delay
4
+ primitive. The initial `delayReadhead`, Delay/Chorus, tempo policy and existing
5
+ sounds are unchanged. This candidate supports bounded delay-time transitions by
6
+ crossfading two fixed delay offsets. It is not a pitch shifter or time stretcher.
7
+
8
+ ## API and construction
9
+
10
+ ```ts
11
+ const delay = instantiate(dualHeadDelay, {
12
+ sampleRate: ctx.sampleRate,
13
+ maxDelaySeconds: 2,
14
+ transitionSamples: 256,
15
+ }, { name: 'transitionDelay' });
16
+ // Exactly once per sample:
17
+ const { output, transitioning, timingRejected } = delay.tick(input, timeSeconds, reset);
18
+ ```
19
+
20
+ For same-sample feedback or processing before history is written:
21
+
22
+ ```ts
23
+ const tap = delay.read(timeSeconds, reset);
24
+ tap.write(input.add(tap.output.mul(0.5)));
25
+ output.write(tap.output);
26
+ ```
27
+
28
+ Use exactly one `tick`, or one `read` followed by one call to that read's `write`,
29
+ per sample per instance. The object and closure exist at graph construction only.
30
+ No allocation occurs on the audio thread. Audio and seconds are f32 nodes; reset
31
+ and both diagnostics are boolean nodes. Input audio must be finite. No parameter,
32
+ state, routing or loader framework is introduced; use native unworklet 0.4.1 APIs.
33
+
34
+ Construction requires integer `sampleRate` in 8000..192000, matching the enclosing
35
+ processor, capacity from one sample through eight seconds (default two seconds),
36
+ and integer `transitionSamples` in 2..65536 (default 256). These are tested candidate
37
+ implementation bounds, not general product or real-time capacity claims. Allocation
38
+ is one fixed f64 history of `ceil(sampleRate * maxDelaySeconds) + 2` values, plus
39
+ scalar state. At the maximum eight seconds/192 kHz this is 1,536,002 history
40
+ values, or 12,288,016 history bytes. The measured packed four-output fixture uses
41
+ 12,320,768 bytes of native WASM memory at that capacity, unchanged across its
42
+ allocation/tiny-signal smoke. There is no resize or dynamic per-request buffer.
43
+
44
+ ## Timing and rapid requests
45
+
46
+ Times are accepted within the f32-representable endpoints
47
+ `[f32(1/sampleRate), f32(maxDelaySeconds)]`. Conversion to sample offset uses f64,
48
+ with only endpoint rounding clamped to the exact capacity. Invalid finite times,
49
+ NaN and infinities are rejected, `timingRejected=true`, and the last valid request
50
+ is retained. They do not silently replace an unavailable musical time with a
51
+ clamped rhythm. A fresh or reset instance with an invalid time starts at one sample.
52
+
53
+ The first sample, and every reset sample, captures the valid current request
54
+ immediately, without a startup fade. Later distinct accepted delays start a fade
55
+ only when the previous fade is finished. For a transition starting at sample `s`
56
+ with length `L`, the current output is
57
+
58
+ `(1 - a) * x[n - oldDelay] + a * x[n - newDelay]`,
59
+ where `a = (n - s)/(L - 1)` for `s <= n < s + L`.
60
+
61
+ The request sample is old-only; sample `s+L-1` is new-only. Both delay offsets stay
62
+ fixed for that whole fade. `transitioning` is true on all L samples, including
63
+ both endpoints. An unchanged accepted delay causes no fade. A change while fading
64
+ updates a single latest-valid-request slot; it never moves either active head,
65
+ interrupts the fade, or starts a third head. After the fade ends, the most recent
66
+ accepted request starts another fade on the next sample if it differs from the
67
+ settled target. A request back to the old delay therefore completes the outward
68
+ fade before returning. A request back to the active target removes a stale queued
69
+ request. Invalid requests leave this queue alone. This bounded coalescing policy
70
+ can skip intermediate automation values.
71
+
72
+ An idle change reaches its requested endpoint L-1 samples after its request. A
73
+ request during an existing fade may wait for that fade and a second full fade;
74
+ newer requests may supersede it. Continuous modulation has no exact tracking or
75
+ uniform control-latency guarantee. This is a control transition duration, not an
76
+ additional audio lookahead delay. Audio is always read before the current input
77
+ is written, so the minimum audio delay is one sample; there is no zero-delay dry
78
+ feedthrough.
79
+
80
+ ## Interpolation, gain, phase and tails
81
+
82
+ Each fixed head linearly interpolates its two adjacent source samples. For offset
83
+ `k+f`, this is `(1-f)*x[n-k] + f*x[n-k-1]`. The interpolation response is
84
+ `(1-f)+f*exp(-j*w)` apart from integer delay; the half-sample case reaches zero at
85
+ Nyquist. The transition is a linear amplitude crossfade rather than equal power.
86
+ All four source weights are nonnegative and sum to one, so finite input extrema
87
+ are not exceeded by this primitive. Correlated in-phase heads retain unity gain;
88
+ uncorrelated heads dip in energy at the midpoint, and opposite-phase heads can
89
+ cancel. The two times can create comb filtering, transient smearing, duplicated
90
+ or skipped content, and modulation sidebands. A sine maintains its original
91
+ frequency at each fixed head, but the overlapping signal need not maintain its
92
+ amplitude or phase during a transition. No arbitrary-modulation Doppler-free,
93
+ antialiasing, click-free, seamless or ideal pitch-preservation claim is made.
94
+
95
+ There is no internal feedback, dry/wet, bypass, limiter or gain compensation.
96
+ Callers compose those explicitly. An undelayed dry mix combs against the wet delay;
97
+ both branches have no implicit alignment. Convex dry/wet preserves the primitive's
98
+ input peak bound. Feedback changes that bound: for a constant magnitude feedback
99
+ below one, the history can be bounded by inputPeak/(1-abs(feedback)); external
100
+ filtering, gain, clipping and modulation are the caller's responsibility. The
101
+ read-before-write API adds no extra circulation sample beyond the selected delay.
102
+
103
+ Stopping input lets stored source samples finish. Without feedback, no stored
104
+ contribution survives beyond `ceil(sampleRate*maxDelaySeconds)` samples after the
105
+ last nonzero input, even through requests. A transition can expose earlier history
106
+ or spread an impulse across both heads, so its tail is not a single echo. External
107
+ feedback extends the tail. Reset cancels transitions and logically invalidates all
108
+ prior history before the read; the current input is then the first new sample.
109
+ Held reset remains silent. Reset can click and is not a bypass or memory-erasure API.
110
+
111
+ ## Numerical state and verification boundary
112
+
113
+ History is multiplied by exact `2**128` before f64 storage and divided after the
114
+ weighted read. This avoids native buffer scrubbing of tiny valid f32 samples and
115
+ keeps full-range finite f32 weighted sums inside f64. It does not recover a result
116
+ below f32's representable range. Stable named state records history, cursor, valid
117
+ count, initialized state, current/target/queued delays and remaining fade samples.
118
+ Snapshot continuation requires the same schema, sample rate, capacity and fade
119
+ length. Logical reset does not physically wipe old snapshot bytes.
120
+
121
+ The dedicated tests compare native rendering with an independent unbounded source
122
+ timeline and absolute-time transition schedule, plus analytic impulse, sinusoidal,
123
+ phase/cancellation and feedback assertions. They cover rapid changes, invalid
124
+ controls, minimum/maximum and fractional wraps, reset, instance isolation,
125
+ full-range and tiny finite audio, and in-flight snapshot continuation at 44.1/48/96
126
+ kHz. The isolated packed consumer uses the public subpath, strict declarations,
127
+ native parameter edits and actual rendering. Audio remains CANDIDATE, not an
128
+ approved golden. Local cost reports are diagnostic only: an initial 96 kHz run observed a
129
+ 4.655 ms warm quantum against a 1.333 ms nominal budget. Host jitter and cold
130
+ execution are not hidden by median timings. No browser, hardware
131
+ real-time deadline, general pitch/time processing or listening approval follows.