Merge pull request '[perf] release unused AudioBufferSourceNode + releaseAudioNode' (#1805) from jeromew/strudel:fix-perf11 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1805 Reviewed-by: Aria <glossing@noreply.codeberg.org>
This commit is contained in:
commit
eb18648a2a
7 changed files with 91 additions and 48 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,10 +545,54 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
||||||
return Number(freq);
|
return Number(freq);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const destroyAudioWorkletNode = (node) => {
|
// This helper should be used instead of the `node.onended = callback` pattern
|
||||||
if (node == null) {
|
// It adds a mechanism to help minimize gc retention
|
||||||
return;
|
export const onceEnded = (node, callback) => {
|
||||||
}
|
let onended = callback;
|
||||||
node.disconnect();
|
node.onended = function cleanup() {
|
||||||
node.parameters.get('end')?.setValueAtTime(0, 0);
|
onended && onended();
|
||||||
|
onended = null;
|
||||||
|
this.onended = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const releaseAudioNode = (node) => {
|
||||||
|
if (node == null) return;
|
||||||
|
|
||||||
|
// check we received an AudioNode
|
||||||
|
if (!(node instanceof AudioNode)) {
|
||||||
|
throw new Error('releaseAudioNode can only release an AudioNode');
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/disconnect
|
||||||
|
node.disconnect();
|
||||||
|
|
||||||
|
// make sure all AudioScheduledSourceNodes are in a stopped state
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/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 {
|
||||||
|
node.stop();
|
||||||
|
} catch (e) {
|
||||||
|
// At the stage, `start` was not called on the node
|
||||||
|
// but an `onended` callback releasing resources may exist
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://www.w3.org/TR/webaudio-1.1/#AudioNode-actively-processing
|
||||||
|
// An AudioWorkletNode is actively processing when its AudioWorkletProcessor's [[callable process]]
|
||||||
|
// returns true and either its active source flag is true or
|
||||||
|
// any AudioNode connected to one of its inputs is actively processing.
|
||||||
|
if (node instanceof AudioWorkletNode) {
|
||||||
|
node.parameters.get('end')?.setValueAtTime(0, 0);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 } 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>
|
||||||
|
|
@ -286,17 +293,19 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||||
|
|
||||||
const { bufferSource, sliceDuration, offset } = await getSampleBufferSource(value, bank, resolveUrl);
|
const { bufferSource, sliceDuration, offset } = await getSampleBufferSource(value, bank, resolveUrl);
|
||||||
|
|
||||||
// asny stuff above took too long?
|
|
||||||
if (ac.currentTime > t) {
|
|
||||||
logger(`[sampler] still loading sound "${s}:${n}"`, 'highlight');
|
|
||||||
// console.warn('sample still loading:', s, n);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!bufferSource) {
|
if (!bufferSource) {
|
||||||
logger(`[sampler] could not load "${s}:${n}"`, 'error');
|
logger(`[sampler] could not load "${s}:${n}"`, 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// async stuff above took too long?
|
||||||
|
if (ac.currentTime > t) {
|
||||||
|
logger(`[sampler] loading sound "${s}:${n}" took too long`, 'highlight');
|
||||||
|
// AudioBufferSourceNode will never be used. discard it
|
||||||
|
releaseAudioNode(bufferSource);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// vibrato
|
// vibrato
|
||||||
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
|
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, t);
|
||||||
|
|
||||||
|
|
@ -319,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,7 +3,6 @@ import { registerSound, soundMap } from './superdough.mjs';
|
||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import {
|
import {
|
||||||
applyFM,
|
applyFM,
|
||||||
destroyAudioWorkletNode,
|
|
||||||
gainNode,
|
gainNode,
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
getFrequencyFromValue,
|
getFrequencyFromValue,
|
||||||
|
|
@ -13,6 +12,7 @@ import {
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
noises,
|
noises,
|
||||||
|
releaseAudioNode,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
@ -192,8 +192,7 @@ export function registerSynthSounds() {
|
||||||
let timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
releaseAudioNode(o);
|
||||||
envGain.disconnect();
|
|
||||||
onended();
|
onended();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
|
|
@ -270,8 +269,7 @@ export function registerSynthSounds() {
|
||||||
let timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
releaseAudioNode(o);
|
||||||
envGain.disconnect();
|
|
||||||
onended();
|
onended();
|
||||||
},
|
},
|
||||||
begin,
|
begin,
|
||||||
|
|
@ -344,9 +342,8 @@ export function registerSynthSounds() {
|
||||||
let timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
releaseAudioNode(o);
|
||||||
destroyAudioWorkletNode(lfo);
|
releaseAudioNode(lfo);
|
||||||
envGain.disconnect();
|
|
||||||
onended();
|
onended();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@ import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||||
import {
|
import {
|
||||||
applyFM,
|
applyFM,
|
||||||
applyParameterModulators,
|
applyParameterModulators,
|
||||||
destroyAudioWorkletNode,
|
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
getFrequencyFromValue,
|
getFrequencyFromValue,
|
||||||
getParamADSR,
|
getParamADSR,
|
||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
|
releaseAudioNode,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
@ -319,10 +319,9 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
||||||
const timeoutNode = webAudioTimeout(
|
const timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(source);
|
releaseAudioNode(source);
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
node.disconnect();
|
|
||||||
wtPosModulators?.disconnect();
|
wtPosModulators?.disconnect();
|
||||||
wtWarpModulators?.disconnect();
|
wtWarpModulators?.disconnect();
|
||||||
onended();
|
onended();
|
||||||
|
|
|
||||||
|
|
@ -508,13 +508,14 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
process(_input, outputs, params) {
|
process(_input, outputs, params) {
|
||||||
if (currentTime <= params.begin[0]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (currentTime >= params.end[0]) {
|
if (currentTime >= params.end[0]) {
|
||||||
// this.port.postMessage({ type: 'onended' });
|
// should terminate
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (currentTime <= params.begin[0]) {
|
||||||
|
// keep alive
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
const voices = params.voices[0]; // k-rate
|
const voices = params.voices[0]; // k-rate
|
||||||
for (let i = 0; i < output[0].length; i++) {
|
for (let i = 0; i < output[0].length; i++) {
|
||||||
|
|
|
||||||
|
|
@ -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