proper audio node cleanup
+ cleanup onTriggerSample
This commit is contained in:
parent
65e48c05f0
commit
be6b011204
3 changed files with 46 additions and 28 deletions
|
|
@ -150,7 +150,7 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
setSound(key, (t, hapValue) => onTriggerSample(t, hapValue, value), {
|
setSound(key, (t, hapValue, onended) => onTriggerSample(t, hapValue, onended, value), {
|
||||||
type: 'sample',
|
type: 'sample',
|
||||||
samples: value,
|
samples: value,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
|
|
@ -161,7 +161,7 @@ export const samples = async (sampleMap, baseUrl = sampleMap._base || '', option
|
||||||
|
|
||||||
const cutGroups = [];
|
const cutGroups = [];
|
||||||
|
|
||||||
export async function onTriggerSample(t, value, bank) {
|
export async function onTriggerSample(t, value, onended, bank) {
|
||||||
const {
|
const {
|
||||||
s,
|
s,
|
||||||
freq,
|
freq,
|
||||||
|
|
@ -176,24 +176,19 @@ export async function onTriggerSample(t, value, bank) {
|
||||||
begin = 0,
|
begin = 0,
|
||||||
end = 1,
|
end = 1,
|
||||||
} = value;
|
} = value;
|
||||||
const ac = getAudioContext();
|
|
||||||
// destructure adsr here, because the default should be different for synths and samples
|
|
||||||
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
|
|
||||||
// load sample
|
// load sample
|
||||||
if (speed === 0) {
|
if (speed === 0) {
|
||||||
// no playback
|
// no playback
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const ac = getAudioContext();
|
||||||
|
// destructure adsr here, because the default should be different for synths and samples
|
||||||
|
const { attack = 0.001, decay = 0.001, sustain = 1, release = 0.001 } = value;
|
||||||
//const soundfont = getSoundfontKey(s);
|
//const soundfont = getSoundfontKey(s);
|
||||||
let bufferSource;
|
const time = t + nudge;
|
||||||
|
|
||||||
|
const bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank);
|
||||||
|
|
||||||
//if (soundfont) {
|
|
||||||
// is soundfont
|
|
||||||
//bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac, freq);
|
|
||||||
//} else {
|
|
||||||
// is sample from loaded samples(..)
|
|
||||||
bufferSource = await getSampleBufferSource(s, n, note, speed, freq, bank);
|
|
||||||
//}
|
|
||||||
// asny stuff above took too long?
|
// asny stuff above took too long?
|
||||||
if (ac.currentTime > t) {
|
if (ac.currentTime > t) {
|
||||||
logger(`[sampler] still loading sound "${s}:${n}"`, 'highlight');
|
logger(`[sampler] still loading sound "${s}:${n}"`, 'highlight');
|
||||||
|
|
@ -201,7 +196,7 @@ export async function onTriggerSample(t, value, bank) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!bufferSource) {
|
if (!bufferSource) {
|
||||||
console.warn('no buffer source');
|
logger(`[sampler] could not load "${s}:${n}"`, 'error');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bufferSource.playbackRate.value = Math.abs(speed) * bufferSource.playbackRate.value;
|
bufferSource.playbackRate.value = Math.abs(speed) * bufferSource.playbackRate.value;
|
||||||
|
|
@ -212,7 +207,6 @@ export async function onTriggerSample(t, value, bank) {
|
||||||
// "The computation of the offset into the sound is performed using the sound buffer's natural sample rate,
|
// "The computation of the offset into the sound is performed using the sound buffer's natural sample rate,
|
||||||
// rather than the current playback rate, so even if the sound is playing at twice its normal speed,
|
// rather than the current playback rate, so even if the sound is playing at twice its normal speed,
|
||||||
// the midway point through a 10-second audio buffer is still 5."
|
// the midway point through a 10-second audio buffer is still 5."
|
||||||
const time = t + nudge;
|
|
||||||
const offset = begin * bufferSource.buffer.duration;
|
const offset = begin * bufferSource.buffer.duration;
|
||||||
bufferSource.start(time, offset);
|
bufferSource.start(time, offset);
|
||||||
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
const bufferDuration = bufferSource.buffer.duration / bufferSource.playbackRate.value;
|
||||||
|
|
@ -229,19 +223,33 @@ export async function onTriggerSample(t, value, bank) {
|
||||||
}*/
|
}*/
|
||||||
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
|
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
|
||||||
bufferSource.connect(envelope);
|
bufferSource.connect(envelope);
|
||||||
if (cut !== undefined) {
|
bufferSource.onended = function () {
|
||||||
cutGroups[cut]?.stop(time); // fade out?
|
bufferSource.disconnect();
|
||||||
cutGroups[cut] = bufferSource;
|
envelope.disconnect();
|
||||||
}
|
onended();
|
||||||
const stop = (endTime) => {
|
};
|
||||||
|
const stop = (endTime, playWholeBuffer = !clip) => {
|
||||||
let releaseTime = endTime;
|
let releaseTime = endTime;
|
||||||
if (!clip) {
|
if (playWholeBuffer) {
|
||||||
releaseTime = t + (end - begin) * bufferDuration;
|
releaseTime = t + (end - begin) * bufferDuration;
|
||||||
}
|
}
|
||||||
bufferSource.stop(releaseTime + release);
|
bufferSource.stop(releaseTime + release);
|
||||||
releaseEnvelope(releaseTime);
|
releaseEnvelope(releaseTime);
|
||||||
};
|
};
|
||||||
return { node: envelope, stop };
|
const handle = { node: envelope, bufferSource, stop };
|
||||||
|
|
||||||
|
// cut groups
|
||||||
|
// TODO: sometimes, the cutting won't work for very fast triggering...
|
||||||
|
// it worked before :-/
|
||||||
|
if (cut !== undefined) {
|
||||||
|
const prev = cutGroups[cut];
|
||||||
|
if (prev) {
|
||||||
|
prev.stop(time, false);
|
||||||
|
}
|
||||||
|
cutGroups[cut] = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*const getSoundfontKey = (s) => {
|
/*const getSoundfontKey = (s) => {
|
||||||
|
|
@ -262,3 +270,4 @@ export async function onTriggerSample(t, value, bank) {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
};*/
|
};*/
|
||||||
|
// bufferSource = await globalThis.getFontBufferSource(soundfont, note || n, ac, freq);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ export function registerSynthSounds() {
|
||||||
['sine', 'square', 'triangle', 'sawtooth'].forEach((wave) => {
|
['sine', 'square', 'triangle', 'sawtooth'].forEach((wave) => {
|
||||||
setSound(
|
setSound(
|
||||||
wave,
|
wave,
|
||||||
(t, value) => {
|
(t, value, onended) => {
|
||||||
// destructure adsr here, because the default should be different for synths and samples
|
// destructure adsr here, because the default should be different for synths and samples
|
||||||
const { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = value;
|
const { attack = 0.001, decay = 0.05, sustain = 0.6, release = 0.01 } = value;
|
||||||
let { n, note, freq } = value;
|
let { n, note, freq } = value;
|
||||||
|
|
@ -25,6 +25,11 @@ export function registerSynthSounds() {
|
||||||
const g = gainNode(0.3);
|
const g = gainNode(0.3);
|
||||||
// envelope
|
// envelope
|
||||||
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
|
const { node: envelope, stop: releaseEnvelope } = getEnvelope(attack, decay, sustain, release, 1, t);
|
||||||
|
o.onended = () => {
|
||||||
|
o.disconnect();
|
||||||
|
g.disconnect();
|
||||||
|
onended();
|
||||||
|
};
|
||||||
return {
|
return {
|
||||||
node: o.connect(g).connect(envelope),
|
node: o.connect(g).connect(envelope),
|
||||||
stop: (t) => {
|
stop: (t) => {
|
||||||
|
|
|
||||||
|
|
@ -155,8 +155,10 @@ export const webaudioOutput = async (hap, deadline, hapDuration, cps) => {
|
||||||
} = hap.value;
|
} = hap.value;
|
||||||
const { velocity = 1 } = hap.context;
|
const { velocity = 1 } = hap.context;
|
||||||
gain *= velocity; // legacy fix for velocity
|
gain *= velocity; // legacy fix for velocity
|
||||||
// the chain will hold all audio nodes that connect to each other
|
let toDisconnect = []; // audio nodes that will be disconnected when the source has ended
|
||||||
const chain = [];
|
const onended = () => {
|
||||||
|
toDisconnect.forEach((n) => n?.disconnect());
|
||||||
|
};
|
||||||
if (bank && s) {
|
if (bank && s) {
|
||||||
s = `${bank}_${s}`;
|
s = `${bank}_${s}`;
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +168,7 @@ export const webaudioOutput = async (hap, deadline, hapDuration, cps) => {
|
||||||
sourceNode = source(t, hap.value);
|
sourceNode = source(t, hap.value);
|
||||||
} else if (soundMap.get()[s]) {
|
} else if (soundMap.get()[s]) {
|
||||||
const { onTrigger } = soundMap.get()[s];
|
const { onTrigger } = soundMap.get()[s];
|
||||||
const soundHandle = await onTrigger(t, hap.value);
|
const soundHandle = await onTrigger(t, hap.value, onended);
|
||||||
if (soundHandle) {
|
if (soundHandle) {
|
||||||
sourceNode = soundHandle.node;
|
sourceNode = soundHandle.node;
|
||||||
soundHandle.stop(t + hapDuration);
|
soundHandle.stop(t + hapDuration);
|
||||||
|
|
@ -183,6 +185,7 @@ export const webaudioOutput = async (hap, deadline, hapDuration, cps) => {
|
||||||
logger('[webaudio] skip hap: still loading', ac.currentTime - t);
|
logger('[webaudio] skip hap: still loading', ac.currentTime - t);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const chain = []; // audio nodes that will be connected to each other sequentially
|
||||||
chain.push(sourceNode);
|
chain.push(sourceNode);
|
||||||
|
|
||||||
// gain stage
|
// gain stage
|
||||||
|
|
@ -227,8 +230,9 @@ export const webaudioOutput = async (hap, deadline, hapDuration, cps) => {
|
||||||
// connect chain elements together
|
// connect chain elements together
|
||||||
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
||||||
|
|
||||||
// disconnect all nodes when source node has ended:
|
// toDisconnect = all the node that should be disconnected in onended callback
|
||||||
chain[0].onended = () => chain.concat([delaySend, reverbSend]).forEach((n) => n?.disconnect());
|
// this is crucial for performance
|
||||||
|
toDisconnect = chain.concat([delaySend, reverbSend]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const webaudioOutputTrigger = (t, hap, ct, cps) => webaudioOutput(hap, t - ct, hap.duration / cps, cps);
|
export const webaudioOutputTrigger = (t, hap, ct, cps) => webaudioOutput(hap, t - ct, hap.duration / cps, cps);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue