Add release parameter, avoid clicks, some cleanup
This commit is contained in:
parent
e5fe998327
commit
4f15d681b0
2 changed files with 87 additions and 53 deletions
|
|
@ -331,18 +331,19 @@ function getDelay(orbit, delaytime, delayfeedback, t, channels) {
|
|||
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
|
||||
}
|
||||
delayfeedback = clamp(delayfeedback, 0, 0.98);
|
||||
if (!orbits[orbit].delayNode) {
|
||||
let delayNode = orbits[orbit].delayNode;
|
||||
if (delayNode === undefined) {
|
||||
const ac = getAudioContext();
|
||||
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
||||
dly.start?.(t); // for some reason, this throws when audion extension is installed..
|
||||
connectToOrbit(dly, orbit);
|
||||
orbits[orbit].delayNode = dly;
|
||||
delayNode = dly;
|
||||
}
|
||||
orbits[orbit].delayNode.delayTime.value !== delaytime &&
|
||||
orbits[orbit].delayNode.delayTime.setValueAtTime(delaytime, t);
|
||||
orbits[orbit].delayNode.feedback.value !== delayfeedback &&
|
||||
orbits[orbit].delayNode.feedback.setValueAtTime(delayfeedback, t);
|
||||
return orbits[orbit].delayNode;
|
||||
delayNode.delayTime.value !== delaytime &&
|
||||
delayNode.delayTime.setValueAtTime(delaytime, t);
|
||||
delayNode.feedback.value !== delayfeedback &&
|
||||
delayNode.feedback.setValueAtTime(delayfeedback, t);
|
||||
return delayNode;
|
||||
}
|
||||
|
||||
export function getLfo(audioContext, begin, end, properties = {}) {
|
||||
|
|
@ -398,42 +399,59 @@ function getFilterType(ftype) {
|
|||
return typeof ftype === 'number' ? filterTypes[Math.floor(_mod(ftype, filterTypes.length))] : ftype;
|
||||
}
|
||||
|
||||
//type orbit {
|
||||
// gain: number,
|
||||
// reverbNode: reverbNode
|
||||
// delayNode: delayNode
|
||||
//}
|
||||
// type orbit {
|
||||
// output: GainNode,
|
||||
// reverbNode: ConvolverNode
|
||||
// delayNode: FeedbackDelayNode
|
||||
// }
|
||||
let orbits = {};
|
||||
function connectToOrbit(node, orbit) {
|
||||
if (orbits[orbit] == null) {
|
||||
errorLogger(new Error('target orbit does not exist'), 'superdough');
|
||||
}
|
||||
node.connect(orbits[orbit].gain);
|
||||
node.connect(orbits[orbit].output);
|
||||
}
|
||||
|
||||
function setOrbit(audioContext, orbit, channels) {
|
||||
if (orbits[orbit] == null) {
|
||||
orbits[orbit] = {
|
||||
gain: new GainNode(audioContext, { gain: 1 }),
|
||||
// Setup output node through which all audio filters prior to hitting
|
||||
// the destination (and thus allows for global volume automation)
|
||||
output: new GainNode(audioContext, { gain: 1 }),
|
||||
};
|
||||
connectToDestination(orbits[orbit].gain, channels);
|
||||
connectToDestination(orbits[orbit].output, channels);
|
||||
}
|
||||
}
|
||||
function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.1, duckdepth = 1) {
|
||||
const targetArr = [targetOrbit].flat();
|
||||
|
||||
targetArr.forEach((target) => {
|
||||
function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.003, releasetime = 0.1, duckdepth = 1) {
|
||||
const targetArr = [targetOrbit].flat();
|
||||
const attackArr = [attacktime].flat();
|
||||
const releaseArr = [releasetime].flat();
|
||||
const depthArr = [duckdepth].flat();
|
||||
|
||||
targetArr.forEach((target, idx) => {
|
||||
if (orbits[target] == null) {
|
||||
errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough');
|
||||
return;
|
||||
}
|
||||
const attack = attackArr[idx] ?? attackArr[0];
|
||||
const release = Math.max(releaseArr[idx] ?? releaseArr[0], 0.002);
|
||||
const depth = depthArr[idx] ?? depthArr[0];
|
||||
const gainParam = orbits[target].output.gain;
|
||||
webAudioTimeout(
|
||||
audioContext,
|
||||
() => {
|
||||
orbits[target].gain.gain.cancelScheduledValues(t);
|
||||
const currVal = orbits[target].gain.gain.value;
|
||||
orbits[target].gain.gain.linearRampToValueAtTime(clamp(1 - Math.pow(duckdepth, 0.5), 0.01, currVal), t);
|
||||
orbits[target].gain.gain.exponentialRampToValueAtTime(1, t + Math.max(0.002, attacktime));
|
||||
gainParam.cancelScheduledValues(t);
|
||||
const currVal = gainParam.value;
|
||||
const duckedVal = clamp(1 - Math.sqrt(depth), 0.01, currVal);
|
||||
|
||||
// Guarantees the value is set to currVal at time t. This in conjunction with
|
||||
// cancelScheduledValues above emulates cancelAndHoldAtTime on browsers which lack
|
||||
// that method
|
||||
gainParam.setValueAtTime(currVal, t);
|
||||
|
||||
gainParam.exponentialRampToValueAtTime(duckedVal, t + attack);
|
||||
gainParam.exponentialRampToValueAtTime(1, t + attack + release);
|
||||
},
|
||||
0,
|
||||
t - 0.01,
|
||||
|
|
@ -444,27 +462,28 @@ function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.1, duckdepth = 1
|
|||
let hasChanged = (now, before) => now !== undefined && now !== before;
|
||||
function getReverb(orbit, duration, fade, lp, dim, ir) {
|
||||
// If no reverb has been created for a given orbit, create one
|
||||
if (!orbits[orbit].reverbNode) {
|
||||
let reverbNode = orbits[orbit].reverbNode;
|
||||
if (reverbNode === undefined) {
|
||||
const ac = getAudioContext();
|
||||
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
||||
connectToOrbit(reverb, orbit);
|
||||
orbits[orbit].reverbNode = reverb;
|
||||
reverbNode = reverb;
|
||||
}
|
||||
if (
|
||||
hasChanged(duration, orbits[orbit].reverbNode.duration) ||
|
||||
hasChanged(fade, orbits[orbit].reverbNode.fade) ||
|
||||
hasChanged(lp, orbits[orbit].reverbNode.lp) ||
|
||||
hasChanged(dim, orbits[orbit].reverbNode.dim) ||
|
||||
orbits[orbit].reverbNode.ir !== ir
|
||||
hasChanged(duration, reverbNode.duration) ||
|
||||
hasChanged(fade, reverbNode.fade) ||
|
||||
hasChanged(lp, reverbNode.lp) ||
|
||||
hasChanged(dim, reverbNode.dim) ||
|
||||
reverbNode.ir !== ir
|
||||
) {
|
||||
// only regenerate when something has changed
|
||||
// avoids endless regeneration on things like
|
||||
// stack(s("a"), s("b").rsize(8)).room(.5)
|
||||
// this only works when args may stay undefined until here
|
||||
// setting default values breaks this
|
||||
orbits[orbit].reverbNode.generate(duration, fade, lp, dim, ir);
|
||||
reverbNode.generate(duration, fade, lp, dim, ir);
|
||||
}
|
||||
return orbits[orbit].reverbNode;
|
||||
return reverbNode;
|
||||
}
|
||||
|
||||
export let analysers = {},
|
||||
|
|
@ -562,6 +581,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||
density = getDefaultValue('density'),
|
||||
duckorbit,
|
||||
duckattack,
|
||||
duckrelease,
|
||||
duckdepth,
|
||||
// filters
|
||||
fanchor = getDefaultValue('fanchor'),
|
||||
|
|
@ -640,7 +660,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||
setOrbit(ac, orbit, channels, t, cycle, cps);
|
||||
|
||||
if (duckorbit != null) {
|
||||
duckOrbit(ac, duckorbit, t, duckattack, duckdepth);
|
||||
duckOrbit(ac, duckorbit, t, duckattack, duckrelease, duckdepth);
|
||||
}
|
||||
|
||||
gain = applyGainCurve(nanFallback(gain, 1));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue