diff --git a/packages/webaudio/feedbackdelay.mjs b/packages/webaudio/feedbackdelay.mjs index 1b9ae4e7..c182d655 100644 --- a/packages/webaudio/feedbackdelay.mjs +++ b/packages/webaudio/feedbackdelay.mjs @@ -7,6 +7,7 @@ if (typeof DelayNode !== 'undefined') { const feedbackGain = ac.createGain(); feedbackGain.gain.value = Math.min(Math.abs(feedback), 0.995); + this.feedback = feedbackGain.gain; const delayGain = ac.createGain(); delayGain.gain.value = wet; diff --git a/packages/webaudio/webaudio.mjs b/packages/webaudio/webaudio.mjs index fb863433..89b3233e 100644 --- a/packages/webaudio/webaudio.mjs +++ b/packages/webaudio/webaudio.mjs @@ -189,6 +189,27 @@ function gainNode(value) { } const cutGroups = []; +let delays = {}; +function getDelay(orbit, delay, delaytime, delayfeedback, t) { + if (!delays[orbit]) { + const ac = getAudioContext(); + const dly = ac.createFeedbackDelay(delay, delaytime, delayfeedback); + dly.start(t); + dly.connect(getDestination()); + delays[orbit] = dly; + } + delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t); + delays[orbit].feedback.value !== delayfeedback && delays[orbit].feedback.setValueAtTime(delayfeedback, t); + return delays[orbit]; +} + +function effectSend(input, effect, wet) { + const send = gainNode(wet); + input.connect(send); + send.connect(effect); + return send; +} + // export const webaudioOutput = async (t, hap, ct, cps) => { export const webaudioOutput = async (hap, deadline, hapDuration) => { try { @@ -228,12 +249,13 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => { end = 1, vowel, delay = 0, - delayfeedback = 0, - delaytime = 0, + delayfeedback = 0.5, + delaytime = 0.25, unit, nudge = 0, // TODO: is this in seconds? cut, loop, + orbit = 1, } = hap.value; const { velocity = 1 } = hap.context; gain *= velocity; // legacy fix for velocity @@ -327,42 +349,45 @@ export const webaudioOutput = async (hap, deadline, hapDuration) => { const adsr = getADSR(attack, decay, sustain, release, 1, t, t + duration); chain.push(adsr); } - // master out - const master = ac.createGain(); - master.gain.value = gain; - chain.push(master); + + // gain stage + chain.push(gainNode(gain)); // filters cutoff !== undefined && chain.push(getFilter('lowpass', cutoff, resonance)); hcutoff !== undefined && chain.push(getFilter('highpass', hcutoff, hresonance)); bandf !== undefined && chain.push(getFilter('bandpass', bandf, bandq)); vowel !== undefined && chain.push(ac.createVowelFilter(vowel)); + + // effects coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse })); crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush })); shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape })); - // TODO delay / delaytime / delayfeedback + // panning if (pan !== undefined) { const panner = ac.createStereoPanner(); panner.pan.value = 2 * pan - 1; chain.push(panner); } + // last gain const post = gainNode(1); chain.push(post); - post.connect(getDestination()); + + // delay + let delaySend; if (delay > 0 && delaytime > 0 && delayfeedback > 0) { - const dly = ac.createFeedbackDelay(delay, delaytime, delayfeedback); - dly.start(t); - post.connect(dly); - dly.connect(getDestination()); + const delyNode = getDelay(orbit, 1, delaytime, delayfeedback, t); + delaySend = effectSend(post, delyNode, delay); } // connect chain elements together chain.slice(1).reduce((last, current) => last.connect(current), chain[0]); + // disconnect all nodes when source node has ended: - chain[0].onended = () => chain.forEach((n) => n.disconnect()); + chain[0].onended = () => chain.concat([delaySend]).forEach((n) => n?.disconnect()); } catch (e) { console.warn('.out error:', e); }