@denaudio/den 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/LICENSE-APACHE +202 -0
- package/LICENSE-MIT +21 -0
- package/README.md +102 -2
- package/dist/biquad-eq.d.ts +11 -0
- package/dist/biquad-eq.js +35 -0
- package/dist/catalog-filter-math.d.ts +4 -0
- package/dist/catalog-filter-math.js +30 -0
- package/dist/character-filter.d.ts +13 -0
- package/dist/character-filter.js +53 -0
- package/dist/convolution-spectrum.d.ts +25 -0
- package/dist/convolution-spectrum.js +82 -0
- package/dist/convolution.d.ts +15 -0
- package/dist/convolution.js +81 -0
- package/dist/crossover.d.ts +11 -0
- package/dist/crossover.js +17 -0
- package/dist/curve-shaper.d.ts +26 -0
- package/dist/curve-shaper.js +73 -0
- package/dist/delay-fx.d.ts +37 -0
- package/dist/delay-fx.js +84 -0
- package/dist/delay-readhead.d.ts +19 -0
- package/dist/delay-readhead.js +54 -0
- package/dist/delay-settings.d.ts +55 -0
- package/dist/delay-settings.js +20 -0
- package/dist/drive.d.ts +24 -0
- package/dist/drive.js +116 -0
- package/dist/dual-head-delay.d.ts +28 -0
- package/dist/dual-head-delay.js +77 -0
- package/dist/dynamics.d.ts +61 -0
- package/dist/dynamics.js +130 -0
- package/dist/envelope.d.ts +34 -0
- package/dist/envelope.js +62 -0
- package/dist/filter.d.ts +9 -0
- package/dist/filter.js +34 -0
- package/dist/filterbank-vocoder.d.ts +35 -0
- package/dist/filterbank-vocoder.js +52 -0
- package/dist/formant-bank.d.ts +15 -0
- package/dist/formant-bank.js +28 -0
- package/dist/freeze-reverb.d.ts +35 -0
- package/dist/freeze-reverb.js +61 -0
- package/dist/frequency-shifter.d.ts +24 -0
- package/dist/frequency-shifter.js +57 -0
- package/dist/gate.d.ts +2 -0
- package/dist/gate.js +12 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/instrument-example.d.ts +12 -0
- package/dist/instrument-example.js +29 -0
- package/dist/instrument-settings.d.ts +95 -0
- package/dist/instrument-settings.js +36 -0
- package/dist/instrument.d.ts +42 -0
- package/dist/instrument.js +166 -0
- package/dist/lfo.d.ts +19 -0
- package/dist/lfo.js +55 -0
- package/dist/live-buffer.d.ts +39 -0
- package/dist/live-buffer.js +52 -0
- package/dist/live-granular.d.ts +41 -0
- package/dist/live-granular.js +64 -0
- package/dist/lookahead-limiter.d.ts +31 -0
- package/dist/lookahead-limiter.js +67 -0
- package/dist/loop-crossfade.d.ts +28 -0
- package/dist/loop-crossfade.js +72 -0
- package/dist/modulation-fx.d.ts +47 -0
- package/dist/modulation-fx.js +58 -0
- package/dist/modulation.d.ts +132 -0
- package/dist/modulation.js +228 -0
- package/dist/mpe-expression.d.ts +36 -0
- package/dist/mpe-expression.js +73 -0
- package/dist/multiband-dynamics.d.ts +50 -0
- package/dist/multiband-dynamics.js +60 -0
- package/dist/musical-controls.d.ts +76 -0
- package/dist/musical-controls.js +125 -0
- package/dist/musical-examples.d.ts +21 -0
- package/dist/musical-examples.js +142 -0
- package/dist/musical-materials.d.ts +110 -0
- package/dist/musical-materials.js +56 -0
- package/dist/oscillator.d.ts +10 -0
- package/dist/oscillator.js +35 -0
- package/dist/oversampled-drive.d.ts +19 -0
- package/dist/oversampled-drive.js +99 -0
- package/dist/performance.d.ts +61 -0
- package/dist/performance.js +241 -0
- package/dist/pitch-quantizer.d.ts +26 -0
- package/dist/pitch-quantizer.js +88 -0
- package/dist/prepared-convolution.d.ts +22 -0
- package/dist/prepared-convolution.js +99 -0
- package/dist/resident-recorder.d.ts +28 -0
- package/dist/resident-recorder.js +68 -0
- package/dist/resident-time-stretch.d.ts +44 -0
- package/dist/resident-time-stretch.js +135 -0
- package/dist/resonator.d.ts +32 -0
- package/dist/resonator.js +63 -0
- package/dist/reverb.d.ts +27 -0
- package/dist/reverb.js +52 -0
- package/dist/sample.d.ts +113 -0
- package/dist/sample.js +212 -0
- package/dist/source.d.ts +58 -0
- package/dist/source.js +113 -0
- package/dist/spatial-chains.d.ts +48 -0
- package/dist/spatial-chains.js +76 -0
- package/dist/spectral-fft.d.ts +21 -0
- package/dist/spectral-fft.js +74 -0
- package/dist/spectral-freeze.d.ts +22 -0
- package/dist/spectral-freeze.js +74 -0
- package/dist/spectral-gate.d.ts +21 -0
- package/dist/spectral-gate.js +58 -0
- package/dist/spectral-stft.d.ts +13 -0
- package/dist/spectral-stft.js +69 -0
- package/dist/spectral-texture.d.ts +42 -0
- package/dist/spectral-texture.js +141 -0
- package/dist/spectral.d.ts +16 -0
- package/dist/spectral.js +10 -0
- package/dist/state-variable-filter.d.ts +17 -0
- package/dist/state-variable-filter.js +25 -0
- package/dist/stereo-delay.d.ts +45 -0
- package/dist/stereo-delay.js +70 -0
- package/dist/virtual-analog.d.ts +26 -0
- package/dist/virtual-analog.js +66 -0
- package/dist/voice-policy.d.ts +37 -0
- package/dist/voice-policy.js +245 -0
- package/dist/wavetable-bands.d.ts +37 -0
- package/dist/wavetable-bands.js +145 -0
- package/dist/wavetable-read.d.ts +6 -0
- package/dist/wavetable-read.js +11 -0
- package/dist/wavetable.d.ts +29 -0
- package/dist/wavetable.js +49 -0
- package/dist/windowed-pitch-shift.d.ts +34 -0
- package/dist/windowed-pitch-shift.js +51 -0
- package/docs/catalog-audition.md +76 -0
- package/docs/catalog-filters.md +125 -0
- package/docs/catalog-modulation-fx.md +220 -0
- package/docs/catalog-status.md +431 -0
- package/docs/character-filter.md +139 -0
- package/docs/contracts.md +131 -0
- package/docs/control-modulation.md +194 -0
- package/docs/convolution.md +45 -0
- package/docs/curve-shaper.md +117 -0
- package/docs/delay-fx-capture-findings.json +134 -0
- package/docs/delay-fx.md +202 -0
- package/docs/delay-readhead.md +121 -0
- package/docs/delay-settings-candidate.json +333 -0
- package/docs/delay-settings.md +111 -0
- package/docs/dependency-findings.md +61 -0
- package/docs/deployment-status.md +37 -0
- package/docs/drive.md +152 -0
- package/docs/dual-head-delay.md +131 -0
- package/docs/dynamics.md +180 -0
- package/docs/envelope-integer-staging.md +64 -0
- package/docs/filter-materialization-results.json +71 -0
- package/docs/filter-materialization.md +135 -0
- package/docs/filter-oscillator.md +126 -0
- package/docs/filterbank-vocoder-entry.md +58 -0
- package/docs/freeze-reverb.md +179 -0
- package/docs/frequency-shifter.md +133 -0
- package/docs/frontend-audition.md +22 -0
- package/docs/gain-schedule-verification.md +53 -0
- package/docs/initial-acceptance.md +79 -0
- package/docs/instrument-performance.md +191 -0
- package/docs/instrument-settings.md +130 -0
- package/docs/instrument.md +204 -0
- package/docs/integration-candidate.md +114 -0
- package/docs/live-buffer.md +163 -0
- package/docs/live-granular.md +179 -0
- package/docs/lookahead-multiband.md +183 -0
- package/docs/loop-crossfade.md +99 -0
- package/docs/mpe-expression.md +65 -0
- package/docs/musical-clock-seek-boundary.md +31 -0
- package/docs/musical-controls-contract.md +144 -0
- package/docs/musical-examples.md +255 -0
- package/docs/oversampled-drive.md +153 -0
- package/docs/performance.md +53 -0
- package/docs/pitch-quantizer.md +55 -0
- package/docs/prepared-convolution.md +188 -0
- package/docs/realtime-audition.md +63 -0
- package/docs/resident-recorder.md +151 -0
- package/docs/resident-time-stretch.md +190 -0
- package/docs/sample.md +191 -0
- package/docs/sound-audition-headroom.md +103 -0
- package/docs/sources.md +81 -0
- package/docs/spatial-chains-entry.md +250 -0
- package/docs/spectral-freeze-entry.md +130 -0
- package/docs/spectral-gate-entry.md +130 -0
- package/docs/spectral-large-candidate.md +74 -0
- package/docs/spectral-texture-entry.md +174 -0
- package/docs/spectral.md +118 -0
- package/docs/virtual-analog.md +77 -0
- package/docs/voice-policy.md +38 -0
- package/docs/wavetable-bands.md +138 -0
- package/docs/wavetable.md +101 -0
- package/docs/windowed-pitch-shift.md +112 -0
- 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,61 @@
|
|
|
1
|
+
# unworklet 0.4.1 findings
|
|
2
|
+
|
|
3
|
+
The upstream reports #68–106 were read as reports, not assumed verified. These
|
|
4
|
+
checks use registry-installed 0.4.1 packages and the checked-in dependency locks.
|
|
5
|
+
No unworklet repository or dependency source was modified.
|
|
6
|
+
|
|
7
|
+
| Issue | Independently observed here | Smallest den-local constraint |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| [#78](https://github.com/yuichkun/unworklet/issues/78) | Real Chromium: gain initialized 0.5 but pre-render snapshot stores 0; editing to 0.25 while suspended still stores last-rendered 0.5. | Save after stable parameter values have rendered. Browser test verifies 0.5 → 0.25 → restored 0.5 in actual output. Suspended preset editing remains unsupported. |
|
|
10
|
+
| [#79](https://github.com/yuichkun/unworklet/issues/79) | Restoring f32(1) into same-name i32 state yields 1065353216. | Gate accepts identical schema/rate only. Do not expose incompatible saved presets without reviewed migration tests. No den serializer added. |
|
|
11
|
+
| [#96](https://github.com/yuichkun/unworklet/issues/96) | duration=896/48000 executes 8 blocks instead of 7. | Fixed gate durations 128/sampleRate and 256/sampleRate are verified by lengths/state; do not infer arbitrary durations are safe. |
|
|
12
|
+
| [#98](https://github.com/yuichkun/unworklet/issues/98) | Unity DC fails the frequency-gain helper with 6.021 dB. | Direct independent sample comparisons; no FFT is needed for this gate. |
|
|
13
|
+
| [#104](https://github.com/yuichkun/unworklet/issues/104) | Default config `{name:'default'}` swallows `{name:'x'}`, output 7 rather than 1. | Required config parameter plus explicit separate instance name renders 1. Used by gate and consumer. |
|
|
14
|
+
| [#106](https://github.com/yuichkun/unworklet/issues/106) | Pure lang type import under TS 5.9.3 ESNext fails with Volar TS2416; ES2023 has zero diagnostics. | ES2023, strict=true, skipLibCheck=false. No type shim or blanket declaration skipping. |
|
|
15
|
+
|
|
16
|
+
The pinned dependency reproduction assertions intentionally capture known wrong
|
|
17
|
+
behavior. On dependency upgrade, investigate changes and replace them with correct
|
|
18
|
+
regression expectations; do not preserve upstream bugs as product requirements.
|
|
19
|
+
|
|
20
|
+
`expectAudioMatches` is exercised against an independently calculated one-sample
|
|
21
|
+
memory and against an intentionally incorrect answer. The helper passes the former
|
|
22
|
+
and rejects the latter. No audio snapshot matcher or golden update is used.
|
|
23
|
+
|
|
24
|
+
Reviewed but **not reproduced** here: #95 (snapshot matcher name reuse), #97
|
|
25
|
+
(integer WAV golden scale). The gate uses no approved golden; its WAVs use the
|
|
26
|
+
existing float encoder, and numerical assertions inspect raw samples. Before future
|
|
27
|
+
golden adoption, test these helper paths rather than assuming approval machinery.
|
|
28
|
+
#100/#101/#102 concern build/HMR reuse; the gate uses a new build process and a new
|
|
29
|
+
AudioContext, and does not claim HMR/repeated in-process build correctness.
|
|
30
|
+
`.uwk` lowering, SIMD, MIDI dispatch, DevTools, arbitrary schema migration, and the
|
|
31
|
+
other reported APIs are outside this fixture; their reports are not cleared by
|
|
32
|
+
this PR. Later lanes must inspect and reproduce the bugs relevant to their APIs.
|
|
33
|
+
|
|
34
|
+
## Browser sample-rate scope (known, non-blocking)
|
|
35
|
+
|
|
36
|
+
The supported entry consumer creates a **48000 Hz** AudioContext. Offline render
|
|
37
|
+
coverage at 44100/48000/96000 is separate and does not prove browser support at
|
|
38
|
+
those rates. The manifest names `offlineSampleRates`, `browserRenderedSampleRates`,
|
|
39
|
+
and per-context `browserCoverage` explicitly.
|
|
40
|
+
|
|
41
|
+
Real Chromium contexts at 44100 and 96000 are also probed: `createNode` refuses the
|
|
42
|
+
plugin's 48000-Hz artifact, reporting the compiled and actual rates. This is the
|
|
43
|
+
intentional safety guard associated with upstream
|
|
44
|
+
[#22](https://github.com/yuichkun/unworklet/issues/22), not a den DSP failure. In
|
|
45
|
+
published 0.4.1, unplugin calls `compile(processor)` without options and its public
|
|
46
|
+
options expose only analysis-artifact and cross-origin-isolation flags. Core
|
|
47
|
+
`compile` supports a rate for offline/custom bundling, but `createNode` does not
|
|
48
|
+
recompile at context rate; `lang/browser.compileSource(source)` likewise has no
|
|
49
|
+
rate argument in 0.4.1. No metadata bypass, custom runtime/loader, or upstream edit
|
|
50
|
+
is introduced. The rejection probes document this limitation; non-48-kHz browser
|
|
51
|
+
support is not an acceptance requirement and does not block den's next lanes.
|
|
52
|
+
|
|
53
|
+
## Tooling security
|
|
54
|
+
|
|
55
|
+
Vite is pinned to **7.3.6** in both the package and isolated consumer. The earlier
|
|
56
|
+
7.3.1 pin was affected by
|
|
57
|
+
[GHSA-p9ff-h696-f583](https://github.com/vitejs/vite/security/advisories/GHSA-p9ff-h696-f583)
|
|
58
|
+
and other advisories returned by `npm audit`. The gate only serves a production
|
|
59
|
+
preview on `127.0.0.1`, not a network-exposed development server; nevertheless the
|
|
60
|
+
affected tooling has been replaced. Both lockfiles also resolve the patched
|
|
61
|
+
esbuild dependency. Root and consumer audit results are checked after 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.
|