@weasel-js/audio 1.10.1 → 1.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,6 +20,11 @@ to `setTimeout`, and a hidden tab books late again.
20
20
  The worker times the pass, but the pass itself runs on the main thread, so a
21
21
  stall there longer than the lookahead starts whatever came due late. The
22
22
  `onLate(lateMs, when)` option reports each one; if it fires, raise `lookahead`.
23
+ Or pass `synthVoices: 'worklet'`: `playNote` and `playNoise` voices are then
24
+ rendered in an `AudioWorkletProcessor` from events posted when they are played,
25
+ so one played ahead of a stall starts on time. The processor loads from a
26
+ `blob:` URL (CSP: `script-src blob:`); until it has, and where it cannot,
27
+ synth voices play on nodes.
23
28
 
24
29
  A suspended `AudioContext` is a separate matter: its clock stops, and on resume
25
30
  the engine drops what came due meanwhile rather than firing it all at once.
@@ -175,6 +180,27 @@ fade is `insertFadeMs`, default 15. A move is a splice out and a splice back in,
175
180
  so the moved effect drops out for one fade. A removed effect's `dispose` runs
176
181
  once it has left the graph.
177
182
 
183
+ ## Patchable graph
184
+
185
+ `engine.graph(bus)` returns a graph of nodes feeding that bus's input (the
186
+ first bus by default), which can be edited while it plays. Every structural
187
+ edit crossfades — `add`, `remove`, `replace`, `connect`, `disconnect` — and
188
+ `set` ramps a param. Sources are `createFileSourceNode` (a looping buffer),
189
+ `createOscillatorNode` and `createNoiseNode`; `createGainNode`,
190
+ `createPanNode`, `createMixNode`, `filterNode`, `delayNode`, `reverbNode` and
191
+ `compressorNode` process, and `insertNode` wraps any insert effect. `tap(id)`
192
+ puts an analyser on a node. The graph is disposed with the engine.
193
+
194
+ ```ts
195
+ const g = engine.graph('music');
196
+ g.add('osc', createOscillatorNode(engine.context, { frequency: 220 }));
197
+ g.add('echo', delayNode(engine.context, { time: 300, feedback: 0.4 }));
198
+ g.connect('osc', { node: 'echo', port: 'in' });
199
+ g.connect('echo', { output: true });
200
+ g.start();
201
+ g.replace('osc', createOscillatorNode(engine.context, { wave: 'square', frequency: 220 }));
202
+ ```
203
+
178
204
  ## Streaming
179
205
 
180
206
  `load` and `decode` hold a whole sound in memory, which is wrong for a long
package/dist/index.d.ts CHANGED
@@ -24,7 +24,7 @@ interface AnalyserTap {
24
24
  }
25
25
  /** Attach a new `AnalyserNode` to `source`. The tap only listens, adding no
26
26
  * path to the destination; `dispose()` detaches it. */
27
- declare function createAnalyserTap(ctx: AudioContext, source: AudioNode, opts?: AnalyserTapOptions): AnalyserTap;
27
+ declare function createAnalyserTap(ctx: BaseAudioContext, source: AudioNode, opts?: AnalyserTapOptions): AnalyserTap;
28
28
 
29
29
  /** A one-shot timer pair, as `AudioEngineOptions.setTimer` takes it. */
30
30
  interface Timers {
@@ -127,6 +127,87 @@ type BusGraphOptions = InsertChainOptions;
127
127
  */
128
128
  declare function createBusGraph(ctx: AudioContext, names: string[], opts?: BusGraphOptions): BusGraph;
129
129
 
130
+ /** What a node factory returns and the graph wraps. `inputs` is empty for a source. */
131
+ interface GraphNodeDef {
132
+ readonly inputs: Readonly<Record<string, AudioNode>>;
133
+ readonly output: AudioNode;
134
+ /** `rampMs` is always passed; the graph supplies its fade when the caller gives none. */
135
+ readonly params: Readonly<Record<string, (value: number, rampMs: number) => void>>;
136
+ /** Sources only. `when` is context seconds. Called when the graph starts, or on add/replace while it runs. A source must be startable again after `stop`. */
137
+ start?(when: number): void;
138
+ /** Sources only. Stop at `when` (context seconds). */
139
+ stop?(when: number): void;
140
+ /** Called once, after the node has faded out of the graph. */
141
+ dispose?(): void;
142
+ }
143
+ /** A connection's far end: a node's named input port, or the graph's output. */
144
+ type GraphTarget = {
145
+ node: string;
146
+ port: string;
147
+ } | {
148
+ output: true;
149
+ };
150
+ /** One edit to an `AudioGraph`, applied by its `apply` in order. */
151
+ type GraphPatch = {
152
+ kind: 'add';
153
+ id: string;
154
+ def: GraphNodeDef;
155
+ } | {
156
+ kind: 'remove';
157
+ id: string;
158
+ } | {
159
+ kind: 'replace';
160
+ id: string;
161
+ def: GraphNodeDef;
162
+ } | {
163
+ kind: 'connect';
164
+ from: string;
165
+ to: GraphTarget;
166
+ } | {
167
+ kind: 'disconnect';
168
+ from: string;
169
+ to: GraphTarget;
170
+ } | {
171
+ kind: 'set';
172
+ id: string;
173
+ param: string;
174
+ value: number;
175
+ rampMs?: number;
176
+ };
177
+ /** Options for `AudioGraph`: the crossfade length (`fadeMs`) and any timer overrides. */
178
+ interface AudioGraphOptions extends Partial<Timers> {
179
+ /** Crossfade for every structural edit, and the default ramp for `set`, in ms. Default 15. */
180
+ fadeMs?: number;
181
+ }
182
+ /** A patchable graph of nodes that can be added, wired and set while it plays. */
183
+ interface AudioGraph {
184
+ add(id: string, def: GraphNodeDef): void;
185
+ remove(id: string): void;
186
+ replace(id: string, def: GraphNodeDef): void;
187
+ connect(from: string, to: GraphTarget): void;
188
+ disconnect(from: string, to: GraphTarget): void;
189
+ set(id: string, param: string, value: number, rampMs?: number): void;
190
+ /** Applies in order; each patch calls the method of the same name.
191
+ * A structural error anywhere in the list throws before anything changes.
192
+ * A param write that throws is skipped; the rest apply, then its error is rethrown. */
193
+ apply(patches: readonly GraphPatch[]): void;
194
+ /** Start every source now, and every source added later, until `stop`. */
195
+ start(): void;
196
+ /** Fade the graph's output to silence over `fadeMs`, then stop every source. */
197
+ stop(): void;
198
+ playing(): boolean;
199
+ has(id: string): boolean;
200
+ ids(): string[];
201
+ /** Listens to a node's output; survives `replace`; disposed with the node. */
202
+ tap(id: string, opts?: AnalyserTapOptions): AnalyserTap;
203
+ /** Resolves once no fade is in flight. */
204
+ settled(): Promise<void>;
205
+ /** Cancels fades, stops sources, disposes every def and tap, disconnects from the destination.
206
+ * While it may be sounding on a running context, it first fades the output out over `fadeMs`;
207
+ * every mutator throws from the call on. */
208
+ dispose(): void;
209
+ }
210
+
130
211
  /** Opaque reference to a decoded sound. Mirrors `TextureHandle` in core. */
131
212
  interface SoundHandle {
132
213
  readonly id: string;
@@ -448,12 +529,26 @@ interface AudioEngineOptions {
448
529
  clearTimer?: (handle: unknown) => void;
449
530
  /** A voice or an `engine.schedule` callback came due `lateMs` after its
450
531
  * time `when`, both in engine ms: the main thread stalled for longer than
451
- * the lookahead covers, and the voice sounded late by that much. */
532
+ * the lookahead covers, and the voice sounded late by that much. A
533
+ * `synthVoices: 'worklet'` voice is never reported: it was not late. */
452
534
  onLate?: (lateMs: number, when: number) => void;
453
535
  /** Crossfade for every insert-chain edit and bypass, in ms. Default 15. */
454
536
  insertFadeMs?: number;
455
537
  /** Uniform in [0, 1), for where in its loop a noise voice starts. Default `Math.random`. */
456
538
  random?: () => number;
539
+ /**
540
+ * Where `playNote` and `playNoise` make their samples. Default `'nodes'`:
541
+ * oscillator and buffer nodes, built when the scheduler fires, so a main
542
+ * thread stalled past the lookahead starts the voice late. `'worklet'`
543
+ * renders them in an `AudioWorkletProcessor` from events posted at the
544
+ * call, so a voice played ahead of a stall still starts on time. Its handle,
545
+ * bus pool, stealing, pan and position behave as a node voice's do.
546
+ *
547
+ * The processor loads from a `blob:` URL, which a CSP must allow under
548
+ * `script-src`. Until it has loaded — `unlock()` waits for it — and on a
549
+ * context with no `audioWorklet`, synth voices play on nodes.
550
+ */
551
+ synthVoices?: 'nodes' | 'worklet';
457
552
  }
458
553
 
459
554
  /** A sound engine: named mix buses, a per-bus voice pool, lookahead
@@ -508,6 +603,8 @@ interface AudioEngine {
508
603
  * for a future `when` is not counted until it starts. */
509
604
  activeVoices(bus?: string): number;
510
605
  analyser(busName?: string, opts?: AnalyserTapOptions): AnalyserTap;
606
+ /** A graph feeding `bus`'s input (default: the first bus). Disposed with the engine. */
607
+ graph(bus?: string): AudioGraph;
511
608
  setListener(p: Vec2, opts?: SpatialOptions): void;
512
609
  dispose(): void;
513
610
  }
@@ -631,8 +728,12 @@ interface Scheduler {
631
728
  * true time to `source.start()` rather than "now". It runs on the first
632
729
  * pass whose lookahead window reaches it — or, when the window already
633
730
  * covers it and the scheduler is running, at the end of the current task,
634
- * in time order with everything else then due. */
635
- schedule(when: number, fire: (when: number) => void, key?: string): void;
731
+ * in time order with everything else then due. `reportLate: false` keeps
732
+ * it from `onLate`, for a callback whose work no longer depends on when it
733
+ * runs. */
734
+ schedule(when: number, fire: (when: number) => void, key?: string, opts?: {
735
+ reportLate?: boolean;
736
+ }): void;
636
737
  /** Drop queued events with this key. A pass takes its whole batch out of the
637
738
  * queue before firing any of it, so a callback cannot cancel a sibling that
638
739
  * came due alongside it. */
@@ -990,4 +1091,78 @@ declare function biquadCoefficients(type: BiquadFilterType, frequency: number, s
990
1091
  /** How much a filter with coefficients `c` passes at `hz`, in dB. */
991
1092
  declare function biquadResponseDb(c: BiquadCoefficients, hz: number, sampleRate: number): number;
992
1093
 
993
- export { type Acquisition, type AnalyserTap, type AnalyserTapOptions, type AudioEngine, type AudioEngineOptions, type BiquadCoefficients, type BusGraph, type BusGraphOptions, type BusHandle, type CompressorEffect, type CompressorEffectOptions, type DelayEffect, type DelayEffectOptions, type Envelope, type EnvelopePoint, type FilterEffect, type FilterEffectOptions, type Glide, type Inharmonic, type InsertAddOptions, type InsertChain, type InsertChainOptions, type InsertEffect, type InsertSlot, type KernelNoise, type KernelNote, type NoiseColor, type NoiseOptions, type NoisePatch, type NoteOptions, type PatternDetune, type PatternEvent, type PatternHit, type PatternLoadOptions, type PatternNoise, type PatternNote, type PatternPlayer, type PatternPlayerOptions, type Pitch, type PlayOptions, type ResolvedEnvelope, type ReverbEffect, type ReverbEffectOptions, SCORE_BAR_STEPS, type Scheduler, type SchedulerOptions, type Score, type ScoreBarOptions, type ScoreCue, type ScoreCueName, type ScoreCueReading, type ScoreMode, type ScoreMood, type ScoreOptions, type ScoreParams, type ScoreState, type SoundCache, type SoundHandle, type SpatialOptions, type StealPolicy, type StreamOptions, type SynthPartial, type SynthPatch, type TickTimer, type Vec2, type VoiceDropReason, type VoiceFilter, type VoiceHandle, type VoiceKernel, type VoicePool, type VoicePoolOptions, type VoiceRecord, type Waveform, type WetDryMix, biquadCoefficients, biquadResponseDb, createAnalyserTap, createAudioEngine, createBusGraph, createCompressorEffect, createDelayEffect, createFilterEffect, createNoiseKernel, createNoteKernel, createPatternPlayer, createReverbEffect, createScheduler, createScore, createSoundCache, createTickTimer, createVoicePool, envelopeLevel, envelopePoints, midiToFrequency, noiseSamples, noteToMidi, partialPresets, patternLength, patternVoicePeaks, readScoreCue, renderNoise, renderNote, resolveEnvelope, scoreBar, scoreChord, scoreCues, scoreModes, scoreParams, spatialize, toFrequency };
1094
+ /** Options for `impulseResponse`: the tail's decay, channel count and noise seed. */
1095
+ interface ImpulseResponseOptions {
1096
+ /** How far the tail falls by its end, dB. Default 60 (an RT60). */
1097
+ decayDb?: number;
1098
+ /** Default 2. */
1099
+ channels?: number;
1100
+ /** Seed for the noise, so the same call makes the same buffer. Default 1. */
1101
+ seed?: number;
1102
+ }
1103
+ /** Noise under an exponential decay that falls `decayDb` over `seconds`. */
1104
+ declare function impulseResponse(ctx: BaseAudioContext, seconds: number, opts?: ImpulseResponseOptions): AudioBuffer;
1105
+
1106
+ /**
1107
+ * A patchable graph of node defs feeding `destination`. Every structural edit
1108
+ * fades: a connection opens through its own gain and closes through it before
1109
+ * the cut, and a removed node leaves once its edges have closed.
1110
+ */
1111
+ declare function createAudioGraph(ctx: BaseAudioContext, destination: AudioNode, opts?: AudioGraphOptions): AudioGraph;
1112
+
1113
+ /** The waves an oscillator node takes: any `Waveform` except an inharmonic one. */
1114
+ type OscillatorWave = Exclude<Waveform, Inharmonic>;
1115
+ /** Options for `createFileSourceNode`: the buffer to play and whether it loops. */
1116
+ interface FileSourceNodeOptions {
1117
+ buffer: AudioBuffer;
1118
+ /** Default true. */
1119
+ loop?: boolean;
1120
+ }
1121
+ /** Plays a buffer, picking up where `stop` left it. Params: `rate`, `level`. */
1122
+ declare function createFileSourceNode(ctx: BaseAudioContext, opts: FileSourceNodeOptions): GraphNodeDef;
1123
+ /** Options for `createOscillatorNode`: the wave shape and starting frequency. */
1124
+ interface OscillatorNodeOptions {
1125
+ /** Default 'sine'. An array is the amplitude of each whole-number partial. */
1126
+ wave?: OscillatorWave;
1127
+ /** Hz. Default 440. */
1128
+ frequency?: number;
1129
+ }
1130
+ /** A tone. Params: `frequency`, `detune`, `level`. */
1131
+ declare function createOscillatorNode(ctx: BaseAudioContext, opts: OscillatorNodeOptions): GraphNodeDef;
1132
+ /** Options for `createNoiseNode`: the noise color whose shared buffer it loops. */
1133
+ interface NoiseNodeOptions {
1134
+ color: NoiseColor;
1135
+ }
1136
+ /** A loop of the shared noise buffer for its color. Param: `level`. */
1137
+ declare function createNoiseNode(ctx: BaseAudioContext, opts: NoiseNodeOptions): GraphNodeDef;
1138
+
1139
+ /** A graph node that multiplies its input by a gain: port `in`; param `gain`. */
1140
+ declare function createGainNode(ctx: BaseAudioContext, opts?: {
1141
+ gain?: number;
1142
+ }): GraphNodeDef;
1143
+ /** A graph node that pans its input in stereo: port `in`; param `pan`. */
1144
+ declare function createPanNode(ctx: BaseAudioContext, opts?: {
1145
+ pan?: number;
1146
+ }): GraphNodeDef;
1147
+ /** One port and one level param per name in `inputs`, summed into `output`. */
1148
+ declare function createMixNode(ctx: BaseAudioContext, opts: {
1149
+ inputs: readonly string[];
1150
+ }): GraphNodeDef;
1151
+ /** Wrap an insert effect as a node with one port, `in`. */
1152
+ declare function insertNode(effect: InsertEffect, params: GraphNodeDef['params']): GraphNodeDef;
1153
+ /** A graph node over a filter insert: port `in`; params `frequency`, `Q`, `gain`. */
1154
+ declare function filterNode(ctx: BaseAudioContext, opts?: FilterEffectOptions): GraphNodeDef;
1155
+ /** A graph node over a delay insert: port `in`; params `time` (ms), `feedback`, `mix`. */
1156
+ declare function delayNode(ctx: BaseAudioContext, opts?: DelayEffectOptions): GraphNodeDef;
1157
+ /** Options for `reverbNode`: the reverb effect's options, plus an `impulse` or the `seconds` of a synthesized one. */
1158
+ type ReverbNodeOptions = Omit<ReverbEffectOptions, 'impulse'> & {
1159
+ impulse?: AudioBuffer;
1160
+ /** Length of the synthesized impulse when none is given. Default 2. */
1161
+ seconds?: number;
1162
+ };
1163
+ /** A graph node over a reverb insert: port `in`; param `mix`. */
1164
+ declare function reverbNode(ctx: BaseAudioContext, opts?: ReverbNodeOptions): GraphNodeDef;
1165
+ /** A graph node over a compressor insert: port `in`; params `threshold`, `knee`, `ratio`, `attack` (ms), `release` (ms), `makeup`. */
1166
+ declare function compressorNode(ctx: BaseAudioContext, opts?: CompressorEffectOptions): GraphNodeDef;
1167
+
1168
+ export { type Acquisition, type AnalyserTap, type AnalyserTapOptions, type AudioEngine, type AudioEngineOptions, type AudioGraph, type AudioGraphOptions, type BiquadCoefficients, type BusGraph, type BusGraphOptions, type BusHandle, type CompressorEffect, type CompressorEffectOptions, type DelayEffect, type DelayEffectOptions, type Envelope, type EnvelopePoint, type FileSourceNodeOptions, type FilterEffect, type FilterEffectOptions, type Glide, type GraphNodeDef, type GraphPatch, type GraphTarget, type ImpulseResponseOptions, type Inharmonic, type InsertAddOptions, type InsertChain, type InsertChainOptions, type InsertEffect, type InsertSlot, type KernelNoise, type KernelNote, type NoiseColor, type NoiseNodeOptions, type NoiseOptions, type NoisePatch, type NoteOptions, type OscillatorNodeOptions, type OscillatorWave, type PatternDetune, type PatternEvent, type PatternHit, type PatternLoadOptions, type PatternNoise, type PatternNote, type PatternPlayer, type PatternPlayerOptions, type Pitch, type PlayOptions, type ResolvedEnvelope, type ReverbEffect, type ReverbEffectOptions, type ReverbNodeOptions, SCORE_BAR_STEPS, type Scheduler, type SchedulerOptions, type Score, type ScoreBarOptions, type ScoreCue, type ScoreCueName, type ScoreCueReading, type ScoreMode, type ScoreMood, type ScoreOptions, type ScoreParams, type ScoreState, type SoundCache, type SoundHandle, type SpatialOptions, type StealPolicy, type StreamOptions, type SynthPartial, type SynthPatch, type TickTimer, type Vec2, type VoiceDropReason, type VoiceFilter, type VoiceHandle, type VoiceKernel, type VoicePool, type VoicePoolOptions, type VoiceRecord, type Waveform, type WetDryMix, biquadCoefficients, biquadResponseDb, compressorNode, createAnalyserTap, createAudioEngine, createAudioGraph, createBusGraph, createCompressorEffect, createDelayEffect, createFileSourceNode, createFilterEffect, createGainNode, createMixNode, createNoiseKernel, createNoiseNode, createNoteKernel, createOscillatorNode, createPanNode, createPatternPlayer, createReverbEffect, createScheduler, createScore, createSoundCache, createTickTimer, createVoicePool, delayNode, envelopeLevel, envelopePoints, filterNode, impulseResponse, insertNode, midiToFrequency, noiseSamples, noteToMidi, partialPresets, patternLength, patternVoicePeaks, readScoreCue, renderNoise, renderNote, resolveEnvelope, reverbNode, scoreBar, scoreChord, scoreCues, scoreModes, scoreParams, spatialize, toFrequency };