Revert to old stop format for compatibility

This commit is contained in:
Aria
2025-11-18 13:52:22 -06:00
parent c828adce03
commit 89247d92db
7 changed files with 109 additions and 85 deletions
+6 -3
View File
@@ -144,7 +144,7 @@ export function registerSoundfonts() {
Object.entries(gm).forEach(([name, fonts]) => {
registerSound(
name,
async (time, value) => {
async (time, value, onended) => {
const [attack, decay, sustain, release] = getADSRValues([
value.attack,
value.decay,
@@ -166,10 +166,13 @@ export function registerSoundfonts() {
let vibratoOscillator = getVibratoOscillator(bufferSource.detune, value, time);
// pitch envelope
getPitchEnvelope(bufferSource.detune, value, time, holdEnd);
const cleanup = () => {
bufferSource.onended = () => {
cleanupNodes([bufferSource, vibratoOscillator, node]);
onended();
};
return { node, cleanup };
const envEnd = holdEnd + release + 0.01;
bufferSource.stop(envEnd);
return { node, stop: bufferSource.stop };
},
{ type: 'soundfont', prebake: true, fonts },
);
+6 -8
View File
@@ -518,15 +518,13 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
return Number(freq);
};
export const cleanupNode = (node) => {
if (node == null) {
return;
}
node.disconnect();
export const cleanupNode = (node, time) => {
if (node == null) return;
node.disconnect?.();
node.parameters?.get('end')?.setValueAtTime(0, 0);
node.stop?.();
node.stop?.(time);
};
export const cleanupNodes = (nodes) => {
nodes.map(cleanupNodes);
export const cleanupNodes = (nodes, time) => {
nodes.map((n) => cleanupNode(n, time));
};
+3 -2
View File
@@ -319,10 +319,11 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
node.connect(out);
const cleanup = () => {
const stop = () => {
cleanupNodes([bufferSource, vibratoOscillator, node]);
onended();
};
const handle = { node: out, cleanup };
const handle = { node: out, stop };
// cut groups
if (cut !== undefined) {
+14 -33
View File
@@ -10,6 +10,8 @@ import './vowel.mjs';
import { _mod, clamp, cycleToSeconds, pickAndRename } from './util.mjs';
import workletsUrl from './worklets.mjs?audioworklet';
import {
cleanupNode,
cleanupNodes,
createFilter,
gainNode,
getCompressor,
@@ -152,7 +154,7 @@ export const getAudioDevices = async () => {
let defaultDefaultValues = {
s: 'triangle',
gain: 1,
gain: 0.8,
postgain: 1,
density: '.03',
channels: [1, 2],
@@ -169,6 +171,7 @@ let defaultDefaultValues = {
velocity: 1,
fft: 8,
tremolodepth: 1,
tremolophase: 0,
drive: 0.69,
};
@@ -484,15 +487,15 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
}
// get source AudioNode
let sourceNode, cleanup;
let sourceNode, stop;
if (source) {
sourceNode = source(t, value, hapDuration, cps);
} else if (getSound(s)) {
const { onTrigger } = getSound(s);
const soundHandle = await onTrigger(t, value, () => void 0, cps);
const soundHandle = await onTrigger(t, value, () => {}, cps);
if (soundHandle) {
sourceNode = soundHandle.node;
cleanup = soundHandle.cleanup;
stop = soundHandle.stop;
activeSoundSources.set(chainID, new WeakRef(soundHandle)); // allow GC
}
} else {
@@ -509,7 +512,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
return;
}
const chain = []; // audio nodes that will be connected to each other sequentially
chain.push({ input: sourceNode, output: sourceNode, cleanup });
chain.push({ input: sourceNode, output: sourceNode, stop });
FX = [...FX, value]; // run through the FX chain and then run through all FX outside of it as well
for (const fx of FX) {
let {
@@ -684,18 +687,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
}
// phaser
if (fx.phaserrate !== undefined && phaserdepth > 0) {
const dry = gainNode(1);
const phaserFX = getPhaser(t, endWithRelease, fx.phaserrate, phaserdepth, fx.phasercenter, fx.phasersweep);
const fb = gainNode(fx.phaserFeedback ?? 0.4);
const mix = gainNode(0.5);
dry.connect(mix);
dry.connect(phaserFX).connect(mix);
mix.connect(fb).connect(phaserFX);
cleanup = () => {
phaserFX.disconnect();
dry.disconnect();
};
chain.push({ input: dry, output: mix, cleanup });
chain.push(phaserFX);
}
// delay
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
@@ -707,13 +700,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
const sum = new GainNode(ac, { gain: 1, channelCount: 2, channelCountMode: 'explicit' });
dry.connect(dryDelay).connect(sum);
dry.connect(delayNode).connect(wetDelay).connect(sum);
const cleanup = () => {
dryDelay.disconnect();
delayNode.disconnect();
dry.disconnect();
wetDelay.disconnect();
};
chain.push({ input: dry, output: sum, cleanup });
const stop = () => cleanupNodes([dryDelay, delayNode, dry, wetDelay]);
chain.push({ input: dry, output: sum, stop });
}
// reverb
if (fx.room > 0) {
@@ -743,13 +731,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
const sum = new GainNode(ac, { gain: 1, channelCount: 2, channelCountMode: 'explicit' });
dry.connect(dryReverb).connect(sum);
dry.connect(reverbNode).connect(wetReverb).connect(sum);
const cleanup = () => {
dryReverb.disconnect();
reverbNode.disconnect();
dry.disconnect();
wetReverb.disconnect();
};
chain.push({ input: dry, output: sum, cleanup });
const stop = () => cleanupNodes([dryReverb, reverbNode, dry, wetReverb]);
chain.push({ input: dry, output: sum, stop });
}
}
@@ -818,9 +801,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
ac,
() => {
audioNodes.forEach((n) => {
const node = n?.output ?? n;
node?.disconnect();
n?.cleanup?.();
cleanupNode(n?.output ?? n, endWithRelease + 0.01);
});
activeSoundSources.delete(chainID);
},
+61 -29
View File
@@ -13,6 +13,7 @@ import {
getVibratoOscillator,
getWorklet,
noises,
webAudioTimeout,
} from './helpers.mjs';
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
@@ -39,24 +40,27 @@ export function registerSynthSounds() {
[...waveforms].forEach((s) => {
registerSound(
s,
(t, value) => {
(t, value, onended) => {
const [attack, decay, sustain, release] = getADSRValues(
[value.attack, value.decay, value.sustain, value.release],
'linear',
[0.001, 0.05, 0.6, 0.01],
);
const { node: o } = getOscillator(s, t, value);
const { node: o, stop } = getOscillator(s, t, value);
// turn down
const g = gainNode(0.3);
const cleanup = () => {
o.onended = () => {
cleanupNodes([o, g]);
onended();
};
const node = o.connect(g).connect(gainNode(1));
const holdEnd = t + value.duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
const envEnd = holdEnd + release + 0.01;
stop(envEnd);
return {
node,
cleanup,
stop,
};
},
{ type: 'synth', prebake: true },
@@ -65,7 +69,7 @@ export function registerSynthSounds() {
registerSound(
'sbd',
(t, value) => {
(t, value, onended) => {
const { duration, decay = 0.5, pdecay = 0.5, penv = 36, clip } = value;
const ctx = getAudioContext();
const attackhold = 0.02;
@@ -95,8 +99,9 @@ export function registerSynthSounds() {
const mix = gainNode(mixGain);
const cleanup = () => {
o.onended = () => {
cleanupNodes([o, g, sat, noise.node, noiseGain]);
onended();
};
const node = o.connect(sat).connect(g).connect(mix);
@@ -112,9 +117,11 @@ export function registerSynthSounds() {
mix.gain.setValueAtTime(mixGain, end - 0.01);
mix.gain.linearRampToValueAtTime(0, end);
o.stop(end);
return {
node,
cleanup,
stop: o.stop,
};
},
{ type: 'synth', prebake: true },
@@ -122,7 +129,7 @@ export function registerSynthSounds() {
registerSound(
'supersaw',
(begin, value) => {
(begin, value, onended) => {
const ac = getAudioContext();
let { duration, n, unison = 5, spread = 0.6, detune } = value;
detune = detune ?? n ?? 0.18;
@@ -162,12 +169,18 @@ export function registerSynthSounds() {
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear');
const cleanup = () => {
cleanupNodes([o, fm, vibratoOscillator]);
};
const timeoutNode = webAudioTimeout(
ac,
() => {
cleanupNodes([o, fm, vibratoOscillator]);
onended();
},
begin,
end,
);
return {
node,
cleanup,
stop: timeoutNode.stop,
};
},
{ prebake: true, type: 'synth' },
@@ -175,7 +188,7 @@ export function registerSynthSounds() {
registerSound(
'bytebeat',
(begin, value) => {
(begin, value, onended) => {
const defaultBeats = [
'(t%255 >= t/255%255)*255',
'(t*(t*8%60 <= 300)|(-t)*(t*4%512 < 256))+t/400',
@@ -223,14 +236,18 @@ export function registerSynthSounds() {
o.port.postMessage({ codeText: byteBeatExpression, byteBeatStartTime, frequency });
const node = o.connect(gainNode(1));
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
const cleanup = () => {
cleanupNodes([node, o]);
};
const timeoutNode = webAudioTimeout(
ac,
() => {
cleanupNodes([node, o]);
onended();
},
begin,
end,
);
return {
node,
cleanup,
stop: timeoutNode.stop,
};
},
{ prebake: true, type: 'synth' },
@@ -238,7 +255,7 @@ export function registerSynthSounds() {
registerSound(
'pulse',
(begin, value) => {
(begin, value, onended) => {
const ac = getAudioContext();
let { pwrate, pwsweep } = value;
if (pwsweep == null) {
@@ -288,12 +305,18 @@ export function registerSynthSounds() {
lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep });
lfo.connect(o.parameters.get('pulsewidth'));
}
const cleanup = () => {
cleanupNodes([o, lfo, fm, vibratoOscillator]);
};
const timeoutNode = webAudioTimeout(
ac,
() => {
cleanupNodes([node, o, lfo, fm, vibratoOscillator]);
onended();
},
begin,
end,
);
return {
node,
cleanup,
stop: timeoutNode.stop,
};
},
{ prebake: true, type: 'synth' },
@@ -302,7 +325,7 @@ export function registerSynthSounds() {
[...noises].forEach((s) => {
registerSound(
s,
(t, value) => {
(t, value, onended) => {
const [attack, decay, sustain, release] = getADSRValues(
[value.attack, value.decay, value.sustain, value.release],
'linear',
@@ -314,7 +337,7 @@ export function registerSynthSounds() {
let { density } = value;
sound = getNoiseOscillator(s, t, density);
let { node: o } = sound;
let { node: o, stop } = sound;
// turn down
const g = gainNode(0.3);
@@ -325,12 +348,15 @@ export function registerSynthSounds() {
let node = o.connect(g).connect(envGain);
const holdEnd = t + duration;
getParamADSR(node.gain, attack, decay, sustain, release, 0, 1, t, holdEnd, 'linear');
const cleanup = () => {
o.onended = () => {
cleanupNodes([node, o, g]);
onended();
};
const envEnd = holdEnd + release + 0.01;
stop(envEnd);
return {
node,
cleanup,
stop,
};
},
{ type: 'synth', prebake: true },
@@ -397,8 +423,14 @@ export function getOscillator(s, t, value) {
if (noise) {
noiseMix = getNoiseMix(o, noise, t);
}
const stop = (time) => {
fmModulator.stop(time);
vibratoOscillator?.stop(time);
noiseMix?.stop(time);
o.stop(time);
};
return {
node: noiseMix?.node || o,
stop,
};
}
+14 -6
View File
@@ -10,6 +10,7 @@ import {
getPitchEnvelope,
getVibratoOscillator,
getWorklet,
webAudioTimeout,
} from './helpers.mjs';
import { logger } from './logger.mjs';
@@ -165,8 +166,8 @@ const _processTables = (json, baseUrl, frameLen, options = {}) => {
export function registerWaveTable(key, tables, params) {
registerSound(
key,
(t, hapValue, cps) => {
return onTriggerSynth(t, hapValue, tables, cps, params?.frameLen ?? 2048);
(t, hapValue, onended, cps) => {
return onTriggerSynth(t, hapValue, onended, tables, cps, params?.frameLen ?? 2048);
},
{
type: 'wavetable',
@@ -206,7 +207,7 @@ export const tables = async (url, frameLen, json, options = {}) => {
});
};
export async function onTriggerSynth(t, value, tables, cps, frameLen) {
export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
const { s, n = 0, duration, clip } = value;
const ac = getAudioContext();
const [attack, decay, sustain, release] = getADSRValues([value.attack, value.decay, value.sustain, value.release]);
@@ -314,8 +315,15 @@ export async function onTriggerSynth(t, value, tables, cps, frameLen) {
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
const handle = { node, source };
handle.cleanup = () => {
cleanupNodes([source, vibratoOscillator, fm, node, wtPosModulators, wtWarpModulators]);
};
const timeoutNode = webAudioTimeout(
ac,
() => {
cleanupNodes([source, vibratoOscillator, fm, node, wtPosModulators, wtWarpModulators]);
onended();
},
t,
envEnd,
);
handle.stop = timeoutNode.stop;
return handle;
}
+5 -4
View File
@@ -73,9 +73,6 @@ export const getZZFX = (value, t) => {
const source = getAudioContext().createBufferSource();
source.buffer = buffer;
source.start(t);
const cleanup = () => {
cleanupNode(source);
};
return {
node: source,
};
@@ -85,8 +82,12 @@ export function registerZZFXSounds() {
['zzfx', 'z_sine', 'z_sawtooth', 'z_triangle', 'z_square', 'z_tan', 'z_noise'].forEach((wave) => {
registerSound(
wave,
(t, value) => {
(t, value, onended) => {
const { node: o } = getZZFX({ s: wave, ...value }, t);
o.onended = () => {
cleanupNode(o);
onended();
};
return {
node: o,
stop: () => {},