onceEnded mechanism to clean audioNode.onended callbacks
This commit is contained in:
parent
af7855402f
commit
0ec9826de5
4 changed files with 45 additions and 26 deletions
|
|
@ -6,6 +6,7 @@ import {
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
|
onceEnded,
|
||||||
} from '@strudel/webaudio';
|
} from '@strudel/webaudio';
|
||||||
import gm from './gm.mjs';
|
import gm from './gm.mjs';
|
||||||
|
|
||||||
|
|
@ -170,12 +171,12 @@ export function registerSoundfonts() {
|
||||||
|
|
||||||
bufferSource.stop(envEnd);
|
bufferSource.stop(envEnd);
|
||||||
const stop = (releaseTime) => {};
|
const stop = (releaseTime) => {};
|
||||||
bufferSource.onended = () => {
|
onceEnded(bufferSource, () => {
|
||||||
bufferSource.disconnect();
|
bufferSource.disconnect();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
node.disconnect();
|
node.disconnect();
|
||||||
onended();
|
onended();
|
||||||
};
|
});
|
||||||
return { node, stop };
|
return { node, stop };
|
||||||
},
|
},
|
||||||
{ type: 'soundfont', prebake: true, fonts },
|
{ type: 'soundfont', prebake: true, fonts },
|
||||||
|
|
|
||||||
|
|
@ -349,20 +349,11 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||||
constantNode.connect(zeroGain);
|
constantNode.connect(zeroGain);
|
||||||
|
|
||||||
// Schedule the `onComplete` callback to occur at `stopTime`
|
// Schedule the `onComplete` callback to occur at `stopTime`
|
||||||
constantNode.onended = () => {
|
onceEnded(constantNode, () => {
|
||||||
// Ensure garbage collection
|
releaseAudioNode(zeroGain);
|
||||||
try {
|
releaseAudioNode(constantNode);
|
||||||
zeroGain.disconnect();
|
|
||||||
} catch {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
constantNode.disconnect();
|
|
||||||
} catch {
|
|
||||||
// pass
|
|
||||||
}
|
|
||||||
onComplete();
|
onComplete();
|
||||||
};
|
});
|
||||||
constantNode.start(startTime);
|
constantNode.start(startTime);
|
||||||
constantNode.stop(stopTime);
|
constantNode.stop(stopTime);
|
||||||
return constantNode;
|
return constantNode;
|
||||||
|
|
@ -554,6 +545,17 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
||||||
return Number(freq);
|
return Number(freq);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// This helper should be used instead of the `node.onended = callback` pattern
|
||||||
|
// It adds a mechanism to help minimize gc retention
|
||||||
|
export const onceEnded = (node, callback) => {
|
||||||
|
let onended = callback;
|
||||||
|
node.onended = function cleanup() {
|
||||||
|
onended && onended();
|
||||||
|
onended = null;
|
||||||
|
this.onended = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const releaseAudioNode = (node) => {
|
export const releaseAudioNode = (node) => {
|
||||||
if (node == null) return;
|
if (node == null) return;
|
||||||
|
|
||||||
|
|
@ -565,16 +567,24 @@ export const releaseAudioNode = (node) => {
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/disconnect
|
// https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/disconnect
|
||||||
node.disconnect();
|
node.disconnect();
|
||||||
|
|
||||||
// make sure all AudioScheduledSourceNode is in a stopped state
|
// make sure all AudioScheduledSourceNodes are in a stopped state
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/AudioScheduledSourceNode
|
// https://developer.mozilla.org/en-US/docs/Web/API/AudioScheduledSourceNode
|
||||||
if (node instanceof AudioScheduledSourceNode) {
|
if (node instanceof AudioScheduledSourceNode) {
|
||||||
|
if (node.onended && node.onended.name !== 'cleanup') {
|
||||||
|
logger(
|
||||||
|
`[superdough] Deprecation warning: it seems your code path is setting 'node.onended = callback' instead of using the onceEnded helper`,
|
||||||
|
);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
node.stop();
|
node.stop();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof DOMException && e.name === 'InvalidStateError') {
|
// At the stage, `start` was not called on the node
|
||||||
node.start(node.context.currentTime + 5); // will never happen
|
// but an `onended` callback releasing resources may exist
|
||||||
node.stop();
|
// and we want it to fire :
|
||||||
}
|
// - we force a start/stop cycle so that `onended` gets called
|
||||||
|
// - we `lock` the node so that no-one can start it
|
||||||
|
node.start(node.context.currentTime + 5); // will never happen
|
||||||
|
node.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,14 @@
|
||||||
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||||
import { registerSound, registerWaveTable } from './index.mjs';
|
import { registerSound, registerWaveTable } from './index.mjs';
|
||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator, releaseAudioNode } from './helpers.mjs';
|
import {
|
||||||
|
getADSRValues,
|
||||||
|
getParamADSR,
|
||||||
|
getPitchEnvelope,
|
||||||
|
getVibratoOscillator,
|
||||||
|
onceEnded,
|
||||||
|
releaseAudioNode,
|
||||||
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||||
|
|
@ -321,13 +328,13 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||||
|
|
||||||
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
|
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
|
||||||
node.connect(out);
|
node.connect(out);
|
||||||
bufferSource.onended = function () {
|
onceEnded(bufferSource, function () {
|
||||||
bufferSource.disconnect();
|
bufferSource.disconnect();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
node.disconnect();
|
node.disconnect();
|
||||||
out.disconnect();
|
out.disconnect();
|
||||||
onended();
|
onended();
|
||||||
};
|
});
|
||||||
let envEnd = holdEnd + release + 0.01;
|
let envEnd = holdEnd + release + 0.01;
|
||||||
bufferSource.stop(envEnd);
|
bufferSource.stop(envEnd);
|
||||||
const stop = (endTime) => {
|
const stop = (endTime) => {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { midiToFreq, noteToMidi } from './util.mjs';
|
||||||
import { registerSound } from './superdough.mjs';
|
import { registerSound } from './superdough.mjs';
|
||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import { buildSamples } from './zzfx_fork.mjs';
|
import { buildSamples } from './zzfx_fork.mjs';
|
||||||
|
import { onceEnded, releaseAudioNode } from './helpers.mjs';
|
||||||
|
|
||||||
export const getZZFX = (value, t) => {
|
export const getZZFX = (value, t) => {
|
||||||
let {
|
let {
|
||||||
|
|
@ -83,10 +84,10 @@ export function registerZZFXSounds() {
|
||||||
wave,
|
wave,
|
||||||
(t, value, onended) => {
|
(t, value, onended) => {
|
||||||
const { node: o } = getZZFX({ s: wave, ...value }, t);
|
const { node: o } = getZZFX({ s: wave, ...value }, t);
|
||||||
o.onended = () => {
|
onceEnded(o, () => {
|
||||||
o.disconnect();
|
releaseAudioNode(o);
|
||||||
onended();
|
onended();
|
||||||
};
|
});
|
||||||
return {
|
return {
|
||||||
node: o,
|
node: o,
|
||||||
stop: () => {},
|
stop: () => {},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue