@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,10 @@
1
+ import { type Node } from '@unworklet/core';
2
+ export interface OscillatorConfig {
3
+ sampleRate: number;
4
+ waveform: 'sine' | 'saw';
5
+ }
6
+ /** Phase accumulator with sine or four-sample polynomial BLEP saw. */
7
+ export declare const oscillator: import("@unworklet/core").SubgraphDecl<[config: OscillatorConfig], {
8
+ /** Emit current phase, then advance. Reset emits phase zero on that sample. */
9
+ tick(frequency: Node<"f32">, reset: Node<"bool">): Node<"f32">;
10
+ }>;
@@ -0,0 +1,35 @@
1
+ import { defineSubgraph, f32, f64, select, state } from '@unworklet/core';
2
+ /** Phase accumulator with sine or four-sample polynomial BLEP saw. */
3
+ export const oscillator = defineSubgraph((config) => {
4
+ if (!Number.isFinite(config.sampleRate) || config.sampleRate < 8000 || config.sampleRate > 192000) {
5
+ throw new RangeError('oscillator sampleRate must be finite and in [8000, 192000] Hz');
6
+ }
7
+ if (config.waveform !== 'sine' && config.waveform !== 'saw')
8
+ throw new RangeError('unknown oscillator waveform');
9
+ const phase = state.f64(0).named('phase');
10
+ return {
11
+ /** Emit current phase, then advance. Reset emits phase zero on that sample. */
12
+ tick(frequency, reset) {
13
+ const dt = f64(frequency).clamp(0, 0.45 * config.sampleRate).div(config.sampleRate);
14
+ const p = select(reset, f64(0), phase.read());
15
+ phase.write(p.add(dt).frac());
16
+ if (config.waveform === 'sine') {
17
+ // Fold to [-pi/2, pi/2]; degree 13 has <7e-10 remainder here.
18
+ const cycles = select(p.lt(0.25), p, select(p.lt(0.75), f64(0.5).sub(p), p.sub(1)));
19
+ const angle = cycles.mul(2 * Math.PI), z = angle.mul(angle);
20
+ const polynomial = f64(1 / 6227020800).mul(z).sub(1 / 39916800).mul(z).add(1 / 362880).mul(z).sub(1 / 5040).mul(z).add(1 / 120).mul(z).sub(1 / 6).mul(z).add(1);
21
+ return f32(angle.mul(polynomial));
22
+ }
23
+ // A zero frequency holds phase; avoid 0/0 even in eagerly evaluated selects.
24
+ const width = select(dt.gt(0), dt, f64(1));
25
+ // Integrate a cubic B-spline kernel (four boxes convolved). Its step
26
+ // complement for x >= 0 has support two sample intervals.
27
+ const complement = (x) => {
28
+ const x2 = x.mul(x), outer = f64(2).sub(x).max(0);
29
+ return select(x.lt(1), f64(0.5).sub(x.mul(2 / 3)).add(x2.mul(x).div(3)).sub(x2.mul(x2).div(8)), outer.mul(outer).mul(outer).mul(outer).div(24));
30
+ };
31
+ const correction = complement(p.div(width)).sub(complement(f64(1).sub(p).div(width))).mul(2);
32
+ return f32(p.mul(2).sub(1).add(select(dt.gt(0), correction, f64(0))));
33
+ },
34
+ };
35
+ });
@@ -0,0 +1,19 @@
1
+ import { type Node } from '@unworklet/core';
2
+ import type { DriveCurve } from './drive.js';
3
+ /** Matched linear-phase bulk delay. FIR precursors exist before sample 32. */
4
+ export declare const OVERSAMPLED_DRIVE_LATENCY_SAMPLES = 32;
5
+ export interface OversampledDriveConfig {
6
+ /** Host rate. The fixed arithmetic shaper has no nested sample-rate runtime. */
7
+ sampleRate: number;
8
+ factor: 2 | 4;
9
+ curve: DriveCurve;
10
+ }
11
+ /** Fixed polyphase interpolation -> memoryless shape -> FIR/phase-zero
12
+ * decimation. Both factors have 32-sample matched bulk delay and deliberate
13
+ * high-frequency rolloff. Dry follows the exact linear resampling cascade.
14
+ */
15
+ export declare const oversampledDrive: import("@unworklet/core").SubgraphDecl<[config: OversampledDriveConfig], {
16
+ /** Once per host sample. Input [-8,8], linear gain [0,32], mix [0,1].
17
+ * Reset invalidates old history before consuming the current sample. */
18
+ tick(input: Node<"f32">, gain: Node<"f32">, mix: Node<"f32">, reset: Node<"bool">): Node<"f32">;
19
+ }>;
@@ -0,0 +1,99 @@
1
+ import { defineSubgraph, f32, f64, floor, i32, select, state } from '@unworklet/core';
2
+ /** Matched linear-phase bulk delay. FIR precursors exist before sample 32. */
3
+ export const OVERSAMPLED_DRIVE_LATENCY_SAMPLES = 32;
4
+ const SCALE = 2 ** 128;
5
+ function bounded(value, low, high) {
6
+ const wide = f64(value);
7
+ return select(wide.eq(wide), wide, f64(0)).clamp(low, high);
8
+ }
9
+ function soft(value) {
10
+ const x = value.clamp(-1, 1);
11
+ return x.mul(f64(1.5).sub(x.mul(x).mul(.5)));
12
+ }
13
+ function shape(x, curve) {
14
+ if (curve === 'hard')
15
+ return x.clamp(-1, 1);
16
+ if (curve === 'soft')
17
+ return soft(x);
18
+ if (curve === 'asymmetric')
19
+ return select(x.lt(0), soft(x.mul(2)).mul(.5), soft(x));
20
+ const phase = x.add(1).sub(floor(x.add(1).div(4)).mul(4));
21
+ return select(x.abs().lte(1), x, f64(1).sub(phase.sub(2).abs()));
22
+ }
23
+ function kernel(factor) {
24
+ const half = 16 * factor, cutoff = .45 / factor;
25
+ const values = Array.from({ length: 2 * half + 1 }, (_, k) => {
26
+ const distance = Math.abs(k - half);
27
+ if (distance === half)
28
+ return 0;
29
+ const ideal = distance === 0 ? 2 * cutoff : Math.sin(2 * Math.PI * cutoff * distance) / (Math.PI * distance);
30
+ return ideal * (.42 + .5 * Math.cos(Math.PI * distance / half) + .08 * Math.cos(2 * Math.PI * distance / half));
31
+ });
32
+ const sum = values.reduce((a, b) => a + b, 0);
33
+ return values.map(value => value / sum);
34
+ }
35
+ /** Fixed polyphase interpolation -> memoryless shape -> FIR/phase-zero
36
+ * decimation. Both factors have 32-sample matched bulk delay and deliberate
37
+ * high-frequency rolloff. Dry follows the exact linear resampling cascade.
38
+ */
39
+ export const oversampledDrive = defineSubgraph((config) => {
40
+ const { sampleRate, factor, curve } = config;
41
+ if (!Number.isFinite(sampleRate) || sampleRate < 8000 || sampleRate > 192000)
42
+ throw new RangeError('oversampledDrive sampleRate must be in [8000,192000] Hz');
43
+ if (factor !== 2 && factor !== 4)
44
+ throw new RangeError('oversampledDrive factor must be 2 or 4');
45
+ if (!['hard', 'soft', 'asymmetric', 'fold'].includes(curve))
46
+ throw new RangeError('unknown oversampledDrive curve');
47
+ const h = kernel(factor), size = h.length;
48
+ const dryKernel = Array.from({ length: 65 }, (_, j) => factor * h.reduce((sum, value, k) => sum + value * (h[factor * j - k] ?? 0), 0));
49
+ const driven = state.buffer.f64({ size: 33 }).expose({ name: 'driven-scaled', snapshot: 'persistent' });
50
+ const raw = state.buffer.f64({ size: 65 }).expose({ name: 'dry-scaled', snapshot: 'persistent' });
51
+ const shaped = state.buffer.f64({ size }).expose({ name: 'shaped-scaled', snapshot: 'persistent' });
52
+ const drivenCursor = state.i32(0).named('driven-cursor'), dryCursor = state.i32(0).named('dry-cursor');
53
+ const highCursor = state.i32(0).named('high-cursor');
54
+ const baseValid = state.i32(0).named('base-valid'), highValid = state.i32(0).named('high-valid');
55
+ const up = state.f64(0).expose({ name: 'interpolated-scaled', snapshot: 'transient' });
56
+ const wet = state.f64(0).expose({ name: 'wet-sum-scaled', snapshot: 'transient' });
57
+ const dry = state.f64(0).expose({ name: 'dry-sum-scaled', snapshot: 'transient' });
58
+ return {
59
+ /** Once per host sample. Input [-8,8], linear gain [0,32], mix [0,1].
60
+ * Reset invalidates old history before consuming the current sample. */
61
+ tick(input, gain, mix, reset) {
62
+ const audio = bounded(input, -8, 8), amount = bounded(mix, 0, 1);
63
+ baseValid.write(select(reset, i32(1), baseValid.read().add(1).min(65)));
64
+ highValid.write(select(reset, i32(0), highValid.read()));
65
+ raw.write(dryCursor.read(), audio.mul(SCALE));
66
+ driven.write(drivenCursor.read(), audio.mul(bounded(gain, 0, 32)).mul(SCALE));
67
+ const baseAt = (lag) => select(baseValid.read().gt(lag), driven.read(drivenCursor.read().add(33 - lag).mod(33)), f64(0));
68
+ const highAt = (lag) => select(highValid.read().gt(lag), shaped.read(highCursor.read().add(size - lag).mod(size)), f64(0));
69
+ // These are fixed construction-time phases, not a second graph scheduler.
70
+ // Filtering suppresses images before and after the nonlinearity.
71
+ for (let phase = 0; phase < factor; phase++) {
72
+ up.write(0);
73
+ for (let tap = phase; tap < size; tap += factor) {
74
+ if (h[tap] !== 0)
75
+ up.write(up.read().add(baseAt((tap - phase) / factor).mul(factor * h[tap])));
76
+ }
77
+ shaped.write(highCursor.read(), shape(up.read().div(SCALE), curve).mul(SCALE));
78
+ highValid.write(highValid.read().add(1).min(size));
79
+ if (phase === 0) {
80
+ wet.write(0);
81
+ for (let tap = 0; tap < size; tap++)
82
+ if (h[tap] !== 0)
83
+ wet.write(wet.read().add(highAt(tap).mul(h[tap])));
84
+ }
85
+ highCursor.write(highCursor.read().add(1).mod(size));
86
+ }
87
+ dry.write(0);
88
+ for (let tap = 0; tap < dryKernel.length; tap++) {
89
+ if (dryKernel[tap] !== 0) {
90
+ const previous = select(baseValid.read().gt(tap), raw.read(dryCursor.read().add(65 - tap).mod(65)), f64(0));
91
+ dry.write(dry.read().add(previous.mul(dryKernel[tap])));
92
+ }
93
+ }
94
+ drivenCursor.write(drivenCursor.read().add(1).mod(33));
95
+ dryCursor.write(dryCursor.read().add(1).mod(65));
96
+ return f32(dry.read().mul(f64(1).sub(amount)).add(wet.read().mul(amount)).div(SCALE));
97
+ },
98
+ };
99
+ });
@@ -0,0 +1,61 @@
1
+ import { type MidiInputHandle, type Node } from '@unworklet/core';
2
+ import { type VoicePolicyConfig } from './voice-policy.js';
3
+ export interface PitchGlideConfig {
4
+ sampleRate: number;
5
+ }
6
+ export interface PitchGlideControls {
7
+ /** MIDI-note semitones, clamped [-128,255]. */
8
+ target: Node<'f32'>;
9
+ /** [0,30] seconds; rounded to nearest sample and latched on target change. */
10
+ seconds: Node<'f32'>;
11
+ /** Level-sensitive: follow target immediately, consuming its current value. */
12
+ reset: Node<'bool'>;
13
+ /** Level-sensitive immediate target following, e.g. a nonlegato retrigger. */
14
+ snap: Node<'bool'>;
15
+ }
16
+ /** Constant-time linear glide in musical pitch. Call once per sample. */
17
+ export declare const pitchGlide: import("@unworklet/core").SubgraphDecl<[config: PitchGlideConfig], {
18
+ tick(c: PitchGlideControls): Node<"f32">;
19
+ }>;
20
+ export interface TuningControls {
21
+ note: Node<'f32'>;
22
+ transpose: Node<'f32'>;
23
+ cents: Node<'f32'>;
24
+ a4: Node<'f32'>;
25
+ }
26
+ /** Finite controls, bounded 12-TET conversion, one final frequency ceiling. */
27
+ export declare function tunedFrequency(c: TuningControls, maxFrequency: number): Node<"f32">;
28
+ export interface PerformancePolicyConfig extends VoicePolicyConfig {
29
+ /** Poly default/max 4; mono must be 1. */
30
+ capacity?: number;
31
+ /** Default/max 8; counts physically-held AND pedal-held identities. */
32
+ heldCapacity?: number;
33
+ }
34
+ /** Native MIDI expression + bounded sustain ledger over the unchanged allocator.
35
+ * MIDI remains quantum-boundary FIFO. All performance state is transient.
36
+ * See docs/performance.md for duplicate, overflow, CC and DSP-clear semantics.
37
+ */
38
+ export declare const performancePolicy: import("@unworklet/core").SubgraphDecl<[config: PerformancePolicyConfig], {
39
+ capacity: number;
40
+ heldCapacity: number;
41
+ reset: (when?: Node<"bool">) => void;
42
+ overflowed: () => Node<"bool">;
43
+ bindMidi(input: MidiInputHandle): void;
44
+ voices: {
45
+ read(): {
46
+ channelPressure: Node<"f32">;
47
+ polyPressure: Node<"f32">;
48
+ bend: Node<"f32">;
49
+ timbre: Node<"f32">;
50
+ sustain: Node<"bool">;
51
+ active: Node<"bool">;
52
+ gate: Node<"bool">;
53
+ note: Node<"i32">;
54
+ channel: Node<"i32">;
55
+ velocity: Node<"f32">;
56
+ };
57
+ takeRetrigger(): Node<"bool">;
58
+ takeReset(): Node<"bool">;
59
+ releaseFinished(done: Node<"bool">): void;
60
+ }[];
61
+ }>;
@@ -0,0 +1,241 @@
1
+ import { bool, defineSubgraph, f32, f64, i32, instantiate, select, state } from '@unworklet/core';
2
+ import { voicePolicy } from './voice-policy.js';
3
+ // Native scalar writes flush tiny values. Scale musical pitch state so every
4
+ // finite f32 target survives; this is storage encoding, not a new noise floor.
5
+ const PITCH_SCALE = 2 ** 128;
6
+ /** Constant-time linear glide in musical pitch. Call once per sample. */
7
+ export const pitchGlide = defineSubgraph((config) => {
8
+ if (!Number.isInteger(config.sampleRate) || config.sampleRate < 8000 || config.sampleRate > 192000) {
9
+ throw new RangeError('pitchGlide sampleRate must be an integer in [8000,192000]');
10
+ }
11
+ const live = state.bool(false).named('live');
12
+ const value = state.f64(0).named('valueScaled');
13
+ const target = state.f64(0).named('targetScaled');
14
+ const remaining = state.i32(0).named('remaining');
15
+ return { tick(c) {
16
+ const destination = f64(c.target.clamp(-128, 255)).mul(PITCH_SCALE);
17
+ const changed = destination.eq(target.read()).not();
18
+ const immediate = c.reset.or(c.snap).or(live.read().not());
19
+ const frames = i32(f64(c.seconds.clamp(0, 30)).mul(config.sampleRate).add(0.5).floor());
20
+ remaining.write(select(immediate, i32(0), select(changed, frames, remaining.read())));
21
+ const count = remaining.read();
22
+ const output = select(immediate.or(count.lte(1)), destination, value.read().add(destination.sub(value.read()).div(f64(count.max(1)))));
23
+ value.write(output);
24
+ target.write(destination);
25
+ remaining.write(count.sub(1).max(0));
26
+ live.write(true);
27
+ return f32(output.div(PITCH_SCALE));
28
+ } };
29
+ });
30
+ /** Finite controls, bounded 12-TET conversion, one final frequency ceiling. */
31
+ export function tunedFrequency(c, maxFrequency) {
32
+ if (!Number.isFinite(maxFrequency) || maxFrequency <= 0 || maxFrequency > 3.4028234663852886e38) {
33
+ throw new RangeError('maxFrequency must be positive and representable as finite f32');
34
+ }
35
+ const semitones = f64(c.note.clamp(-128, 255)).sub(69)
36
+ .add(f64(c.transpose.clamp(-48, 48))).add(f64(c.cents.clamp(-1200, 1200)).div(100));
37
+ // Both pow and exp use an approximate f32 bridge in core 0.4.1.
38
+ // |semitones| <= 257: exp(x/32), |x/32| < .465, degree 12,
39
+ // then five squarings. The bounded remainder stays below f32 rounding.
40
+ const x = semitones.mul(Math.LN2 / (12 * 32));
41
+ let factorial = 1;
42
+ const inverse = [1];
43
+ for (let n = 1; n <= 12; n++) {
44
+ factorial *= n;
45
+ inverse.push(1 / factorial);
46
+ }
47
+ let ratio = f64(inverse[12]);
48
+ for (let n = 11; n >= 0; n--)
49
+ ratio = ratio.mul(x).add(inverse[n]);
50
+ for (let n = 0; n < 5; n++)
51
+ ratio = ratio.mul(ratio);
52
+ return f32(f64(c.a4.clamp(220, 880)).mul(ratio).min(maxFrequency));
53
+ }
54
+ /** Native MIDI expression + bounded sustain ledger over the unchanged allocator.
55
+ * MIDI remains quantum-boundary FIFO. All performance state is transient.
56
+ * See docs/performance.md for duplicate, overflow, CC and DSP-clear semantics.
57
+ */
58
+ export const performancePolicy = defineSubgraph((config) => {
59
+ const size = config.capacity ?? (config.mode === 'mono' ? 1 : 4);
60
+ const heldSize = config.heldCapacity ?? 8;
61
+ if (!Number.isInteger(size) || size < 1 || size > 4)
62
+ throw new RangeError('performance capacity must be an integer in [1,4]');
63
+ if (!Number.isInteger(heldSize) || heldSize < size || heldSize > 8)
64
+ throw new RangeError('performance heldCapacity must be between capacity and 8');
65
+ const policy = instantiate(voicePolicy, { ...config, capacity: size, heldCapacity: heldSize }, { name: 'allocator' });
66
+ const ints = (name, length) => state.buffer.i32({ size: length }).expose({ name, snapshot: 'transient' });
67
+ const floats = (name, length) => state.buffer.f32({ size: length }).expose({ name, snapshot: 'transient' });
68
+ // Dense ages (1..heldSize), not an ever-increasing event counter.
69
+ const age = ints('heldAge', heldSize), key = ints('heldKey', heldSize), channel = ints('heldChannel', heldSize), down = ints('heldDown', heldSize);
70
+ const pedal = ints('pedal', 16), bend = floats('bend', 16), pressure = floats('channelPressure', 16), timbre = floats('timbre', 16);
71
+ const polyPressure = floats('polyPressure', size), trigger = ints('trigger', size), clear = ints('clear', size);
72
+ const oldKey = ints('oldKey', size), oldChannel = ints('oldChannel', size), scratch = ints('scratch', 3);
73
+ const previousIdentity = state.i32(-1).expose({ name: 'previousIdentity', snapshot: 'transient' });
74
+ const overflow = state.bool(false).expose({ name: 'overflow', snapshot: 'transient' });
75
+ const validChannel = (ch) => ch.gte(0).and(ch.lte(15));
76
+ const validKey = (note, ch) => validChannel(ch).and(note.gte(0)).and(note.lte(127));
77
+ const safeChannel = (ch) => ch.clamp(0, 15);
78
+ function newestIdentity() {
79
+ scratch.write(0, -1);
80
+ scratch.write(1, 0);
81
+ for (let n = 0; n < heldSize; n++) {
82
+ const newer = age.read(n).gt(scratch.read(1));
83
+ scratch.write(0, select(newer, i32(n), scratch.read(0)));
84
+ scratch.write(1, select(newer, age.read(n), scratch.read(1)));
85
+ }
86
+ }
87
+ function beforeChange() {
88
+ if (config.mode === 'mono') {
89
+ newestIdentity();
90
+ previousIdentity.write(scratch.read(0));
91
+ }
92
+ policy.voices.forEach((voice, n) => { const v = voice.read(); oldKey.write(n, v.note); oldChannel.write(n, v.channel); });
93
+ }
94
+ function afterChange() {
95
+ if (config.mode === 'mono')
96
+ newestIdentity();
97
+ const identityChanged = config.mode === 'mono' ? scratch.read(0).gte(0).and(scratch.read(0).eq(previousIdentity.read()).not()) : bool(false);
98
+ policy.voices.forEach((voice, n) => {
99
+ const v = voice.read(), retrigger = voice.takeRetrigger();
100
+ trigger.write(n, select(retrigger, i32(1), trigger.read(n)));
101
+ polyPressure.write(n, select(retrigger.or(identityChanged).or(v.active.not()).or(v.note.eq(oldKey.read(n)).not()).or(v.channel.eq(oldChannel.read(n)).not()), f32(0), polyPressure.read(n)));
102
+ });
103
+ }
104
+ function remove(index, when) {
105
+ const old = age.read(index);
106
+ for (let n = 0; n < heldSize; n++)
107
+ age.write(n, select(when.and(age.read(n).gt(old)), age.read(n).sub(1), age.read(n)));
108
+ age.write(index, select(when, i32(0), age.read(index)));
109
+ key.write(index, select(when, i32(0), key.read(index)));
110
+ channel.write(index, select(when, i32(0), channel.read(index)));
111
+ down.write(index, select(when, i32(0), down.read(index)));
112
+ }
113
+ function oldest(ch, note, physicallyDown, matchKey) {
114
+ scratch.write(0, 0);
115
+ scratch.write(1, heldSize + 1);
116
+ for (let n = 0; n < heldSize; n++) {
117
+ const candidate = age.read(n).gt(0).and(age.read(n).lt(scratch.read(1))).and(channel.read(n).eq(ch))
118
+ .and(physicallyDown ? down.read(n).gt(0) : down.read(n).eq(0)).and(matchKey ? key.read(n).eq(note) : bool(true));
119
+ scratch.write(0, select(candidate, i32(n), scratch.read(0)));
120
+ scratch.write(1, select(candidate, age.read(n), scratch.read(1)));
121
+ }
122
+ }
123
+ function noteOff(note, ch, when = bool(true)) {
124
+ oldest(ch, note, true, true);
125
+ const index = scratch.read(0), found = when.and(validKey(note, ch)).and(scratch.read(1).lte(heldSize));
126
+ const release = found.and(pedal.read(safeChannel(ch)).eq(0));
127
+ down.write(index, select(found, i32(0), down.read(index)));
128
+ policy.noteOff(select(release, note, i32(-1)), ch);
129
+ remove(index, release);
130
+ }
131
+ function flush(ch) {
132
+ for (let n = 0; n < heldSize; n++) {
133
+ oldest(ch, i32(0), false, false);
134
+ const index = scratch.read(0), found = validChannel(ch).and(scratch.read(1).lte(heldSize));
135
+ policy.noteOff(select(found, key.read(index), i32(-1)), ch);
136
+ remove(index, found);
137
+ }
138
+ }
139
+ function noteOn(note, ch, velocity) {
140
+ noteOff(note, ch, velocity.eq(0));
141
+ scratch.write(0, 0);
142
+ scratch.write(1, 0);
143
+ scratch.write(2, 0);
144
+ for (let n = heldSize - 1; n >= 0; n--) {
145
+ const free = age.read(n).eq(0);
146
+ scratch.write(0, select(free, i32(n), scratch.read(0)));
147
+ scratch.write(1, select(free, i32(1), scratch.read(1)));
148
+ scratch.write(2, scratch.read(2).add(select(free, i32(0), i32(1))));
149
+ }
150
+ const index = scratch.read(0), enabled = validKey(note, ch).and(velocity.gt(0)).and(velocity.lte(127));
151
+ const accept = enabled.and(scratch.read(1).gt(0));
152
+ overflow.write(overflow.read().or(enabled.and(accept.not())));
153
+ age.write(index, select(accept, scratch.read(2).add(1), age.read(index)));
154
+ key.write(index, select(accept, note, key.read(index)));
155
+ channel.write(index, select(accept, ch, channel.read(index)));
156
+ down.write(index, select(accept, i32(1), down.read(index)));
157
+ policy.noteOn(select(accept, note, i32(-1)), ch, velocity);
158
+ }
159
+ function allSoundOff(ch) {
160
+ policy.voices.forEach((voice, n) => {
161
+ const affected = voice.read().active.and(voice.read().channel.eq(ch));
162
+ clear.write(n, select(affected, i32(1), clear.read(n)));
163
+ trigger.write(n, select(affected, i32(0), trigger.read(n)));
164
+ });
165
+ policy.allSoundOff(ch);
166
+ for (let n = 0; n < heldSize; n++)
167
+ remove(i32(n), age.read(n).gt(0).and(channel.read(n).eq(ch)));
168
+ const safe = safeChannel(ch);
169
+ pedal.write(safe, select(validChannel(ch), i32(0), pedal.read(safe)));
170
+ }
171
+ function control(ch, controller, value) {
172
+ const safe = safeChannel(ch), valid = validChannel(ch).and(value.gte(0)).and(value.lte(127));
173
+ const sustain = valid.and(controller.eq(64)), resetControllers = valid.and(controller.eq(121));
174
+ const notesOff = valid.and(controller.eq(123)), soundOff = valid.and(controller.eq(120));
175
+ pedal.write(safe, select(resetControllers, i32(0), select(sustain, select(value.gte(64), i32(1), i32(0)), pedal.read(safe))));
176
+ bend.write(safe, select(resetControllers, f32(0), bend.read(safe)));
177
+ pressure.write(safe, select(resetControllers, f32(0), pressure.read(safe)));
178
+ timbre.write(safe, select(resetControllers, f32(0), select(valid.and(controller.eq(74)), f32(value).div(127), timbre.read(safe))));
179
+ policy.voices.forEach((voice, n) => polyPressure.write(n, select(resetControllers.and(voice.read().channel.eq(ch)), f32(0), polyPressure.read(n))));
180
+ for (let n = 0; n < heldSize; n++)
181
+ down.write(n, select(notesOff.and(channel.read(n).eq(ch)), i32(0), down.read(n)));
182
+ const release = resetControllers.or(sustain.and(value.lt(64))).or(notesOff.and(pedal.read(safe).eq(0)));
183
+ flush(select(release, ch, i32(-1)));
184
+ allSoundOff(select(soundOff, ch, i32(-1)));
185
+ }
186
+ function reset(when = bool(true)) {
187
+ policy.reset(when);
188
+ for (let n = 0; n < heldSize; n++) {
189
+ age.write(n, select(when, i32(0), age.read(n)));
190
+ key.write(n, select(when, i32(0), key.read(n)));
191
+ channel.write(n, select(when, i32(0), channel.read(n)));
192
+ down.write(n, select(when, i32(0), down.read(n)));
193
+ }
194
+ for (let ch = 0; ch < 16; ch++) {
195
+ pedal.write(ch, select(when, i32(0), pedal.read(ch)));
196
+ bend.write(ch, select(when, f32(0), bend.read(ch)));
197
+ pressure.write(ch, select(when, f32(0), pressure.read(ch)));
198
+ timbre.write(ch, select(when, f32(0), timbre.read(ch)));
199
+ }
200
+ for (let n = 0; n < size; n++) {
201
+ polyPressure.write(n, select(when, f32(0), polyPressure.read(n)));
202
+ trigger.write(n, select(when, i32(0), trigger.read(n)));
203
+ clear.write(n, select(when, i32(1), clear.read(n)));
204
+ }
205
+ overflow.write(select(when, false, overflow.read()));
206
+ }
207
+ return {
208
+ capacity: size, heldCapacity: heldSize, reset,
209
+ overflowed: () => overflow.read().or(policy.overflowed()),
210
+ bindMidi(input) {
211
+ input.onEvent('noteOn', e => { beforeChange(); noteOn(e.note, e.channel, e.velocity); afterChange(); });
212
+ input.onEvent('noteOff', e => { beforeChange(); noteOff(e.note, e.channel); afterChange(); });
213
+ input.onEvent('cc', e => { beforeChange(); control(e.channel, e.controller, e.value); afterChange(); });
214
+ input.onEvent('pitchBend', e => {
215
+ const ch = safeChannel(e.channel), value = f32(e.value).sub(8192);
216
+ bend.write(ch, select(validChannel(e.channel).and(e.value.gte(0)).and(e.value.lte(16383)), value.div(select(e.value.lt(8192), f32(8192), f32(8191))), bend.read(ch)));
217
+ });
218
+ input.onEvent('channelPressure', e => {
219
+ const ch = safeChannel(e.channel);
220
+ pressure.write(ch, select(validChannel(e.channel).and(e.pressure.gte(0)).and(e.pressure.lte(127)), f32(e.pressure).div(127), pressure.read(ch)));
221
+ });
222
+ input.onEvent('aftertouch', e => {
223
+ policy.voices.forEach((voice, n) => {
224
+ const v = voice.read(), match = v.active.and(v.channel.eq(e.channel)).and(v.note.eq(e.note)).and(e.pressure.gte(0)).and(e.pressure.lte(127));
225
+ polyPressure.write(n, select(match, f32(e.pressure).div(127), polyPressure.read(n)));
226
+ });
227
+ });
228
+ },
229
+ voices: policy.voices.map((voice, n) => ({
230
+ read() {
231
+ const v = voice.read(), ch = safeChannel(v.channel);
232
+ return { ...v, channelPressure: select(v.active, pressure.read(ch), f32(0)),
233
+ polyPressure: select(v.active, polyPressure.read(n), f32(0)), bend: select(v.active, bend.read(ch), f32(0)),
234
+ timbre: select(v.active, timbre.read(ch), f32(0)), sustain: v.active.and(pedal.read(ch).gt(0)) };
235
+ },
236
+ takeRetrigger() { const value = trigger.read(n).gt(0); trigger.write(n, 0); return value; },
237
+ takeReset() { const value = clear.read(n).gt(0); clear.write(n, 0); return value; },
238
+ releaseFinished(done) { voice.releaseFinished(done); },
239
+ })),
240
+ };
241
+ });
@@ -0,0 +1,26 @@
1
+ import { type Node } from '@unworklet/core';
2
+ export interface PitchQuantizerConfig {
3
+ /** 1..128 finite offsets in [0,12), in semitones. Copied, f32-rounded, sorted. */
4
+ pitchClasses: readonly number[];
5
+ /** Fixed Schmitt margin in semitones, f32-rounded in [0,12]. Default 0. */
6
+ hysteresis?: number;
7
+ }
8
+ export interface PitchQuantizerOutput {
9
+ /** Selected octave*12 + pitch class, rounded to f32. */
10
+ pitch: Node<'f32'>;
11
+ /** Index into the sorted, f32-rounded pitchClasses. */
12
+ degree: Node<'i32'>;
13
+ /** Octave multiplier; pitch=12*octave+pitchClasses[degree]. Not an octave label. */
14
+ octave: Node<'i32'>;
15
+ }
16
+ /**
17
+ * Audio-rate, octave-periodic nearest pitch, not audio pitch detection or MIDI
18
+ * output. Call once per sample. NaN becomes 0; other inputs clamp to +/-16384.
19
+ * f64 computational distance ties choose the lower pitch. A positive margin
20
+ * retains the previous degree inside its expanded adjacent-midpoint interval,
21
+ * including both endpoints. Reset (level-sensitive) and first call snap to
22
+ * nearest immediately. Zero margin always uses nearest, including tie policy.
23
+ */
24
+ export declare const pitchQuantizer: import("@unworklet/core").SubgraphDecl<[config: PitchQuantizerConfig], {
25
+ tick(input: Node<"f32">, reset: Node<"bool">): PitchQuantizerOutput;
26
+ }>;
@@ -0,0 +1,88 @@
1
+ import { bool, defineSubgraph, f32, f64, i32, select, state } from '@unworklet/core';
2
+ // Native 0.4.1 scalar state flushes tiny values. f32 controls/offsets scaled
3
+ // by this exact power of two remain well above that floor, including 2^-149.
4
+ const SCALE = 2 ** 128;
5
+ /**
6
+ * Audio-rate, octave-periodic nearest pitch, not audio pitch detection or MIDI
7
+ * output. Call once per sample. NaN becomes 0; other inputs clamp to +/-16384.
8
+ * f64 computational distance ties choose the lower pitch. A positive margin
9
+ * retains the previous degree inside its expanded adjacent-midpoint interval,
10
+ * including both endpoints. Reset (level-sensitive) and first call snap to
11
+ * nearest immediately. Zero margin always uses nearest, including tie policy.
12
+ */
13
+ export const pitchQuantizer = defineSubgraph((config) => {
14
+ if (!Array.isArray(config.pitchClasses) || config.pitchClasses.length < 1 || config.pitchClasses.length > 128) {
15
+ throw new RangeError('pitchClasses requires 1..128 offsets');
16
+ }
17
+ // Array.from visits sparse holes too; every supplied slot must be valid.
18
+ const degrees = Array.from(config.pitchClasses, value => {
19
+ if (!Number.isFinite(value) || value < 0 || value >= 12 || Math.fround(value) >= 12) {
20
+ throw new RangeError('pitch class must be finite in [0,12), including after f32 rounding');
21
+ }
22
+ return Math.fround(value) || 0;
23
+ }).sort((a, b) => a - b);
24
+ if (degrees.some((value, n) => n > 0 && value === degrees[n - 1])) {
25
+ throw new RangeError('pitch classes must be distinct after f32 rounding');
26
+ }
27
+ const requestedMargin = config.hysteresis ?? 0;
28
+ if (!Number.isFinite(requestedMargin) || requestedMargin < 0 || requestedMargin > 12) {
29
+ throw new RangeError('hysteresis must be finite in [0,12] semitones');
30
+ }
31
+ const margin = Math.fround(requestedMargin);
32
+ const degree = state.i32(0).named('degree');
33
+ const octave = state.i32(0).named('octave');
34
+ const initialized = state.bool(false).named('initialized');
35
+ const nearestDegree = state.i32(0).named('nearestDegree');
36
+ const nearestOctave = state.i32(0).named('nearestOctave');
37
+ const nearestPitch = state.f64(0).named('nearestPitchScaled');
38
+ const nearestDistance = state.f64(0).named('nearestDistanceScaled');
39
+ const candidateOctave = state.i32(0).named('candidateOctave');
40
+ const candidatePitch = state.f64(0).named('candidatePitchScaled');
41
+ const candidateDistance = state.f64(0).named('candidateDistanceScaled');
42
+ const takeCandidate = state.bool(false).named('takeCandidate');
43
+ const update = state.bool(false).named('update');
44
+ return { tick(input, reset) {
45
+ // Unlike min/max alone, the explicit NaN branch gives a portable policy.
46
+ const x = f64(select(input.eq(input), input, f32(0)).clamp(-16384, 16384));
47
+ nearestDistance.write(f64(32768 * SCALE));
48
+ nearestPitch.write(f64(32768 * SCALE));
49
+ for (let n = 0; n < degrees.length; n++) {
50
+ // Compare both bracketing octaves by their actual f64 distances.
51
+ // A midpoint shortcut is not equivalent near zero: e.g. +/-6 have
52
+ // equal rounded distances from an f32 subnormal, so lower must win.
53
+ candidateOctave.write(i32(x.sub(degrees[n]).div(12).floor()));
54
+ for (let adjacent = 0; adjacent < 2; adjacent++) {
55
+ if (adjacent > 0)
56
+ candidateOctave.write(candidateOctave.read().add(1));
57
+ candidatePitch.write(f64(candidateOctave.read()).mul(12).add(degrees[n]).mul(SCALE));
58
+ candidateDistance.write(x.mul(SCALE).sub(candidatePitch.read()).abs());
59
+ takeCandidate.write(candidateDistance.read().lt(nearestDistance.read()).or(candidateDistance.read().eq(nearestDistance.read()).and(candidatePitch.read().lt(nearestPitch.read()))));
60
+ nearestDegree.write(select(takeCandidate.read(), i32(n), nearestDegree.read()));
61
+ nearestOctave.write(select(takeCandidate.read(), candidateOctave.read(), nearestOctave.read()));
62
+ nearestPitch.write(select(takeCandidate.read(), candidatePitch.read(), nearestPitch.read()));
63
+ nearestDistance.write(select(takeCandidate.read(), candidateDistance.read(), nearestDistance.read()));
64
+ }
65
+ }
66
+ if (margin === 0) {
67
+ update.write(bool(true));
68
+ }
69
+ else {
70
+ let lower = f64(0), upper = f64(0);
71
+ for (let n = 0; n < degrees.length; n++) {
72
+ const previous = n === 0 ? degrees[degrees.length - 1] - 12 : degrees[n - 1];
73
+ const next = n === degrees.length - 1 ? degrees[0] + 12 : degrees[n + 1];
74
+ lower = select(degree.read().eq(n), f64((previous + degrees[n]) / 2 - margin), lower);
75
+ upper = select(degree.read().eq(n), f64((degrees[n] + next) / 2 + margin), upper);
76
+ }
77
+ const base = f64(octave.read()).mul(12);
78
+ update.write(reset.or(initialized.read().not()).or(x.lt(base.add(lower))).or(x.gt(base.add(upper))));
79
+ }
80
+ degree.write(select(update.read(), nearestDegree.read(), degree.read()));
81
+ octave.write(select(update.read(), nearestOctave.read(), octave.read()));
82
+ initialized.write(bool(true));
83
+ let selected = f64(degrees[0]);
84
+ for (let n = 1; n < degrees.length; n++)
85
+ selected = select(degree.read().eq(n), f64(degrees[n]), selected);
86
+ return { pitch: f32(f64(octave.read()).mul(12).add(selected)), degree: degree.read(), octave: octave.read() };
87
+ } };
88
+ });
@@ -0,0 +1,26 @@
1
+ import { type EveryNSamples, type MessageGraphPayload, type Node } from '@unworklet/core';
2
+ import { type PreparedConvolutionConfig, type PreparedConvolutionPacket } from './convolution-spectrum.js';
3
+ export { prepareConvolutionSpectrum, type PreparedConvolutionConfig, type PreparedConvolutionPacket } from './convolution-spectrum.js';
4
+ export interface PreparedConvolutionRuntimeConfig extends PreparedConvolutionConfig {
5
+ /** Pass ctx.renderQuantumSize; aligns native dispatch with snapshot boundaries. */
6
+ renderQuantumSize: number;
7
+ }
8
+ /** Fixed B128/P64 prepared-spectrum mono convolution candidate.
9
+ * Correctness/gain bounds require an unchanged packet from host preparation.
10
+ * Caller owns native CAPACITY_16 ingress with payloadCapacity 131072 bytes.
11
+ * Copy/scan runs in native dispatch, so preload with audible playback gated off.
12
+ */
13
+ export declare const preparedConvolution: import("@unworklet/core").SubgraphDecl<[config: PreparedConvolutionRuntimeConfig], {
14
+ /** Module-specific native event payload; this is not a host loader/ack. */
15
+ load(packet: MessageGraphPayload<PreparedConvolutionPacket>): void;
16
+ /** Once per sample, inside stride-1 forSample with its native scheduler.
17
+ * Finite normalized input[-1,1]. Reset discards its sample and keeps the IR.
18
+ * Reload clears prior tail but accepts the next sample after its handler.
19
+ */
20
+ tick(input: Node<"f32">, reset: Node<"bool">, everyNSamples: EveryNSamples): {
21
+ output: Node<"f32">;
22
+ loaded: Node<"bool">;
23
+ rejected: Node<"bool">;
24
+ revision: Node<"i32">;
25
+ };
26
+ }>;