@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,50 @@
1
+ import { type Node } from '@unworklet/core';
2
+ import { type DynamicsConfig, type DynamicsControls } from './dynamics.js';
3
+ export type MultibandDynamicsBandConfig = Omit<DynamicsConfig, 'sampleRate'>;
4
+ export interface MultibandDynamicsConfig {
5
+ sampleRate: number;
6
+ /** Fixed low/mid/high operations and detector modes. */
7
+ bands: readonly [MultibandDynamicsBandConfig, MultibandDynamicsBandConfig, MultibandDynamicsBandConfig];
8
+ }
9
+ export type MultibandDynamicsBandControls = Omit<DynamicsControls, 'reset'>;
10
+ export interface MultibandDynamicsControls {
11
+ /** Finite fallbacks 200/2000 Hz, individually clamped and then sorted. Equality is permitted. */
12
+ lowCutoffHz: Node<'f32'>;
13
+ highCutoffHz: Node<'f32'>;
14
+ bands: readonly [MultibandDynamicsBandControls, MultibandDynamicsBandControls, MultibandDynamicsBandControls];
15
+ reset: Node<'bool'>;
16
+ }
17
+ /** Three phase-compensated LR4 bands with independent linked-stereo dynamics. */
18
+ export declare const multibandDynamics: import("@unworklet/core").SubgraphDecl<[config: MultibandDynamicsConfig], {
19
+ /** No explicit sample delay. IIR phase/group delay is frequency dependent. */
20
+ tick(left: Node<"f32">, right: Node<"f32">, controls: MultibandDynamicsControls): {
21
+ left: Node<"f32">;
22
+ right: Node<"f32">;
23
+ /** Phase-matched unprocessed recombination; not the original dry PCM. */
24
+ dryLeft: Node<"f32">;
25
+ dryRight: Node<"f32">;
26
+ low: {
27
+ left: Node<"f32">;
28
+ right: Node<"f32">;
29
+ envelope: Node<"f32">;
30
+ gain: Node<"f32">;
31
+ gainDb: Node<"f32">;
32
+ };
33
+ mid: {
34
+ left: Node<"f32">;
35
+ right: Node<"f32">;
36
+ envelope: Node<"f32">;
37
+ gain: Node<"f32">;
38
+ gainDb: Node<"f32">;
39
+ };
40
+ high: {
41
+ left: Node<"f32">;
42
+ right: Node<"f32">;
43
+ envelope: Node<"f32">;
44
+ gain: Node<"f32">;
45
+ gainDb: Node<"f32">;
46
+ };
47
+ lowCutoffHz: Node<"f32">;
48
+ highCutoffHz: Node<"f32">;
49
+ };
50
+ }>;
@@ -0,0 +1,60 @@
1
+ import { defineSubgraph, f32, f64, instantiate, select, state } from '@unworklet/core';
2
+ import { crossover } from './crossover.js';
3
+ import { dynamics } from './dynamics.js';
4
+ function finite(input, fallback = 0) {
5
+ return select(input.eq(input).and(input.abs().lte(3.4028234663852886e38)), input, f32(fallback));
6
+ }
7
+ const sum = (a, b, c) => f32(f64(a).add(f64(b)).add(c ? f64(c) : f64(0)));
8
+ /** Three phase-compensated LR4 bands with independent linked-stereo dynamics. */
9
+ export const multibandDynamics = defineSubgraph((config) => {
10
+ const { sampleRate } = config;
11
+ if (!Number.isInteger(sampleRate) || sampleRate < 8000 || sampleRate > 192000) {
12
+ throw new RangeError('multibandDynamics sampleRate must be an integer in [8000,192000]');
13
+ }
14
+ if (!Array.isArray(config.bands) || config.bands.length !== 3)
15
+ throw new RangeError('multibandDynamics requires exactly three bands');
16
+ const maximum = Math.min(20000, 0.45 * sampleRate);
17
+ const splits = ['left', 'right'].map(channel => ({
18
+ low: instantiate(crossover, { sampleRate }, { name: `${channel}LowSplit` }),
19
+ high: instantiate(crossover, { sampleRate }, { name: `${channel}HighSplit` }),
20
+ compensate: instantiate(crossover, { sampleRate }, { name: `${channel}LowCompensation` }),
21
+ }));
22
+ // Materialize the existing f32 stage boundaries once. Without these native
23
+ // scratch buffers, 0.4.1 re-expands returned expressions at every consumer.
24
+ // Exact binary64 scaling prevents the native <1e-30 buffer-store flush from
25
+ // changing tiny audio; each slot is overwritten before read on every tick.
26
+ const scale = 2 ** 128;
27
+ const scratch = ['left', 'right'].map(channel => state.buffer.f64({ size: 5 })
28
+ .expose({ name: `${channel}StageScratchScaled`, snapshot: 'transient' }));
29
+ const processors = config.bands.map((band, i) => instantiate(dynamics, { ...band, sampleRate }, { name: `band${i}` }));
30
+ return {
31
+ /** No explicit sample delay. IIR phase/group delay is frequency dependent. */
32
+ tick(left, right, controls) {
33
+ const a = finite(controls.lowCutoffHz, 200).clamp(20, maximum);
34
+ const b = finite(controls.highCutoffHz, 2000).clamp(20, maximum);
35
+ const lowCutoffHz = a.min(b), highCutoffHz = a.max(b);
36
+ const bands = [left, right].map((input, ch) => {
37
+ const first = splits[ch].low.tick(finite(input), lowCutoffHz, controls.reset);
38
+ const store = (index, value) => scratch[ch].write(index, f64(value).mul(scale));
39
+ const read = (index) => f32(scratch[ch].read(index).div(scale));
40
+ store(0, first.low);
41
+ store(1, first.high);
42
+ const upper = splits[ch].high.tick(read(1), highCutoffHz, controls.reset);
43
+ const compensation = splits[ch].compensate.tick(read(0), highCutoffHz, controls.reset);
44
+ store(2, sum(compensation.low, compensation.high));
45
+ store(3, upper.low);
46
+ store(4, upper.high);
47
+ return [read(2), read(3), read(4)];
48
+ });
49
+ const processed = processors.map((unit, i) => unit.tick(bands[0][i], bands[1][i], bands[0][i], bands[1][i], { ...controls.bands[i], reset: controls.reset }));
50
+ return {
51
+ left: sum(processed[0].left, processed[1].left, processed[2].left),
52
+ right: sum(processed[0].right, processed[1].right, processed[2].right),
53
+ /** Phase-matched unprocessed recombination; not the original dry PCM. */
54
+ dryLeft: sum(...bands[0]), dryRight: sum(...bands[1]),
55
+ low: processed[0], mid: processed[1], high: processed[2],
56
+ lowCutoffHz, highCutoffHz,
57
+ };
58
+ },
59
+ };
60
+ });
@@ -0,0 +1,76 @@
1
+ import { type Node } from '@unworklet/core';
2
+ export interface CurvedAdsrConfig {
3
+ sampleRate: number;
4
+ }
5
+ export interface CurvedAdsrControls {
6
+ gate: Node<'bool'>;
7
+ /** Level-sensitive; ignored while gate is false. */
8
+ retrigger: Node<'bool'>;
9
+ /** Wins over all other controls and consumes the gate level. */
10
+ reset: Node<'bool'>;
11
+ /** Seconds, clamped [0,30], rounded to the nearest sample (half up). */
12
+ attack: Node<'f32'>;
13
+ decay: Node<'f32'>;
14
+ sustain: Node<'f32'>;
15
+ release: Node<'f32'>;
16
+ /** Latched segment bends [-1,1]: -1 ease-in, 0 linear, +1 ease-out. */
17
+ attackBend: Node<'f32'>;
18
+ decayBend: Node<'f32'>;
19
+ releaseBend: Node<'f32'>;
20
+ }
21
+ export interface CurvedAdsrOutput {
22
+ level: Node<'f32'>;
23
+ done: Node<'bool'>;
24
+ }
25
+ /**
26
+ * Segment-relative curved ADSR. Tick exactly once per sample; finite controls.
27
+ * C(t,b)=t+b*t*(1-t), with exact endpoints. Segment settings latch on entry;
28
+ * decay latches at the preceding positive attack endpoint. Sustain follows its
29
+ * current input in the sustain stage. Retrigger/note-off start at the last
30
+ * emitted f32 level. Zero attack collapses into decay on the same sample.
31
+ * Reset > gate-off > retrigger. Reset consumes held gate. No smoothing/tail
32
+ * threshold; zero sustain remains active. Bend zero is linear, not a promise of
33
+ * byte identity with the legacy iterative envelope. See the entry contract.
34
+ */
35
+ export declare const curvedAdsr: import("@unworklet/core").SubgraphDecl<[config: CurvedAdsrConfig], {
36
+ tick(c: CurvedAdsrControls): CurvedAdsrOutput;
37
+ }>;
38
+ export type MusicalLfoWaveform = 'sine' | 'triangle' | 'saw' | 'square';
39
+ export interface MusicalLfoConfig {
40
+ sampleRate: number;
41
+ mode: 'free' | 'tempo';
42
+ waveform: MusicalLfoWaveform;
43
+ /** Tempo mode only, power of two in [1/64,64], default 1. */
44
+ beatsPerCycle?: number;
45
+ }
46
+ export interface MusicalLfoControls {
47
+ /** Free: Hz [0,20]. Tempo: BPM [0,1000]. */
48
+ rate: Node<'f32'>;
49
+ /** Hold the base clock at zero; offset still applies. Consumes seek level. */
50
+ reset: Node<'bool'>;
51
+ /** Rising-edge seek, including while held. Reset wins. */
52
+ seek: Node<'bool'>;
53
+ /** Seek position in cycles, clamped +/-1048576 then wrapped. */
54
+ position: Node<'f32'>;
55
+ /** Live cycles offset, clamped +/-1048576. May deliberately jump. */
56
+ phaseOffset: Node<'f32'>;
57
+ /** Stops base-phase integration; seek/reset and live offset still apply. */
58
+ hold: Node<'bool'>;
59
+ }
60
+ export interface MusicalLfoOutput {
61
+ value: Node<'f32'>;
62
+ /** Canonical f32 waveform phase [0,1), capped at 1-2^-24. */
63
+ phase: Node<'f32'>;
64
+ }
65
+ /**
66
+ * Fixed-waveform LFO composed from musicalClock. Tick once per sample with finite
67
+ * inputs. Output precedes integration. Held reset pins base zero; release emits
68
+ * zero base before resuming. Hold/rate zero preserve base phase. Tempo follows
69
+ * BPM/(60*beatsPerCycle) exactly within the clock's numerical contract, including
70
+ * up to 1066 2/3 Hz. Public f32 clock-phase quantization is intentional, not the
71
+ * legacy sine LFO's internal-f64 phase guarantee. Waveforms are non-bandlimited;
72
+ * phase/offset/reset changes can jump. No smoothing/depth/routing/transport.
73
+ */
74
+ export declare const musicalLfo: import("@unworklet/core").SubgraphDecl<[config: MusicalLfoConfig], {
75
+ tick(c: MusicalLfoControls): MusicalLfoOutput;
76
+ }>;
@@ -0,0 +1,125 @@
1
+ import { defineSubgraph, f32, f64, i32, instantiate, select, state } from '@unworklet/core';
2
+ import { musicalClock } from './modulation.js';
3
+ function checkSampleRate(sampleRate) {
4
+ if (!Number.isInteger(sampleRate) || sampleRate < 8000 || sampleRate > 192000) {
5
+ throw new RangeError('musical controls sampleRate must be an integer in [8000,192000]');
6
+ }
7
+ }
8
+ // Core 0.4.1 flushes tiny scalar-state writes. Scaling by this exact power of two
9
+ // preserves the entire finite f32 range in the bounded level/bend histories.
10
+ const SCALE = 2 ** 128;
11
+ /**
12
+ * Segment-relative curved ADSR. Tick exactly once per sample; finite controls.
13
+ * C(t,b)=t+b*t*(1-t), with exact endpoints. Segment settings latch on entry;
14
+ * decay latches at the preceding positive attack endpoint. Sustain follows its
15
+ * current input in the sustain stage. Retrigger/note-off start at the last
16
+ * emitted f32 level. Zero attack collapses into decay on the same sample.
17
+ * Reset > gate-off > retrigger. Reset consumes held gate. No smoothing/tail
18
+ * threshold; zero sustain remains active. Bend zero is linear, not a promise of
19
+ * byte identity with the legacy iterative envelope. See the entry contract.
20
+ */
21
+ export const curvedAdsr = defineSubgraph((config) => {
22
+ checkSampleRate(config.sampleRate);
23
+ const stage = state.i32(0).named('stage'); // idle, attack, decay, sustain, release
24
+ const previousGate = state.bool(false).named('previousGate');
25
+ const total = state.i32(0).named('total');
26
+ const elapsed = state.i32(0).named('elapsed');
27
+ const start = state.f64(0).named('startScaled');
28
+ const target = state.f64(0).named('targetScaled');
29
+ const bend = state.f64(0).named('bendScaled');
30
+ const level = state.f64(0).named('levelScaled');
31
+ const frames = (seconds) => i32(f64(seconds.clamp(0, 30)).mul(config.sampleRate).add(0.5).floor());
32
+ return { tick(c) {
33
+ const oldStage = stage.read();
34
+ const on = c.gate.and(previousGate.read().not().or(c.retrigger));
35
+ const off = c.gate.not().and(previousGate.read()).and(oldStage.eq(0).not());
36
+ const a = frames(c.attack), d = frames(c.decay), r = frames(c.release);
37
+ const sustain = f64(c.sustain.clamp(0, 1)).mul(SCALE);
38
+ const attackBend = f64(c.attackBend.clamp(-1, 1)).mul(SCALE);
39
+ const decayBend = f64(c.decayBend.clamp(-1, 1)).mul(SCALE);
40
+ const releaseBend = f64(c.releaseBend.clamp(-1, 1)).mul(SCALE);
41
+ stage.write(select(off, i32(4), select(on, i32(1), oldStage)));
42
+ const active = stage.read(), attack = active.eq(1), release = active.eq(4);
43
+ const zeroAttack = attack.and(select(on, a, total.read()).eq(0));
44
+ const decay = active.eq(2).or(zeroAttack);
45
+ const moving = attack.or(decay).or(release);
46
+ total.write(select(release, select(off, r, total.read()), select(decay, select(zeroAttack, d, total.read()), select(on, a, total.read()))));
47
+ elapsed.write(select(on.or(off).or(zeroAttack), i32(0), elapsed.read()));
48
+ start.write(select(zeroAttack, f64(SCALE), select(on.or(off), level.read(), start.read())));
49
+ target.write(select(release, f64(0), select(decay, select(zeroAttack, sustain, target.read()), f64(SCALE))));
50
+ bend.write(select(release, select(off, releaseBend, bend.read()), select(decay, select(zeroAttack, decayBend, bend.read()), select(on, attackBend, bend.read()))));
51
+ elapsed.write(select(moving, elapsed.read().add(1).min(total.read()), elapsed.read()));
52
+ const t = f64(elapsed.read()).div(f64(total.read().max(1)));
53
+ const curved = t.add(bend.read().div(SCALE).mul(t).mul(f64(1).sub(t)));
54
+ const last = elapsed.read().gte(total.read()).or(release.and(start.read().eq(0)));
55
+ const advanced = select(last, target.read(), start.read().add(target.read().sub(start.read()).mul(curved)));
56
+ const emitted = f32(select(c.reset, f64(0), select(moving, advanced, select(active.eq(3), sustain, f64(0)))).div(SCALE)).clamp(0, 1);
57
+ // Store the actual f32 output: interruption starts at the observable level,
58
+ // not a hidden higher-precision value or a post-warped absolute amplitude.
59
+ level.write(f64(emitted).mul(SCALE));
60
+ const nextStage = select(c.reset, i32(0), select(moving.and(last), select(release, i32(0), select(decay, i32(3), i32(2))), select(decay, i32(2), active)));
61
+ const prepareDecay = attack.and(decay.not()).and(last);
62
+ total.write(select(c.reset, i32(0), select(prepareDecay, d, total.read())));
63
+ elapsed.write(select(c.reset.or(prepareDecay), i32(0), elapsed.read()));
64
+ start.write(select(c.reset, f64(0), select(prepareDecay, f64(SCALE), start.read())));
65
+ target.write(select(c.reset, f64(0), select(prepareDecay, sustain, target.read())));
66
+ bend.write(select(c.reset, f64(0), select(prepareDecay, decayBend, bend.read())));
67
+ stage.write(nextStage);
68
+ previousGate.write(c.gate);
69
+ return { level: f32(level.read().div(SCALE)), done: nextStage.eq(0) };
70
+ } };
71
+ });
72
+ const PHASE_MAX = 1 - 2 ** -24;
73
+ function sineAtPhase(phase) {
74
+ const centered = phase.sub(select(phase.gt(.5), f64(1), f64(0)));
75
+ const folded = select(centered.gt(.25), f64(.5).sub(centered), select(centered.lt(-.25), f64(-.5).sub(centered), centered));
76
+ const x = folded.mul(2 * Math.PI), x2 = x.mul(x);
77
+ // Same degree-13 reflected sine as the legacy LFO. At this canonical public
78
+ // f32 phase, |Taylor remainder| <= (pi/2)^15/15! < 7e-10 before output rounding.
79
+ const polynomial = f64(1 / 6227020800).mul(x2).sub(1 / 39916800)
80
+ .mul(x2).add(1 / 362880).mul(x2).sub(1 / 5040)
81
+ .mul(x2).add(1 / 120).mul(x2).sub(1 / 6).mul(x2).add(1);
82
+ return x.mul(polynomial);
83
+ }
84
+ /**
85
+ * Fixed-waveform LFO composed from musicalClock. Tick once per sample with finite
86
+ * inputs. Output precedes integration. Held reset pins base zero; release emits
87
+ * zero base before resuming. Hold/rate zero preserve base phase. Tempo follows
88
+ * BPM/(60*beatsPerCycle) exactly within the clock's numerical contract, including
89
+ * up to 1066 2/3 Hz. Public f32 clock-phase quantization is intentional, not the
90
+ * legacy sine LFO's internal-f64 phase guarantee. Waveforms are non-bandlimited;
91
+ * phase/offset/reset changes can jump. No smoothing/depth/routing/transport.
92
+ */
93
+ export const musicalLfo = defineSubgraph((config) => {
94
+ checkSampleRate(config.sampleRate);
95
+ if (config.mode !== 'free' && config.mode !== 'tempo')
96
+ throw new RangeError('musicalLfo mode must be free or tempo');
97
+ if (!['sine', 'triangle', 'saw', 'square'].includes(config.waveform))
98
+ throw new RangeError('unsupported musicalLfo waveform');
99
+ const beats = config.beatsPerCycle ?? 1;
100
+ if (!Number.isFinite(beats) || beats < 1 / 64 || beats > 64 || !Number.isInteger(Math.log2(beats))) {
101
+ throw new RangeError('beatsPerCycle must be a power of two in [1/64,64]');
102
+ }
103
+ if (config.mode === 'free' && beats !== 1)
104
+ throw new RangeError('beatsPerCycle applies only to tempo mode');
105
+ const clock = instantiate(musicalClock, {
106
+ sampleRate: config.sampleRate, mode: config.mode, steps: 1, stepsPerBeat: config.mode === 'tempo' ? 1 / beats : 1,
107
+ }, { name: 'clock' });
108
+ return { tick(c) {
109
+ const base = clock.tick({ rate: select(c.hold, f32(0), c.rate.clamp(0, config.mode === 'free' ? 20 : 1000)),
110
+ reset: c.reset, seek: c.seek, position: c.position });
111
+ const offset = f64(c.phaseOffset.clamp(-1048576, 1048576));
112
+ // Remove the integer part toward zero, preserving negative tiny fractions.
113
+ // Add only the signed fraction so large integral offsets cannot erase a
114
+ // tiny base phase. Wrap once afterward and cap the rounded upper endpoint.
115
+ const fraction = offset.sub(select(offset.lt(0), offset.neg().floor().neg(), offset.floor()));
116
+ const shifted = f64(base.phase).add(fraction);
117
+ const phase = f32(shifted.sub(shifted.floor())).clamp(0, PHASE_MAX);
118
+ const p = f64(phase);
119
+ const value = config.waveform === 'sine' ? sineAtPhase(p)
120
+ : config.waveform === 'triangle' ? select(p.lt(.25), p.mul(4), select(p.lt(.75), f64(2).sub(p.mul(4)), p.mul(4).sub(4)))
121
+ : config.waveform === 'saw' ? p.mul(2).sub(1)
122
+ : select(p.lt(.5), f64(1), f64(-1));
123
+ return { value: f32(value).clamp(-1, 1), phase };
124
+ } };
125
+ });
@@ -0,0 +1,21 @@
1
+ export { musicalMaterialsStatus, glassDyadParameters, glassDyadBrightParameters, glassDyadConstruction, makeGlassDyadTable, fmModalHitParameters, fmModalHitBellParameters, fmModalHitModes, grainCloudParameters, grainCloudReverseParameters, grainCloudConstruction, makeGrainCloudSample, shapedEchoParameters, shapedEchoDarkParameters, } from './musical-materials.js';
2
+ /** CANDIDATE fixed two-voice instrument. Independent native gate/frequency params,
3
+ * shared resident morph table and ordinary native snapshot state; no MIDI allocator.
4
+ * Send `load` with makeGlassDyadTable() before gate-on. Output `main` is stereo.
5
+ */
6
+ export declare const glassDyad: import("@unworklet/core").CompiledProcessor<unknown>;
7
+ /** CANDIDATE fixed-pitch A3 struck voice. Gate rise excites one normalized modal
8
+ * impulse and restarts FM/amp. Each new hit clears the old modal tail, keeping
9
+ * its impulse bound; gate-off releases FM while the fixed modal tail continues.
10
+ */
11
+ export declare const fmModalHit: import("@unworklet/core").CompiledProcessor<unknown>;
12
+ /** CANDIDATE two-grain resident texture. `load` accepts makeGrainCloudSample().
13
+ * Gate-off stops new grains and lets existing windows finish. Parameters latch
14
+ * at each grain onset as specified by granularSource; no streaming or time stretch.
15
+ */
16
+ export declare const grainCloud: import("@unworklet/core").CompiledProcessor<unknown>;
17
+ /** CANDIDATE mono-in/stereo-out chain: soft first-order ADAA -> Q=.5 low-pass
18
+ * -> asymmetric feed into stereo ping-pong. `main` input is finite mono <=1.
19
+ * Dry/wet is intentional delay coloration; no external parallel-path alignment.
20
+ */
21
+ export declare const shapedEcho: import("@unworklet/core").CompiledProcessor<unknown>;
@@ -0,0 +1,142 @@
1
+ import { audioInput, audioOutput, bool, CAPACITY_16, defineProcessor, event, f32, forSample, instantiate, param, select, state, } from '@unworklet/core';
2
+ import { envelope } from './envelope.js';
3
+ import { residentSample, granularSource } from './sample.js';
4
+ import { wavetableSource } from './wavetable.js';
5
+ import { frequencyModulation } from './source.js';
6
+ import { modalResonator } from './resonator.js';
7
+ import { drive } from './drive.js';
8
+ import { stateVariableFilter } from './state-variable-filter.js';
9
+ import { pingPongDelay } from './stereo-delay.js';
10
+ import { glassDyadParameters as glass, fmModalHitParameters as hit, grainCloudParameters as grain, shapedEchoParameters as echo, fmModalHitModes, } from './musical-materials.js';
11
+ export { musicalMaterialsStatus, glassDyadParameters, glassDyadBrightParameters, glassDyadConstruction, makeGlassDyadTable, fmModalHitParameters, fmModalHitBellParameters, fmModalHitModes, grainCloudParameters, grainCloudReverseParameters, grainCloudConstruction, makeGrainCloudSample, shapedEchoParameters, shapedEchoDarkParameters, } from './musical-materials.js';
12
+ /** CANDIDATE fixed two-voice instrument. Independent native gate/frequency params,
13
+ * shared resident morph table and ordinary native snapshot state; no MIDI allocator.
14
+ * Send `load` with makeGlassDyadTable() before gate-on. Output `main` is stereo.
15
+ */
16
+ export const glassDyad = defineProcessor(({ sampleRate }) => {
17
+ const asset = instantiate(residentSample, { capacity: 384, sourceSampleRate: 48000 }, { name: 'asset' });
18
+ event({ from: 'main', name: 'load', capacity: CAPACITY_16, payloadCapacity: 1536 })
19
+ .onReceive(({ data }) => asset.load(data));
20
+ const gateA = param.f32({ default: glass.gateA, min: 0, max: 1, automationRate: 'a-rate' }).named('gateA');
21
+ const gateB = param.f32({ default: glass.gateB, min: 0, max: 1, automationRate: 'a-rate' }).named('gateB');
22
+ const frequencyA = param.f32({ default: glass.frequencyA, min: 55, max: 1200, automationRate: 'a-rate' }).named('frequencyA');
23
+ const frequencyB = param.f32({ default: glass.frequencyB, min: 55, max: 1200, automationRate: 'a-rate' }).named('frequencyB');
24
+ const frame = param.f32({ default: glass.frame, min: 0, max: 2, automationRate: 'a-rate' }).named('frame');
25
+ const gain = param.f32({ default: glass.gain, min: 0, max: .3, automationRate: 'a-rate' }).named('gain');
26
+ const reset = param.f32({ default: 0, min: 0, max: 1, automationRate: 'a-rate' }).named('reset');
27
+ const voices = [0, 1].map(n => ({
28
+ wave: instantiate(wavetableSource, { sampleRate, sample: asset, frameLength: 128, frameCount: 3 }, { name: `wave${n}` }),
29
+ amp: instantiate(envelope, { sampleRate }, { name: `amp${n}` }),
30
+ gate: state.bool(false).named(`previousGate${n}`),
31
+ }));
32
+ const out = audioOutput({ channels: 2, name: 'main' });
33
+ return { process() {
34
+ forSample(i => {
35
+ const clear = reset.at(i).gte(.5);
36
+ const values = voices.map((v, n) => {
37
+ const gate = (n === 0 ? gateA : gateB).at(i).gte(.5), trigger = gate.and(v.gate.read().not());
38
+ const amp = v.amp.tick({ gate, retrigger: bool(false), reset: clear,
39
+ attack: f32(.012), decay: f32(.2), sustain: f32(.65), release: f32(.28) });
40
+ const wave = v.wave.tick({ frequencyHz: (n === 0 ? frequencyA : frequencyB).at(i), frame: frame.at(i), reset: clear.or(trigger) });
41
+ v.gate.write(gate);
42
+ return select(clear, f32(0), wave.output.mul(amp.level));
43
+ });
44
+ out.ch(0).at(i).write(values[0].mul(.75).add(values[1].mul(.25)).mul(gain.at(i)));
45
+ out.ch(1).at(i).write(values[0].mul(.25).add(values[1].mul(.75)).mul(gain.at(i)));
46
+ });
47
+ } };
48
+ });
49
+ /** CANDIDATE fixed-pitch A3 struck voice. Gate rise excites one normalized modal
50
+ * impulse and restarts FM/amp. Each new hit clears the old modal tail, keeping
51
+ * its impulse bound; gate-off releases FM while the fixed modal tail continues.
52
+ */
53
+ export const fmModalHit = defineProcessor(({ sampleRate }) => {
54
+ const gate = param.f32({ default: hit.gate, min: 0, max: 1, automationRate: 'a-rate' }).named('gate');
55
+ const strength = param.f32({ default: hit.strength, min: 0, max: 1, automationRate: 'a-rate' }).named('strength');
56
+ const ratio = param.f32({ default: hit.ratio, min: .5, max: 4, automationRate: 'a-rate' }).named('ratio');
57
+ const deviation = param.f32({ default: hit.deviationHz, min: 0, max: 180, automationRate: 'a-rate' }).named('deviationHz');
58
+ const mix = param.f32({ default: hit.modalMix, min: 0, max: 1, automationRate: 'a-rate' }).named('modalMix');
59
+ const gain = param.f32({ default: hit.gain, min: 0, max: .3, automationRate: 'a-rate' }).named('gain');
60
+ const reset = param.f32({ default: 0, min: 0, max: 1, automationRate: 'a-rate' }).named('reset');
61
+ const previousGate = state.bool(false).named('previousGate');
62
+ const velocity = state.f32(0).named('strength');
63
+ const fm = instantiate(frequencyModulation, { sampleRate }, { name: 'fm' });
64
+ const modes = instantiate(modalResonator, { sampleRate, modes: fmModalHitModes }, { name: 'modes' });
65
+ const amp = instantiate(envelope, { sampleRate }, { name: 'amp' });
66
+ const out = audioOutput({ channels: 2, name: 'main' });
67
+ return { process() {
68
+ forSample(i => {
69
+ const on = gate.at(i).gte(.5), clear = reset.at(i).gte(.5);
70
+ const strike = on.and(previousGate.read().not()).and(clear.not());
71
+ velocity.write(select(clear, f32(0), select(strike, strength.at(i), velocity.read())));
72
+ const a = amp.tick({ gate: on, retrigger: strike, reset: clear,
73
+ attack: f32(.001), decay: f32(.18), sustain: f32(0), release: f32(.04) });
74
+ const tone = fm.tick({ carrierHz: f32(220), modulatorHz: ratio.at(i).mul(220),
75
+ deviationHz: deviation.at(i).mul(a.level), feedbackHz: f32(0), reset: clear.or(strike) });
76
+ const ring = modes.tick(select(strike, velocity.read(), f32(0)), clear.or(strike));
77
+ const m = mix.at(i);
78
+ const value = select(clear, f32(0), tone.mul(a.level).mul(velocity.read()).mul(f32(1).sub(m)).add(ring.mul(m)).mul(gain.at(i)));
79
+ out.ch(0).at(i).write(value);
80
+ out.ch(1).at(i).write(value);
81
+ previousGate.write(on);
82
+ });
83
+ } };
84
+ });
85
+ /** CANDIDATE two-grain resident texture. `load` accepts makeGrainCloudSample().
86
+ * Gate-off stops new grains and lets existing windows finish. Parameters latch
87
+ * at each grain onset as specified by granularSource; no streaming or time stretch.
88
+ */
89
+ export const grainCloud = defineProcessor(({ sampleRate }) => {
90
+ const asset = instantiate(residentSample, { capacity: 4096, sourceSampleRate: 48000 }, { name: 'asset' });
91
+ event({ from: 'main', name: 'load', capacity: CAPACITY_16, payloadCapacity: 16384 })
92
+ .onReceive(({ data }) => asset.load(data));
93
+ const source = instantiate(granularSource, { sampleRate, sample: asset, maxGrains: 2, seed: 1741, loop: true }, { name: 'grains' });
94
+ const gate = param.f32({ default: 0, min: 0, max: 1, automationRate: 'a-rate' }).named('gate');
95
+ const position = param.f32({ default: grain.positionFrames, min: 0, max: 4095, automationRate: 'a-rate' }).named('positionFrames');
96
+ const jitter = param.f32({ default: grain.jitterFrames, min: 0, max: 4095, automationRate: 'a-rate' }).named('jitterFrames');
97
+ const rate = param.f32({ default: grain.rate, min: -2, max: 2, automationRate: 'a-rate' }).named('rate');
98
+ const duration = param.f32({ default: grain.durationSeconds, min: .02, max: .15, automationRate: 'a-rate' }).named('durationSeconds');
99
+ const density = param.f32({ default: grain.densityHz, min: 0, max: 20, automationRate: 'a-rate' }).named('densityHz');
100
+ const gain = param.f32({ default: grain.gain, min: 0, max: .4, automationRate: 'a-rate' }).named('gain');
101
+ const reset = param.f32({ default: 0, min: 0, max: 1, automationRate: 'a-rate' }).named('reset');
102
+ const out = audioOutput({ channels: 2, name: 'main' });
103
+ return { process() {
104
+ forSample(i => {
105
+ const result = source.tick({ gate: gate.at(i).gte(.5), reset: reset.at(i).gte(.5),
106
+ positionFrames: position.at(i), jitterFrames: jitter.at(i), rate: rate.at(i),
107
+ durationSeconds: duration.at(i), densityHz: density.at(i) });
108
+ const value = result.output.mul(gain.at(i));
109
+ out.ch(0).at(i).write(value);
110
+ out.ch(1).at(i).write(value);
111
+ });
112
+ } };
113
+ });
114
+ /** CANDIDATE mono-in/stereo-out chain: soft first-order ADAA -> Q=.5 low-pass
115
+ * -> asymmetric feed into stereo ping-pong. `main` input is finite mono <=1.
116
+ * Dry/wet is intentional delay coloration; no external parallel-path alignment.
117
+ */
118
+ export const shapedEcho = defineProcessor(({ sampleRate }) => {
119
+ const input = audioInput({ channels: 1, name: 'main' });
120
+ const out = audioOutput({ channels: 2, name: 'main' });
121
+ const shaper = instantiate(drive, { sampleRate, curve: 'soft', quality: 'adaa' }, { name: 'drive' });
122
+ const filter = instantiate(stateVariableFilter, { sampleRate }, { name: 'filter' });
123
+ const delay = instantiate(pingPongDelay, { sampleRate, maxDelaySeconds: .25 }, { name: 'echo' });
124
+ const driveGain = param.f32({ default: echo.drive, min: 0, max: 8, automationRate: 'a-rate' }).named('drive');
125
+ const cutoff = param.f32({ default: echo.cutoffHz, min: 100, max: 4000, automationRate: 'a-rate' }).named('cutoffHz');
126
+ const time = param.f32({ default: echo.delaySeconds, min: .04, max: .25, automationRate: 'a-rate' }).named('delaySeconds');
127
+ const feedback = param.f32({ default: echo.feedback, min: 0, max: .4, automationRate: 'a-rate' }).named('feedback');
128
+ const mix = param.f32({ default: echo.mix, min: 0, max: .6, automationRate: 'a-rate' }).named('mix');
129
+ const gain = param.f32({ default: echo.gain, min: 0, max: .25, automationRate: 'a-rate' }).named('gain');
130
+ const reset = param.f32({ default: 0, min: 0, max: 1, automationRate: 'a-rate' }).named('reset');
131
+ return { process() {
132
+ forSample(i => {
133
+ const clear = reset.at(i).gte(.5);
134
+ const x = shaper.tick(select(clear, f32(0), input.ch(0).at(i)), driveGain.at(i), f32(1), clear);
135
+ const tone = filter.tick(x, cutoff.at(i), f32(.5), clear).lowpass;
136
+ const wet = delay.tick(tone, tone.mul(.5), { timeSeconds: time.at(i), feedback: feedback.at(i),
137
+ mix: mix.at(i), bypass: bool(false), reset: clear });
138
+ out.ch(0).at(i).write(select(clear, f32(0), wet.left.mul(gain.at(i))));
139
+ out.ch(1).at(i).write(select(clear, f32(0), wet.right.mul(gain.at(i))));
140
+ });
141
+ } };
142
+ });
@@ -0,0 +1,110 @@
1
+ /** Original synthetic materials and ordinary native AudioParam values.
2
+ * These are CANDIDATE examples, not a preset format or listening-approved audio.
3
+ * Asset creation runs on the host before the native `load` message is sent.
4
+ */
5
+ export declare const musicalMaterialsStatus: "CANDIDATE";
6
+ export declare const glassDyadParameters: {
7
+ readonly gateA: 0;
8
+ readonly gateB: 0;
9
+ readonly frequencyA: 220;
10
+ readonly frequencyB: 330;
11
+ readonly frame: 0.25;
12
+ readonly gain: 0.2;
13
+ readonly reset: 0;
14
+ };
15
+ export declare const glassDyadBrightParameters: {
16
+ readonly frame: 1.75;
17
+ readonly gateA: 0;
18
+ readonly gateB: 0;
19
+ readonly frequencyA: 220;
20
+ readonly frequencyB: 330;
21
+ readonly gain: 0.2;
22
+ readonly reset: 0;
23
+ };
24
+ export declare const glassDyadConstruction: {
25
+ readonly voices: 2;
26
+ readonly frameLength: 128;
27
+ readonly frameCount: 3;
28
+ readonly capacity: 384;
29
+ };
30
+ /** Three complete 128-sample cycles, no duplicate endpoint; fixed weights, no normalization. */
31
+ export declare function makeGlassDyadTable(): Float32Array;
32
+ export declare const fmModalHitParameters: {
33
+ readonly gate: 0;
34
+ readonly strength: 0.8;
35
+ readonly ratio: 1.5;
36
+ readonly deviationHz: 100;
37
+ readonly modalMix: 0.35;
38
+ readonly gain: 0.22;
39
+ readonly reset: 0;
40
+ };
41
+ export declare const fmModalHitBellParameters: {
42
+ readonly ratio: 2.75;
43
+ readonly deviationHz: 170;
44
+ readonly modalMix: 0.7;
45
+ readonly gate: 0;
46
+ readonly strength: 0.8;
47
+ readonly gain: 0.22;
48
+ readonly reset: 0;
49
+ };
50
+ export declare const fmModalHitModes: readonly [{
51
+ readonly frequencyHz: 220;
52
+ readonly decaySeconds: 0.42;
53
+ readonly gain: 1;
54
+ }, {
55
+ readonly frequencyHz: 351;
56
+ readonly decaySeconds: 0.24;
57
+ readonly gain: 0.6;
58
+ }, {
59
+ readonly frequencyHz: 563;
60
+ readonly decaySeconds: 0.15;
61
+ readonly gain: 0.35;
62
+ }];
63
+ export declare const grainCloudParameters: {
64
+ readonly gate: 0;
65
+ readonly positionFrames: 512;
66
+ readonly jitterFrames: 240;
67
+ readonly rate: 1;
68
+ readonly durationSeconds: 0.09;
69
+ readonly densityHz: 16;
70
+ readonly gain: 0.28;
71
+ readonly reset: 0;
72
+ };
73
+ export declare const grainCloudReverseParameters: {
74
+ readonly positionFrames: 2800;
75
+ readonly rate: -0.75;
76
+ readonly durationSeconds: 0.12;
77
+ readonly densityHz: 12;
78
+ readonly gate: 0;
79
+ readonly jitterFrames: 240;
80
+ readonly gain: 0.28;
81
+ readonly reset: 0;
82
+ };
83
+ export declare const grainCloudConstruction: {
84
+ readonly capacity: 4096;
85
+ readonly sourceSampleRate: 48000;
86
+ readonly maxGrains: 2;
87
+ readonly seed: 1741;
88
+ };
89
+ /** Original periodic tone cluster with a smooth fixed amplitude contour.
90
+ * Integer cycle counts make the short resident loop continuous. No recorded audio.
91
+ */
92
+ export declare function makeGrainCloudSample(): Float32Array;
93
+ export declare const shapedEchoParameters: {
94
+ readonly drive: 2.4;
95
+ readonly cutoffHz: 1800;
96
+ readonly delaySeconds: 0.125;
97
+ readonly feedback: 0.25;
98
+ readonly mix: 0.3;
99
+ readonly gain: 0.2;
100
+ readonly reset: 0;
101
+ };
102
+ export declare const shapedEchoDarkParameters: {
103
+ readonly drive: 4.5;
104
+ readonly cutoffHz: 650;
105
+ readonly delaySeconds: 0.1875;
106
+ readonly mix: 0.45;
107
+ readonly feedback: 0.25;
108
+ readonly gain: 0.2;
109
+ readonly reset: 0;
110
+ };
@@ -0,0 +1,56 @@
1
+ /** Original synthetic materials and ordinary native AudioParam values.
2
+ * These are CANDIDATE examples, not a preset format or listening-approved audio.
3
+ * Asset creation runs on the host before the native `load` message is sent.
4
+ */
5
+ export const musicalMaterialsStatus = 'CANDIDATE';
6
+ export const glassDyadParameters = {
7
+ gateA: 0, gateB: 0, frequencyA: 220, frequencyB: 330,
8
+ frame: .25, gain: .2, reset: 0,
9
+ };
10
+ export const glassDyadBrightParameters = { ...glassDyadParameters, frame: 1.75 };
11
+ export const glassDyadConstruction = { voices: 2, frameLength: 128, frameCount: 3, capacity: 384 };
12
+ /** Three complete 128-sample cycles, no duplicate endpoint; fixed weights, no normalization. */
13
+ export function makeGlassDyadTable() {
14
+ return Float32Array.from({ length: 384 }, (_, n) => {
15
+ const phase = 2 * Math.PI * (n % 128) / 128;
16
+ const frame = Math.floor(n / 128);
17
+ return frame === 0 ? .8 * Math.sin(phase)
18
+ : frame === 1 ? .6 * Math.sin(phase) + .2 * Math.sin(2 * phase)
19
+ : .4 * Math.sin(phase) + .25 * Math.sin(2 * phase) + .15 * Math.sin(3 * phase);
20
+ });
21
+ }
22
+ export const fmModalHitParameters = {
23
+ gate: 0, strength: .8, ratio: 1.5, deviationHz: 100, modalMix: .35, gain: .22, reset: 0,
24
+ };
25
+ export const fmModalHitBellParameters = { ...fmModalHitParameters, ratio: 2.75, deviationHz: 170, modalMix: .7 };
26
+ export const fmModalHitModes = [
27
+ { frequencyHz: 220, decaySeconds: .42, gain: 1 },
28
+ { frequencyHz: 351, decaySeconds: .24, gain: .6 },
29
+ { frequencyHz: 563, decaySeconds: .15, gain: .35 },
30
+ ];
31
+ export const grainCloudParameters = {
32
+ gate: 0, positionFrames: 512, jitterFrames: 240, rate: 1,
33
+ durationSeconds: .09, densityHz: 16, gain: .28, reset: 0,
34
+ };
35
+ export const grainCloudReverseParameters = {
36
+ ...grainCloudParameters, positionFrames: 2800, rate: -.75,
37
+ durationSeconds: .12, densityHz: 12,
38
+ };
39
+ export const grainCloudConstruction = { capacity: 4096, sourceSampleRate: 48000, maxGrains: 2, seed: 1741 };
40
+ /** Original periodic tone cluster with a smooth fixed amplitude contour.
41
+ * Integer cycle counts make the short resident loop continuous. No recorded audio.
42
+ */
43
+ export function makeGrainCloudSample() {
44
+ return Float32Array.from({ length: 4096 }, (_, n) => {
45
+ const phase = 2 * Math.PI * n / 4096;
46
+ const contour = .55 - .45 * Math.cos(phase);
47
+ return contour * (.32 * Math.sin(19 * phase) + .17 * Math.sin(31 * phase) + .08 * Math.sin(47 * phase));
48
+ });
49
+ }
50
+ export const shapedEchoParameters = {
51
+ drive: 2.4, cutoffHz: 1800, delaySeconds: .125, feedback: .25,
52
+ mix: .3, gain: .2, reset: 0,
53
+ };
54
+ export const shapedEchoDarkParameters = {
55
+ ...shapedEchoParameters, drive: 4.5, cutoffHz: 650, delaySeconds: .1875, mix: .45,
56
+ };