@weasel-js/audio 1.11.1 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -2
- package/dist/index.js +93 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -133,8 +133,9 @@ interface GraphNodeDef {
|
|
|
133
133
|
readonly output: AudioNode;
|
|
134
134
|
/** `rampMs` is always passed; the graph supplies its fade when the caller gives none. */
|
|
135
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
|
-
|
|
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
|
+
* A source that stops on its own, as a non-looping file reaching its end does, calls `ended` so the graph can start it again. */
|
|
138
|
+
start?(when: number, ended?: () => void): void;
|
|
138
139
|
/** Sources only. Stop at `when` (context seconds). */
|
|
139
140
|
stop?(when: number): void;
|
|
140
141
|
/** Called once, after the node has faded out of the graph. */
|
package/dist/index.js
CHANGED
|
@@ -425,7 +425,9 @@ function createFader(ctx, timers, from, to) {
|
|
|
425
425
|
gain.connect(to);
|
|
426
426
|
let cancel = null;
|
|
427
427
|
let closing = false;
|
|
428
|
+
let closed = false;
|
|
428
429
|
let cutDone = false;
|
|
430
|
+
const dones = /* @__PURE__ */ new Set();
|
|
429
431
|
const cut = () => {
|
|
430
432
|
cancel?.();
|
|
431
433
|
cancel = null;
|
|
@@ -440,14 +442,20 @@ function createFader(ctx, timers, from, to) {
|
|
|
440
442
|
writeParam(ctx, gain.gain, 1, ms);
|
|
441
443
|
},
|
|
442
444
|
close(ms, done) {
|
|
443
|
-
if (
|
|
445
|
+
if (closed) done?.();
|
|
446
|
+
if (closed || cutDone) return;
|
|
447
|
+
if (done) dones.add(done);
|
|
448
|
+
if (closing) return;
|
|
444
449
|
closing = true;
|
|
445
450
|
const length = ctx.state === "running" ? ms : 0;
|
|
446
451
|
writeParam(ctx, gain.gain, 0, length);
|
|
447
452
|
cancel = atAudioTime(ctx, timers, ctx.currentTime + length / 1e3, () => {
|
|
448
453
|
cancel = null;
|
|
449
454
|
cut();
|
|
450
|
-
|
|
455
|
+
closed = true;
|
|
456
|
+
const all = [...dones];
|
|
457
|
+
dones.clear();
|
|
458
|
+
for (const d of all) d();
|
|
451
459
|
});
|
|
452
460
|
},
|
|
453
461
|
cut
|
|
@@ -481,11 +489,18 @@ function tapWrapper(ctx, w, opts) {
|
|
|
481
489
|
return t;
|
|
482
490
|
}
|
|
483
491
|
function startWrapper(w, when) {
|
|
484
|
-
|
|
492
|
+
const { def } = w;
|
|
493
|
+
if (w.running || !def.start) return;
|
|
485
494
|
w.running = true;
|
|
486
|
-
|
|
495
|
+
def.start(when, () => {
|
|
496
|
+
if (w.def === def) w.running = false;
|
|
497
|
+
});
|
|
487
498
|
}
|
|
488
499
|
function stopWrapper(w, when) {
|
|
500
|
+
for (const o of w.outgoing) {
|
|
501
|
+
if (o.running) o.def.stop?.(when);
|
|
502
|
+
o.running = false;
|
|
503
|
+
}
|
|
489
504
|
if (!w.running) return;
|
|
490
505
|
w.running = false;
|
|
491
506
|
w.def.stop?.(when);
|
|
@@ -528,10 +543,7 @@ function replaceDef(ctx, timers, w, next, ms, playing, hooks) {
|
|
|
528
543
|
w.outFader = openFader(ctx, timers, next.output, w.out, ms);
|
|
529
544
|
w.def = next;
|
|
530
545
|
w.running = false;
|
|
531
|
-
if (playing
|
|
532
|
-
w.running = true;
|
|
533
|
-
next.start(ctx.currentTime);
|
|
534
|
-
}
|
|
546
|
+
if (playing) startWrapper(w, ctx.currentTime);
|
|
535
547
|
let open = old.faders.length;
|
|
536
548
|
for (const f of old.faders) {
|
|
537
549
|
f.close(ms, () => {
|
|
@@ -3043,13 +3055,67 @@ function impulseResponse(ctx, seconds, opts = {}) {
|
|
|
3043
3055
|
return buffer;
|
|
3044
3056
|
}
|
|
3045
3057
|
|
|
3058
|
+
// src/graph/playhead.ts
|
|
3059
|
+
function createPlayhead(duration, loop, initialRate) {
|
|
3060
|
+
let pos = 0;
|
|
3061
|
+
let from = 0;
|
|
3062
|
+
let until = 0;
|
|
3063
|
+
let t0 = 0;
|
|
3064
|
+
let r0 = initialRate;
|
|
3065
|
+
let t1 = 0;
|
|
3066
|
+
let r1 = initialRate;
|
|
3067
|
+
const rateAt = (t) => {
|
|
3068
|
+
if (t >= t1) return r1;
|
|
3069
|
+
return t <= t0 ? r0 : r0 + (r1 - r0) * (t - t0) / (t1 - t0);
|
|
3070
|
+
};
|
|
3071
|
+
const distance = (t) => t <= t1 ? (t - t0) * (r0 + rateAt(t)) / 2 : (t1 - t0) * (r0 + r1) / 2 + (t - t1) * r1;
|
|
3072
|
+
const wrap = (p) => loop ? (p % duration + duration) % duration : Math.min(Math.max(p, 0), duration);
|
|
3073
|
+
const at = (t) => {
|
|
3074
|
+
const x = Math.min(Math.max(t, from), until);
|
|
3075
|
+
return x <= from ? wrap(pos) : wrap(pos + distance(x) - distance(from));
|
|
3076
|
+
};
|
|
3077
|
+
const hold = (t, rate) => {
|
|
3078
|
+
t0 = t;
|
|
3079
|
+
t1 = t;
|
|
3080
|
+
r0 = rate;
|
|
3081
|
+
r1 = rate;
|
|
3082
|
+
};
|
|
3083
|
+
return {
|
|
3084
|
+
start(when, now) {
|
|
3085
|
+
pos = at(until);
|
|
3086
|
+
from = Math.max(when, now);
|
|
3087
|
+
until = Infinity;
|
|
3088
|
+
hold(now, r1);
|
|
3089
|
+
return pos;
|
|
3090
|
+
},
|
|
3091
|
+
stop(when, now) {
|
|
3092
|
+
until = Math.max(when, now, from);
|
|
3093
|
+
},
|
|
3094
|
+
rate(value, rampMs, now) {
|
|
3095
|
+
pos = at(now);
|
|
3096
|
+
from = Math.min(Math.max(now, from), until);
|
|
3097
|
+
r0 = rateAt(now);
|
|
3098
|
+
t0 = now;
|
|
3099
|
+
t1 = now + Math.max(0, rampMs) / 1e3;
|
|
3100
|
+
r1 = value;
|
|
3101
|
+
},
|
|
3102
|
+
rewind(now) {
|
|
3103
|
+
pos = 0;
|
|
3104
|
+
from = now;
|
|
3105
|
+
until = now;
|
|
3106
|
+
}
|
|
3107
|
+
};
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3046
3110
|
// src/graph/sources.ts
|
|
3047
3111
|
function sourceShell(ctx, make) {
|
|
3048
3112
|
const output = ctx.createGain();
|
|
3049
3113
|
let live = null;
|
|
3114
|
+
const ending = /* @__PURE__ */ new Set();
|
|
3050
3115
|
return {
|
|
3051
3116
|
output,
|
|
3052
3117
|
live: () => live,
|
|
3118
|
+
sounding: () => live ? [live, ...ending] : [...ending],
|
|
3053
3119
|
/** `onEnd` runs if the node ends while it is still the live one: it played out. */
|
|
3054
3120
|
begin(when, setup, offset, onEnd) {
|
|
3055
3121
|
const node = make();
|
|
@@ -3070,7 +3136,11 @@ function sourceShell(ctx, make) {
|
|
|
3070
3136
|
if (!live) return;
|
|
3071
3137
|
const node = live;
|
|
3072
3138
|
live = null;
|
|
3073
|
-
|
|
3139
|
+
ending.add(node);
|
|
3140
|
+
node.onended = () => {
|
|
3141
|
+
ending.delete(node);
|
|
3142
|
+
node.disconnect();
|
|
3143
|
+
};
|
|
3074
3144
|
node.stop(when);
|
|
3075
3145
|
},
|
|
3076
3146
|
dispose() {
|
|
@@ -3088,42 +3158,34 @@ function createFileSourceNode(ctx, opts) {
|
|
|
3088
3158
|
const { buffer } = opts;
|
|
3089
3159
|
const loop = opts.loop ?? true;
|
|
3090
3160
|
let rate = 1;
|
|
3091
|
-
|
|
3092
|
-
let startedAt = 0;
|
|
3161
|
+
const head = createPlayhead(buffer.duration, loop, rate);
|
|
3093
3162
|
const shell = sourceShell(ctx, () => ctx.createBufferSource());
|
|
3094
|
-
const advance = (to) => {
|
|
3095
|
-
const next = offset + Math.max(0, to - startedAt) * rate;
|
|
3096
|
-
offset = loop ? next % buffer.duration : Math.min(next, buffer.duration);
|
|
3097
|
-
startedAt = to;
|
|
3098
|
-
};
|
|
3099
3163
|
return {
|
|
3100
3164
|
inputs: {},
|
|
3101
3165
|
output: shell.output,
|
|
3102
3166
|
params: {
|
|
3103
3167
|
rate(value, rampMs) {
|
|
3104
|
-
const src
|
|
3105
|
-
|
|
3106
|
-
advance(ctx.currentTime);
|
|
3107
|
-
writeParam(ctx, src.playbackRate, value, rampMs);
|
|
3108
|
-
}
|
|
3168
|
+
for (const src of shell.sounding()) writeParam(ctx, src.playbackRate, value, rampMs);
|
|
3169
|
+
head.rate(value, rampMs, ctx.currentTime);
|
|
3109
3170
|
rate = value;
|
|
3110
3171
|
},
|
|
3111
3172
|
level: shell.level
|
|
3112
3173
|
},
|
|
3113
|
-
start(when) {
|
|
3174
|
+
start(when, ended) {
|
|
3114
3175
|
if (shell.live()) return;
|
|
3115
|
-
|
|
3176
|
+
const offset = head.start(when, ctx.currentTime);
|
|
3116
3177
|
shell.begin(when, (s) => {
|
|
3117
3178
|
s.buffer = buffer;
|
|
3118
3179
|
s.loop = loop;
|
|
3119
3180
|
initParam(ctx, s.playbackRate, rate);
|
|
3120
3181
|
}, offset, () => {
|
|
3121
|
-
|
|
3182
|
+
head.rewind(ctx.currentTime);
|
|
3183
|
+
ended?.();
|
|
3122
3184
|
});
|
|
3123
3185
|
},
|
|
3124
3186
|
stop(when) {
|
|
3125
3187
|
if (!shell.live()) return;
|
|
3126
|
-
|
|
3188
|
+
head.stop(when, ctx.currentTime);
|
|
3127
3189
|
shell.end(when);
|
|
3128
3190
|
},
|
|
3129
3191
|
dispose: shell.dispose
|
|
@@ -3139,25 +3201,23 @@ function createOscillatorNode(ctx, opts) {
|
|
|
3139
3201
|
output: shell.output,
|
|
3140
3202
|
params: {
|
|
3141
3203
|
frequency(value, rampMs) {
|
|
3142
|
-
const osc
|
|
3143
|
-
if (osc) writeParam(ctx, osc.frequency, value, rampMs);
|
|
3204
|
+
for (const osc of shell.sounding()) writeParam(ctx, osc.frequency, value, rampMs);
|
|
3144
3205
|
frequency = value;
|
|
3145
3206
|
},
|
|
3146
3207
|
detune(value, rampMs) {
|
|
3147
|
-
const osc
|
|
3148
|
-
if (osc) writeParam(ctx, osc.detune, value, rampMs);
|
|
3208
|
+
for (const osc of shell.sounding()) writeParam(ctx, osc.detune, value, rampMs);
|
|
3149
3209
|
detune = value;
|
|
3150
3210
|
},
|
|
3151
3211
|
level: shell.level
|
|
3152
3212
|
},
|
|
3153
|
-
start(when) {
|
|
3213
|
+
start(when, ended) {
|
|
3154
3214
|
if (shell.live()) return;
|
|
3155
3215
|
shell.begin(when, (osc) => {
|
|
3156
3216
|
if (typeof wave === "string") osc.type = wave;
|
|
3157
3217
|
else osc.setPeriodicWave(periodicWave(ctx, wave));
|
|
3158
3218
|
initParam(ctx, osc.frequency, frequency);
|
|
3159
3219
|
initParam(ctx, osc.detune, detune);
|
|
3160
|
-
});
|
|
3220
|
+
}, void 0, ended);
|
|
3161
3221
|
},
|
|
3162
3222
|
stop: shell.end,
|
|
3163
3223
|
dispose: shell.dispose
|
|
@@ -3169,12 +3229,12 @@ function createNoiseNode(ctx, opts) {
|
|
|
3169
3229
|
inputs: {},
|
|
3170
3230
|
output: shell.output,
|
|
3171
3231
|
params: { level: shell.level },
|
|
3172
|
-
start(when) {
|
|
3232
|
+
start(when, ended) {
|
|
3173
3233
|
if (shell.live()) return;
|
|
3174
3234
|
shell.begin(when, (src) => {
|
|
3175
3235
|
src.buffer = noiseBuffer(ctx, opts.color);
|
|
3176
3236
|
src.loop = true;
|
|
3177
|
-
});
|
|
3237
|
+
}, void 0, ended);
|
|
3178
3238
|
},
|
|
3179
3239
|
stop: shell.end,
|
|
3180
3240
|
dispose: shell.dispose
|