@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,15 @@
1
+ import { type Node } from '@unworklet/core';
2
+ export interface FormantBand {
3
+ frequencyHz: number;
4
+ q: number;
5
+ gain: number;
6
+ }
7
+ export interface FormantBankConfig {
8
+ sampleRate: number;
9
+ bands: readonly FormantBand[];
10
+ }
11
+ /** Fixed 1–8 parallel, unity-peak bandpasses. A resonator bank, not a speech synthesizer. */
12
+ export declare const formantBank: import("@unworklet/core").SubgraphDecl<[config: FormantBankConfig], {
13
+ /** Linear frequency ratio [.25,4] and Q scale [.25,4]; per-band cutoff/Q limits still apply. */
14
+ tick(input: Node<"f32">, frequencyRatio: Node<"f32">, resonanceScale: Node<"f32">, reset: Node<"bool">): Node<"f32">;
15
+ }>;
@@ -0,0 +1,28 @@
1
+ import { defineSubgraph, f32, instantiate } from '@unworklet/core';
2
+ import { stateVariableFilter } from './state-variable-filter.js';
3
+ import { checkedSampleRate } from './catalog-filter-math.js';
4
+ /** Fixed 1–8 parallel, unity-peak bandpasses. A resonator bank, not a speech synthesizer. */
5
+ export const formantBank = defineSubgraph((config) => {
6
+ const maximum = checkedSampleRate(config.sampleRate);
7
+ if (!Array.isArray(config.bands) || config.bands.length < 1 || config.bands.length > 8)
8
+ throw new RangeError('formant bank requires 1–8 fixed bands');
9
+ const bands = config.bands.map((band, index) => {
10
+ if (!Number.isFinite(band.frequencyHz) || band.frequencyHz < 20 || band.frequencyHz > maximum ||
11
+ !Number.isFinite(band.q) || band.q < .5 || band.q > 10 || !Number.isFinite(band.gain) || Math.abs(band.gain) > 1) {
12
+ throw new RangeError('formant band frequency/Q/gain is outside its supported bounds');
13
+ }
14
+ return { frequencyHz: band.frequencyHz, q: band.q, gain: band.gain,
15
+ filter: instantiate(stateVariableFilter, { sampleRate: config.sampleRate }, { name: `band${index}` }) };
16
+ });
17
+ return {
18
+ /** Linear frequency ratio [.25,4] and Q scale [.25,4]; per-band cutoff/Q limits still apply. */
19
+ tick(input, frequencyRatio, resonanceScale, reset) {
20
+ let output = f32(0);
21
+ for (const band of bands) {
22
+ const response = band.filter.tick(input, frequencyRatio.clamp(.25, 4).mul(band.frequencyHz), resonanceScale.clamp(.25, 4).mul(band.q), reset);
23
+ output = output.add(response.bandpass.mul(band.gain));
24
+ }
25
+ return output;
26
+ },
27
+ };
28
+ });
@@ -0,0 +1,35 @@
1
+ import { type Node } from '@unworklet/core';
2
+ export interface FreezeReverbConfig {
3
+ /** Match ctx.sampleRate; integer 8000..192000. */
4
+ sampleRate: number;
5
+ /** Fixed delay-length scale, [0.5,2], default 1. Rebuild to change. */
6
+ roomScale?: number;
7
+ /** Fixed nominal loop-loss time, [0.1,10] seconds, default 1.5.
8
+ * Describes the unfrozen network, not a frozen-tail duration. */
9
+ decaySeconds?: number;
10
+ /** Samples from fully live to fully frozen, or back; integer 0..192000.
11
+ * Default 256. Zero switches immediately and may click. Reversals continue
12
+ * from the current integer progress, without restarting the ramp. */
13
+ transitionSamples?: number;
14
+ }
15
+ export interface FreezeReverbControls {
16
+ /** Level control: ramp to muted excitation + unity feedback while true. */
17
+ freeze: Node<'bool'>;
18
+ /** Highest priority: output silence, discard current excitation, invalidate
19
+ * all delay history and set progress to live. Held reset keeps doing this. */
20
+ reset: Node<'bool'>;
21
+ }
22
+ /** Wet-only four-line integer-delay FDN. Call tick exactly once per sample.
23
+ * The fully frozen, unforced real-arithmetic network preserves stored energy.
24
+ * Actual f64 state has rounding and very-small-state limits, not perpetual
25
+ * exact energy. Finite normalized inputs and adequate output headroom required.
26
+ * No interpolation, damping filter, shimmer, limiter, dry path or normalization.
27
+ */
28
+ export declare const freezeReverb: import("@unworklet/core").SubgraphDecl<[config: FreezeReverbConfig], {
29
+ tick(inputLeft: Node<"f32">, inputRight: Node<"f32">, c: FreezeReverbControls): {
30
+ left: Node<"f32">;
31
+ right: Node<"f32">;
32
+ freezeAmount: Node<"f32">;
33
+ frozen: Node<"bool">;
34
+ };
35
+ }>;
@@ -0,0 +1,61 @@
1
+ import { defineSubgraph, f32, f64, i32, select, state } from '@unworklet/core';
2
+ // Exact binary representation scaling, not audio normalization. Keeps the
3
+ // smallest f32 excitations above the native tiny-state scrub threshold.
4
+ const SCALE = 2 ** 256;
5
+ const ROWS = [[1, 1, 1, 1], [1, -1, 1, -1], [1, 1, -1, -1], [1, -1, -1, 1]];
6
+ /** Wet-only four-line integer-delay FDN. Call tick exactly once per sample.
7
+ * The fully frozen, unforced real-arithmetic network preserves stored energy.
8
+ * Actual f64 state has rounding and very-small-state limits, not perpetual
9
+ * exact energy. Finite normalized inputs and adequate output headroom required.
10
+ * No interpolation, damping filter, shimmer, limiter, dry path or normalization.
11
+ */
12
+ export const freezeReverb = defineSubgraph((config) => {
13
+ const { sampleRate, roomScale = 1, decaySeconds = 1.5, transitionSamples = 256 } = config;
14
+ if (!Number.isInteger(sampleRate) || sampleRate < 8000 || sampleRate > 192000 ||
15
+ !Number.isFinite(roomScale) || roomScale < 0.5 || roomScale > 2 ||
16
+ !Number.isFinite(decaySeconds) || decaySeconds < 0.1 || decaySeconds > 10 ||
17
+ !Number.isInteger(transitionSamples) || transitionSamples < 0 || transitionSamples > 192000) {
18
+ throw new RangeError('freezeReverb requires integer sampleRate [8000,192000], roomScale [0.5,2], decaySeconds [0.1,10], integer transitionSamples [0,192000]');
19
+ }
20
+ const steps = Math.max(1, transitionSamples);
21
+ const progress = state.i32(0).named('freezeProgress');
22
+ const lines = [0.0297, 0.0371, 0.0411, 0.0437].map((seconds, index) => {
23
+ const size = Math.round(seconds * roomScale * sampleRate);
24
+ return {
25
+ size,
26
+ gain: 10 ** (-3 * size / (sampleRate * decaySeconds)),
27
+ history: state.buffer.f64({ size }).expose({ name: `line${index}/historyScaled`, snapshot: 'persistent' }),
28
+ cursor: state.i32(0).named(`line${index}/cursor`),
29
+ valid: state.i32(0).named(`line${index}/valid`),
30
+ wet: state.f64(0).expose({ name: `line${index}/wetScaled`, snapshot: 'transient' }),
31
+ };
32
+ });
33
+ return {
34
+ tick(inputLeft, inputRight, c) {
35
+ const next = transitionSamples === 0
36
+ ? select(c.freeze, i32(1), i32(0))
37
+ : select(c.freeze, select(progress.read().lt(steps), progress.read().add(1), i32(steps)), select(progress.read().gt(0), progress.read().sub(1), i32(0)));
38
+ progress.write(select(c.reset, i32(0), next));
39
+ const amount = f64(progress.read()).div(steps);
40
+ const excitation = f64(1).sub(amount);
41
+ // Materialize every outgoing sample before any buffer write. Integer
42
+ // cursors avoid the fractional seconds/interpolation of delayReadhead.
43
+ for (const line of lines) {
44
+ line.wet.write(select(c.reset.or(line.valid.read().lt(line.size)), f64(0), line.history.read(line.cursor.read())));
45
+ }
46
+ const returns = lines.map(line => line.wet.read().mul(amount.add(excitation.mul(line.gain))));
47
+ const left = select(c.reset, f64(0), f64(inputLeft).mul(SCALE).mul(excitation));
48
+ const right = select(c.reset, f64(0), f64(inputRight).mul(SCALE).mul(excitation));
49
+ for (let row = 0; row < lines.length; row++) {
50
+ const line = lines[row];
51
+ const feedback = returns.reduce((sum, value, column) => sum.add(value.mul(ROWS[row][column] * 0.5)), f64(0));
52
+ const injection = left.add(right.mul(ROWS[1][row])).mul(0.5);
53
+ line.history.write(select(c.reset, i32(0), line.cursor.read()), injection.add(feedback));
54
+ line.cursor.write(select(c.reset, i32(0), select(line.cursor.read().eq(line.size - 1), i32(0), line.cursor.read().add(1))));
55
+ line.valid.write(select(c.reset, i32(0), select(line.valid.read().lt(line.size), line.valid.read().add(1), i32(line.size))));
56
+ }
57
+ const output = (row) => f32(lines.reduce((sum, line, column) => sum.add(line.wet.read().mul(ROWS[row][column] * 0.5)), f64(0)).div(SCALE));
58
+ return { left: output(2), right: output(3), freezeAmount: f32(amount), frozen: progress.read().eq(steps) };
59
+ },
60
+ };
61
+ });
@@ -0,0 +1,24 @@
1
+ import { type Node } from '@unworklet/core';
2
+ /** Matched real/Hilbert group delay; the FIR transient spans 63 samples. */
3
+ export declare const FREQUENCY_SHIFTER_LATENCY_SAMPLES = 31;
4
+ export interface FrequencyShifterConfig {
5
+ sampleRate: number;
6
+ }
7
+ export interface FrequencyShifterControls {
8
+ /** Signed Hz, clamped to [-sampleRate/4, sampleRate/4]. Zero holds phase. */
9
+ shiftHz: Node<'f32'>;
10
+ /** Linear, latency-aligned dry/wet blend, clamped to [0,1]. */
11
+ mix: Node<'f32'>;
12
+ /** Select delayed dry; history and oscillator continue running. */
13
+ bypass: Node<'bool'>;
14
+ /** Level reset: silence/discard this sample, clear history and phase. */
15
+ reset: Node<'bool'>;
16
+ }
17
+ /** Fixed 63-tap Blackman Hilbert frequency shifter. Call once per sample.
18
+ * Finite controls and normalized finite input [-1,1] required. Output may exceed
19
+ * unity. Approximate single-sideband behavior only in the documented useful
20
+ * band; there is no input bandpass, antialiasing, pitch-ratio or time stretching.
21
+ */
22
+ export declare const frequencyShifter: import("@unworklet/core").SubgraphDecl<[config: FrequencyShifterConfig], {
23
+ tick(input: Node<"f32">, c: FrequencyShifterControls): Node<"f32">;
24
+ }>;
@@ -0,0 +1,57 @@
1
+ import { defineSubgraph, f32, f64, i32, select, state } from '@unworklet/core';
2
+ /** Matched real/Hilbert group delay; the FIR transient spans 63 samples. */
3
+ export const FREQUENCY_SHIFTER_LATENCY_SAMPLES = 31;
4
+ // Full-cycle degree-15 sine with f64 arithmetic and a <7e-12 remainder.
5
+ function sine(cycles) {
6
+ const p = cycles.sub(cycles.add(0.5).floor());
7
+ const folded = select(p.gt(0.25), f64(0.5).sub(p), select(p.lt(-0.25), f64(-0.5).sub(p), p));
8
+ const angle = folded.mul(2 * Math.PI), square = angle.mul(angle);
9
+ return angle.mul(f64(-1 / 1307674368000).mul(square).add(1 / 6227020800).mul(square).sub(1 / 39916800)
10
+ .mul(square).add(1 / 362880).mul(square).sub(1 / 5040)
11
+ .mul(square).add(1 / 120).mul(square).sub(1 / 6).mul(square).add(1));
12
+ }
13
+ /** Fixed 63-tap Blackman Hilbert frequency shifter. Call once per sample.
14
+ * Finite controls and normalized finite input [-1,1] required. Output may exceed
15
+ * unity. Approximate single-sideband behavior only in the documented useful
16
+ * band; there is no input bandpass, antialiasing, pitch-ratio or time stretching.
17
+ */
18
+ export const frequencyShifter = defineSubgraph((config) => {
19
+ const { sampleRate } = config;
20
+ if (!Number.isInteger(sampleRate) || sampleRate < 8000 || sampleRate > 192000) {
21
+ throw new RangeError('frequencyShifter requires an integer sampleRate in [8000,192000]');
22
+ }
23
+ const delay = FREQUENCY_SHIFTER_LATENCY_SAMPLES, size = 63;
24
+ // Public 0.4.1 scrubs buffer writes below 1e-30. Exact power-of-two scaling
25
+ // preserves the full finite normalized f32 input range, including subnormals.
26
+ const scale = 2 ** 128;
27
+ const history = state.buffer.f64({ size }).expose({ name: 'historyScaled', snapshot: 'persistent' });
28
+ const cursor = state.i32(0).named('cursor');
29
+ const valid = state.i32(0).named('valid');
30
+ // Centered scaled phase also preserves tiny signed increments near zero.
31
+ // As with any f64 accumulator, increments below its current ULP can stall.
32
+ const phase = state.f64(0).named('phaseScaled');
33
+ const quadrature = state.f64(0).expose({ name: 'quadratureScaled', snapshot: 'transient' });
34
+ return {
35
+ tick(input, c) {
36
+ valid.write(select(c.reset, i32(0), valid.read().add(1).min(size)));
37
+ history.write(cursor.read(), f64(select(c.reset, f32(0), input)).mul(scale));
38
+ const at = (lag) => select(valid.read().gt(lag), history.read(cursor.read().add(size - lag).mod(size)), f64(0));
39
+ quadrature.write(0);
40
+ // Odd antisymmetric taps. The Blackman endpoints are exactly zero;
41
+ // pair differences reduce the 63-tap FIR to 15 bounded multiply-adds.
42
+ for (let offset = 1; offset < delay; offset += 2) {
43
+ const window = 0.42 + 0.5 * Math.cos(Math.PI * offset / delay) + 0.08 * Math.cos(2 * Math.PI * offset / delay);
44
+ quadrature.write(quadrature.read().add(at(delay + offset).sub(at(delay - offset)).mul(2 * window / (Math.PI * offset))));
45
+ }
46
+ const dry = at(delay), p = select(c.reset, f64(0), phase.read().div(scale));
47
+ const wet = dry.mul(sine(p.add(0.25))).sub(quadrature.read().mul(sine(p)));
48
+ const mix = f64(c.mix).clamp(0, 1);
49
+ const output = select(c.bypass, dry, dry.mul(f64(1).sub(mix)).add(wet.mul(mix)));
50
+ const next = p.add(f64(c.shiftHz).clamp(-sampleRate / 4, sampleRate / 4).div(sampleRate));
51
+ // A held reset holds phase zero; the first subsequent sample uses zero.
52
+ phase.write(select(c.reset, f64(0), next.sub(next.add(0.5).floor()).mul(scale)));
53
+ cursor.write(cursor.read().add(1).mod(size));
54
+ return select(c.reset, f32(0), f32(output.div(scale)));
55
+ },
56
+ };
57
+ });
package/dist/gate.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ /** One-sample memory with gain: only an entry-path verification fixture. */
2
+ export declare const gate: import("@unworklet/core").CompiledProcessor<unknown>;
package/dist/gate.js ADDED
@@ -0,0 +1,12 @@
1
+ import { audioInput, audioOutput, defineProcessor, forSample, instantiate, param } from '@unworklet/core';
2
+ import { gateCell } from './index.js';
3
+ /** One-sample memory with gain: only an entry-path verification fixture. */
4
+ export const gate = defineProcessor(() => {
5
+ const input = audioInput({ channels: 1, name: 'main' });
6
+ const output = audioOutput({ channels: 1, name: 'main' });
7
+ const gain = param.f32({ default: 0.5, min: 0, max: 1, automationRate: 'a-rate' }).named('gain');
8
+ const cell = instantiate(gateCell, { scale: 1 }, { name: 'cell' });
9
+ return { process() {
10
+ forSample(i => output.ch(0).at(i).write(cell.tick(input.ch(0).at(i), gain.at(i))));
11
+ } };
12
+ }, { id: 'den.entry.gate.v1' });
@@ -0,0 +1,7 @@
1
+ import { type Node } from '@unworklet/core';
2
+ /** Minimal composition fixture, not a production DSP module. Required config avoids upstream #104. */
3
+ export declare const gateCell: import("@unworklet/core").SubgraphDecl<[config: {
4
+ scale: number;
5
+ }], {
6
+ tick(input: Node<"f32">, gain: Node<"f32">): Node<"f32">;
7
+ }>;
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ import { defineSubgraph, state } from '@unworklet/core';
2
+ /** Minimal composition fixture, not a production DSP module. Required config avoids upstream #104. */
3
+ export const gateCell = defineSubgraph((config) => {
4
+ const previous = state.f32(0).named('previous');
5
+ return {
6
+ tick(input, gain) {
7
+ const output = previous.read().mul(gain).mul(config.scale);
8
+ previous.write(input);
9
+ return output;
10
+ },
11
+ };
12
+ });
@@ -0,0 +1,12 @@
1
+ import { type Node } from '@unworklet/core';
2
+ import type { OscillatorConfig } from './oscillator.js';
3
+ import type { FilterConfig } from './filter.js';
4
+ /** Replacement demonstration: half-level sine using unworklet's math primitive. */
5
+ export declare const exampleOscillator: import("@unworklet/core").SubgraphDecl<[config: OscillatorConfig], {
6
+ tick(frequency: Node<"f32">, reset: Node<"bool">): Node<"f32">;
7
+ }>;
8
+ /** Replacement demonstration: one-pole low-pass, intentionally ignores Q. */
9
+ export declare const exampleFilter: import("@unworklet/core").SubgraphDecl<[config: FilterConfig], {
10
+ tick(input: Node<"f32">, cutoff: Node<"f32">, _q: Node<"f32">, reset: Node<"bool">): Node<"f32">;
11
+ }>;
12
+ export declare const replacementInstrument: import("@unworklet/core").CompiledProcessor<unknown>;
@@ -0,0 +1,29 @@
1
+ import { defineSubgraph, f32, f64, select, state } from '@unworklet/core';
2
+ import { createInstrument } from './instrument.js';
3
+ /** Replacement demonstration: half-level sine using unworklet's math primitive. */
4
+ export const exampleOscillator = defineSubgraph((config) => {
5
+ if (config.waveform !== 'sine')
6
+ throw new RangeError('exampleOscillator supports sine only');
7
+ const phase = state.f64(0).named('phase');
8
+ return { tick(frequency, reset) {
9
+ const p = select(reset, f64(0), phase.read());
10
+ phase.write(p.add(f64(frequency.clamp(0, 0.45 * config.sampleRate)).div(config.sampleRate)).frac());
11
+ return f32(p.mul(2 * Math.PI).sin()).mul(0.5);
12
+ } };
13
+ });
14
+ /** Replacement demonstration: one-pole low-pass, intentionally ignores Q. */
15
+ export const exampleFilter = defineSubgraph((config) => {
16
+ const previous = state.f64(0).named('previous');
17
+ return { tick(input, cutoff, _q, reset) {
18
+ // Backward Euler one-pole: alpha = w/(1+w), w=2*pi*fc/fs.
19
+ const w = f64(cutoff.clamp(20, Math.min(20000, 0.45 * config.sampleRate))).mul(2 * Math.PI / config.sampleRate);
20
+ const old = select(reset, f64(0), previous.read());
21
+ const output = old.add(w.div(w.add(1)).mul(f64(input).sub(old)));
22
+ previous.write(output);
23
+ return f32(output);
24
+ } };
25
+ });
26
+ export const replacementInstrument = createInstrument({
27
+ mode: 'poly', capacity: 4, heldCapacity: 32, waveform: 'sine',
28
+ oscillator: exampleOscillator, filter: exampleFilter,
29
+ });
@@ -0,0 +1,95 @@
1
+ /** CANDIDATE: low saw body, brief filter transient, last-held mono legato. */
2
+ export declare const bassConfig: {
3
+ readonly mode: "mono";
4
+ readonly capacity: 1;
5
+ readonly heldCapacity: 32;
6
+ readonly legato: true;
7
+ readonly waveform: "saw";
8
+ };
9
+ export declare const bassParameters: {
10
+ readonly gain: 0.12;
11
+ readonly ampAttack: 0.005;
12
+ readonly ampDecay: 0.12;
13
+ readonly ampSustain: 0.7;
14
+ readonly ampRelease: 0.12;
15
+ readonly pitchAttack: 0;
16
+ readonly pitchDecay: 0;
17
+ readonly pitchSustain: 0;
18
+ readonly pitchRelease: 0;
19
+ readonly filterAttack: 0.003;
20
+ readonly filterDecay: 0.18;
21
+ readonly filterSustain: 0.2;
22
+ readonly filterRelease: 0.12;
23
+ readonly cutoff: 300;
24
+ readonly resonance: 0.707;
25
+ readonly pitchEnvelopeDepth: 0;
26
+ readonly filterEnvelopeDepth: 3;
27
+ readonly lfoRate: 0;
28
+ readonly lfoAmpDepth: 0;
29
+ readonly lfoPitchDepth: 0;
30
+ readonly lfoFilterDepth: 0;
31
+ readonly bypass: 0;
32
+ };
33
+ /** CANDIDATE: tonal kick/tom body, two-octave pitch drop, retriggered hits. */
34
+ export declare const percussionConfig: {
35
+ readonly mode: "mono";
36
+ readonly capacity: 1;
37
+ readonly heldCapacity: 32;
38
+ readonly legato: false;
39
+ readonly waveform: "sine";
40
+ };
41
+ export declare const percussionParameters: {
42
+ readonly gain: 0.16;
43
+ readonly ampAttack: 0.001;
44
+ readonly ampDecay: 0.22;
45
+ readonly ampSustain: 0;
46
+ readonly ampRelease: 0.04;
47
+ readonly pitchAttack: 0;
48
+ readonly pitchDecay: 0.06;
49
+ readonly pitchSustain: 0;
50
+ readonly pitchRelease: 0.04;
51
+ readonly filterAttack: 0;
52
+ readonly filterDecay: 0.08;
53
+ readonly filterSustain: 0;
54
+ readonly filterRelease: 0.04;
55
+ readonly cutoff: 800;
56
+ readonly resonance: 0.5;
57
+ readonly pitchEnvelopeDepth: 24;
58
+ readonly filterEnvelopeDepth: 2;
59
+ readonly lfoRate: 0;
60
+ readonly lfoAmpDepth: 0;
61
+ readonly lfoPitchDepth: 0;
62
+ readonly lfoFilterDepth: 0;
63
+ readonly bypass: 0;
64
+ };
65
+ /** CANDIDATE: four saw voices, slow rise/release and shared slow modulation. */
66
+ export declare const padConfig: {
67
+ readonly mode: "poly";
68
+ readonly capacity: 4;
69
+ readonly heldCapacity: 32;
70
+ readonly waveform: "saw";
71
+ };
72
+ export declare const padParameters: {
73
+ readonly gain: 0.045;
74
+ readonly ampAttack: 0.8;
75
+ readonly ampDecay: 1.2;
76
+ readonly ampSustain: 0.7;
77
+ readonly ampRelease: 2.4;
78
+ readonly pitchAttack: 0;
79
+ readonly pitchDecay: 0;
80
+ readonly pitchSustain: 0;
81
+ readonly pitchRelease: 0;
82
+ readonly filterAttack: 1.4;
83
+ readonly filterDecay: 1.6;
84
+ readonly filterSustain: 0.5;
85
+ readonly filterRelease: 2.4;
86
+ readonly cutoff: 1400;
87
+ readonly resonance: 0.5;
88
+ readonly pitchEnvelopeDepth: 0;
89
+ readonly filterEnvelopeDepth: 0.75;
90
+ readonly lfoRate: 0.12;
91
+ readonly lfoAmpDepth: 0.08;
92
+ readonly lfoPitchDepth: 0.03;
93
+ readonly lfoFilterDepth: 0.2;
94
+ readonly bypass: 0;
95
+ };
@@ -0,0 +1,36 @@
1
+ /** CANDIDATE: low saw body, brief filter transient, last-held mono legato. */
2
+ export const bassConfig = {
3
+ mode: 'mono', capacity: 1, heldCapacity: 32, legato: true, waveform: 'saw',
4
+ };
5
+ export const bassParameters = {
6
+ gain: 0.12,
7
+ ampAttack: 0.005, ampDecay: 0.12, ampSustain: 0.7, ampRelease: 0.12,
8
+ pitchAttack: 0, pitchDecay: 0, pitchSustain: 0, pitchRelease: 0,
9
+ filterAttack: 0.003, filterDecay: 0.18, filterSustain: 0.2, filterRelease: 0.12,
10
+ cutoff: 300, resonance: 0.707, pitchEnvelopeDepth: 0, filterEnvelopeDepth: 3,
11
+ lfoRate: 0, lfoAmpDepth: 0, lfoPitchDepth: 0, lfoFilterDepth: 0, bypass: 0,
12
+ };
13
+ /** CANDIDATE: tonal kick/tom body, two-octave pitch drop, retriggered hits. */
14
+ export const percussionConfig = {
15
+ mode: 'mono', capacity: 1, heldCapacity: 32, legato: false, waveform: 'sine',
16
+ };
17
+ export const percussionParameters = {
18
+ gain: 0.16,
19
+ ampAttack: 0.001, ampDecay: 0.22, ampSustain: 0, ampRelease: 0.04,
20
+ pitchAttack: 0, pitchDecay: 0.06, pitchSustain: 0, pitchRelease: 0.04,
21
+ filterAttack: 0, filterDecay: 0.08, filterSustain: 0, filterRelease: 0.04,
22
+ cutoff: 800, resonance: 0.5, pitchEnvelopeDepth: 24, filterEnvelopeDepth: 2,
23
+ lfoRate: 0, lfoAmpDepth: 0, lfoPitchDepth: 0, lfoFilterDepth: 0, bypass: 0,
24
+ };
25
+ /** CANDIDATE: four saw voices, slow rise/release and shared slow modulation. */
26
+ export const padConfig = {
27
+ mode: 'poly', capacity: 4, heldCapacity: 32, waveform: 'saw',
28
+ };
29
+ export const padParameters = {
30
+ gain: 0.045,
31
+ ampAttack: 0.8, ampDecay: 1.2, ampSustain: 0.7, ampRelease: 2.4,
32
+ pitchAttack: 0, pitchDecay: 0, pitchSustain: 0, pitchRelease: 0,
33
+ filterAttack: 1.4, filterDecay: 1.6, filterSustain: 0.5, filterRelease: 2.4,
34
+ cutoff: 1400, resonance: 0.5, pitchEnvelopeDepth: 0, filterEnvelopeDepth: 0.75,
35
+ lfoRate: 0.12, lfoAmpDepth: 0.08, lfoPitchDepth: 0.03, lfoFilterDepth: 0.2, bypass: 0,
36
+ };
@@ -0,0 +1,42 @@
1
+ import { oscillator } from './oscillator.js';
2
+ import { filter } from './filter.js';
3
+ import { type VoicePolicyConfig } from './voice-policy.js';
4
+ export { bassConfig, bassParameters, percussionConfig, percussionParameters, padConfig, padParameters } from './instrument-settings.js';
5
+ export interface InstrumentConfig extends VoicePolicyConfig {
6
+ waveform?: 'sine' | 'saw';
7
+ /** Compatible unworklet subgraphs, instantiated independently for each voice. */
8
+ oscillator?: typeof oscillator;
9
+ filter?: typeof filter;
10
+ }
11
+ /** Diagnostic initial AudioParam values, not an approved preset or a preset format. */
12
+ export declare const diagnosticInstrumentParameters: {
13
+ readonly gain: 0.15;
14
+ readonly ampAttack: 0.005;
15
+ readonly ampDecay: 0.08;
16
+ readonly ampSustain: 0.7;
17
+ readonly ampRelease: 0.15;
18
+ readonly pitchAttack: 0;
19
+ readonly pitchDecay: 0.1;
20
+ readonly pitchSustain: 0;
21
+ readonly pitchRelease: 0.1;
22
+ readonly filterAttack: 0.01;
23
+ readonly filterDecay: 0.15;
24
+ readonly filterSustain: 0.2;
25
+ readonly filterRelease: 0.15;
26
+ readonly cutoff: 2500;
27
+ readonly resonance: 0.707;
28
+ readonly pitchEnvelopeDepth: 0;
29
+ readonly filterEnvelopeDepth: 1;
30
+ readonly lfoRate: 2;
31
+ readonly lfoAmpDepth: 0;
32
+ readonly lfoPitchDepth: 0;
33
+ readonly lfoFilterDepth: 0;
34
+ readonly bypass: 0;
35
+ };
36
+ /** MIDI input `midi`, stereo dual-mono output `main`, panic message `reset`.
37
+ * Capacities/mode/waveform/parts are construction choices; all musical controls
38
+ * use native AudioParams. See docs/instrument.md for timing and reset semantics.
39
+ */
40
+ export declare function createInstrument(config: InstrumentConfig): import("@unworklet/core").CompiledProcessor<unknown>;
41
+ /** Diagnostic default: four voices, configurable at construction. */
42
+ export declare const instrument: import("@unworklet/core").CompiledProcessor<unknown>;