From 28c94efaa9d036fd361e3cc8708e1465e39a1432 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 24 Sep 2025 00:24:25 -0700 Subject: [PATCH] format --- packages/superdough/superdough.mjs | 2 +- packages/superdough/superdoughoutput.mjs | 356 +++++++++++------------ packages/superdough/worklets.mjs | 38 +-- 3 files changed, 195 insertions(+), 201 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 1d3028e80..63ed7d30b 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -749,7 +749,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) } if (djf != null) { - orbitBus.getDjf(djf, t) + orbitBus.getDjf(djf, t); } // analyser diff --git a/packages/superdough/superdoughoutput.mjs b/packages/superdough/superdoughoutput.mjs index 38760d041..9ef7bcbf3 100644 --- a/packages/superdough/superdoughoutput.mjs +++ b/packages/superdough/superdoughoutput.mjs @@ -5,202 +5,202 @@ import { clamp } from './util.mjs'; let hasChanged = (now, before) => now !== undefined && now !== before; export class Orbit { - reverbNode; - delayNode; - output; - summingNode; - djfNode; - audioContext; - constructor(audioContext) { - this.audioContext = audioContext; - this.output = new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }); - this.summingNode = new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }); - this.summingNode.connect(this.output); + reverbNode; + delayNode; + output; + summingNode; + djfNode; + audioContext; + constructor(audioContext) { + this.audioContext = audioContext; + this.output = new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }); + this.summingNode = new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }); + this.summingNode.connect(this.output); + } + + disconnect() { + this.output.disconnect(); + this.summingNode.disconnect(); + this.delayNode?.disconnect(); + this.reverbNode?.disconnect(); + } + + getDjf(value, t = 0) { + if (this.djfNode == null) { + this.djfNode = getWorklet(this.audioContext, 'djf-processor', { value }); + this.summingNode.disconnect(); + this.summingNode.connect(this.djfNode); + this.djfNode.connect(this.output); + } + const val = this.djfNode.parameters.get('value'); + val.setValueAtTime(value, t); + } + + getDelay(delaytime = 0, feedback = 0.5, t) { + const maxfeedback = 0.98; + if (feedback > maxfeedback) { + //logger(`feedback was clamped to ${maxfeedback} to save your ears`); + } + feedback = clamp(feedback, 0, 0.98); + if (this.delayNode == null) { + this.delayNode = this.audioContext.createFeedbackDelay(1, delaytime, feedback); + this.delayNode.connect(this.summingNode); + this.delayNode.start?.(t); // for some reason, this throws when audion extension is installed.. + } + this.delayNode.delayTime.value !== delaytime && this.delayNode.delayTime.setValueAtTime(delaytime, t); + this.delayNode.feedback.value !== feedback && this.delayNode.feedback.setValueAtTime(feedback, t); + return this.delayNode; + } + + getReverb(duration, fade, lp, dim, ir, irspeed, irbegin) { + // If no reverb has been created for a given orbit, create one + if (this.reverbNode == null) { + this.reverbNode = this.audioContext.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin); + this.reverbNode.connect(this.summingNode); } - disconnect() { - this.output.disconnect(); - this.summingNode.disconnect(); - this.delayNode?.disconnect(); - this.reverbNode?.disconnect(); + if ( + hasChanged(duration, this.reverbNode.duration) || + hasChanged(fade, this.reverbNode.fade) || + hasChanged(lp, this.reverbNode.lp) || + hasChanged(dim, this.reverbNode.dim) || + hasChanged(irspeed, this.reverbNode.irspeed) || + hasChanged(irbegin, this.reverbNode.irbegin) || + this.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 + this.reverbNode.generate(duration, fade, lp, dim, ir, irspeed, irbegin); } + return this.reverbNode; + } + sendReverb(node, amount) { + effectSend(node, this.reverbNode, amount); + } - getDjf(value, t = 0) { - if (this.djfNode == null){ - this.djfNode = getWorklet(this.audioContext, 'djf-processor', {value}) - this.summingNode.disconnect() - this.summingNode.connect(this.djfNode) - this.djfNode.connect(this.output) - } - const val = this.djfNode.parameters.get('value') - val.setValueAtTime(value, t) - } - - getDelay(delaytime = 0, feedback = 0.5, t) { - const maxfeedback = 0.98; - if (feedback > maxfeedback) { - //logger(`feedback was clamped to ${maxfeedback} to save your ears`); - } - feedback = clamp(feedback, 0, 0.98); - if (this.delayNode == null) { - this.delayNode = this.audioContext.createFeedbackDelay(1, delaytime, feedback); - this.delayNode.connect(this.summingNode); - this.delayNode.start?.(t); // for some reason, this throws when audion extension is installed.. - } - this.delayNode.delayTime.value !== delaytime && this.delayNode.delayTime.setValueAtTime(delaytime, t); - this.delayNode.feedback.value !== feedback && this.delayNode.feedback.setValueAtTime(feedback, t); - return this.delayNode; - } + sendDelay(node, amount) { + effectSend(node, this.delayNode, amount); + } - getReverb(duration, fade, lp, dim, ir, irspeed, irbegin) { - // If no reverb has been created for a given orbit, create one - if (this.reverbNode == null) { - this.reverbNode = this.audioContext.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin); - this.reverbNode.connect(this.summingNode); - } + duck(t, onsettime = 0, attacktime = 0.1, depth = 1) { + const onset = onsettime; + const attack = Math.max(attacktime, 0.002); + const gainParam = this.output.gain; + webAudioTimeout( + this.audioContext, + () => { + const now = this.audioContext.currentTime; - if ( - hasChanged(duration, this.reverbNode.duration) || - hasChanged(fade, this.reverbNode.fade) || - hasChanged(lp, this.reverbNode.lp) || - hasChanged(dim, this.reverbNode.dim) || - hasChanged(irspeed, this.reverbNode.irspeed) || - hasChanged(irbegin, this.reverbNode.irbegin) || - this.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 - this.reverbNode.generate(duration, fade, lp, dim, ir, irspeed, irbegin); - } - return this.reverbNode; - } - sendReverb(node, amount) { - effectSend(node, this.reverbNode, amount); - } + // cancelScheduledValues and setValueAtTime together emulate cancelAndHoldAtTime + // on browsers which lack that method + const currVal = gainParam.value; + gainParam.cancelScheduledValues(now); + gainParam.setValueAtTime(currVal, now); - sendDelay(node, amount) { - effectSend(node, this.delayNode, amount); - } + const t0 = Math.max(t, now); // guard against now > t + const duckedVal = clamp(1 - Math.sqrt(depth), 0.01, currVal); + gainParam.exponentialRampToValueAtTime(duckedVal, t0 + onset); + gainParam.exponentialRampToValueAtTime(1, t0 + onset + attack); + }, + 0, + t - 0.01, + ); + } - duck(t, onsettime = 0, attacktime = 0.1, depth = 1) { - const onset = onsettime; - const attack = Math.max(attacktime, 0.002); - const gainParam = this.output.gain; - webAudioTimeout( - this.audioContext, - () => { - const now = this.audioContext.currentTime; - - // cancelScheduledValues and setValueAtTime together emulate cancelAndHoldAtTime - // on browsers which lack that method - const currVal = gainParam.value; - gainParam.cancelScheduledValues(now); - gainParam.setValueAtTime(currVal, now); - - const t0 = Math.max(t, now); // guard against now > t - const duckedVal = clamp(1 - Math.sqrt(depth), 0.01, currVal); - gainParam.exponentialRampToValueAtTime(duckedVal, t0 + onset); - gainParam.exponentialRampToValueAtTime(1, t0 + onset + attack); - }, - 0, - t - 0.01, - ); - } - - connectToOutput(node) { - node.connect(this.summingNode); - } + connectToOutput(node) { + node.connect(this.summingNode); + } } export class SuperdoughOutput { - channelMerger; - destinationGain; + channelMerger; + destinationGain; - constructor(audioContext) { - this.audioContext = audioContext; - this.initializeAudio(); - } + constructor(audioContext) { + this.audioContext = audioContext; + this.initializeAudio(); + } - initializeAudio() { - const audioContext = this.audioContext; - const maxChannelCount = audioContext.destination.maxChannelCount; - this.audioContext.destination.channelCount = maxChannelCount; - this.channelMerger = new ChannelMergerNode(audioContext, { numberOfInputs: audioContext.destination.channelCount }); - this.destinationGain = new GainNode(audioContext); - this.channelMerger.connect(this.destinationGain); - this.destinationGain.connect(audioContext.destination); - } + initializeAudio() { + const audioContext = this.audioContext; + const maxChannelCount = audioContext.destination.maxChannelCount; + this.audioContext.destination.channelCount = maxChannelCount; + this.channelMerger = new ChannelMergerNode(audioContext, { numberOfInputs: audioContext.destination.channelCount }); + this.destinationGain = new GainNode(audioContext); + this.channelMerger.connect(this.destinationGain); + this.destinationGain.connect(audioContext.destination); + } - reset() { - this.channelMerger.disconnect(); - this.destinationGain.disconnect(); - this.destinationGain = null; - this.channelMerger = null; - this.nodes = {}; - this.initializeAudio(); - } - connectToDestination = (input, channels = [0, 1]) => { - //This upmix can be removed if correct channel counts are set throughout the app, - // and then strudel could theoretically support surround sound audio files - const stereoMix = new StereoPannerNode(this.audioContext); - input.connect(stereoMix); + reset() { + this.channelMerger.disconnect(); + this.destinationGain.disconnect(); + this.destinationGain = null; + this.channelMerger = null; + this.nodes = {}; + this.initializeAudio(); + } + connectToDestination = (input, channels = [0, 1]) => { + //This upmix can be removed if correct channel counts are set throughout the app, + // and then strudel could theoretically support surround sound audio files + const stereoMix = new StereoPannerNode(this.audioContext); + input.connect(stereoMix); - const splitter = new ChannelSplitterNode(this.audioContext, { - numberOfOutputs: stereoMix.channelCount, - }); - stereoMix.connect(splitter); - channels.forEach((ch, i) => { - splitter.connect(this.channelMerger, i % stereoMix.channelCount, ch % this.audioContext.destination.channelCount); - }); - }; + const splitter = new ChannelSplitterNode(this.audioContext, { + numberOfOutputs: stereoMix.channelCount, + }); + stereoMix.connect(splitter); + channels.forEach((ch, i) => { + splitter.connect(this.channelMerger, i % stereoMix.channelCount, ch % this.audioContext.destination.channelCount); + }); + }; } export class SuperdoughAudioController { - audioContext; - output; - nodes = {}; + audioContext; + output; + nodes = {}; - constructor(audioContext) { - this.audioContext = audioContext; - this.output = new SuperdoughOutput(audioContext); - } - - reset() { - Array.from(this.nodes).forEach((node) => { - node.disconnect(); - }); - this.output.reset(); - } - - duck(targetOrbits, t, onsettime = 0, attacktime = 0.1, depth = 1) { - const targetArr = [targetOrbits].flat(); - const onsetArr = [onsettime].flat(); - const attackArr = [attacktime].flat(); - const depthArr = [depth].flat(); - - targetArr.forEach((target, idx) => { - const orbit = this.nodes[target]; - - if (orbit == null) { - errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough'); - return; - } - const onset = onsetArr[idx] ?? onsetArr[0]; - const attack = Math.max(attackArr[idx] ?? attackArr[0], 0.002); - const depth = depthArr[idx] ?? depthArr[0]; - - orbit.duck(t, onset, attack, depth); - }); - } - - getOrbit(orbitNum, channels) { - if (this.nodes[orbitNum] == null) { - this.nodes[orbitNum] = new Orbit(this.audioContext); - this.output.connectToDestination(this.nodes[orbitNum].output, channels); - } - return this.nodes[orbitNum]; + constructor(audioContext) { + this.audioContext = audioContext; + this.output = new SuperdoughOutput(audioContext); + } + + reset() { + Array.from(this.nodes).forEach((node) => { + node.disconnect(); + }); + this.output.reset(); + } + + duck(targetOrbits, t, onsettime = 0, attacktime = 0.1, depth = 1) { + const targetArr = [targetOrbits].flat(); + const onsetArr = [onsettime].flat(); + const attackArr = [attacktime].flat(); + const depthArr = [depth].flat(); + + targetArr.forEach((target, idx) => { + const orbit = this.nodes[target]; + + if (orbit == null) { + errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough'); + return; + } + const onset = onsetArr[idx] ?? onsetArr[0]; + const attack = Math.max(attackArr[idx] ?? attackArr[0], 0.002); + const depth = depthArr[idx] ?? depthArr[0]; + + orbit.duck(t, onset, attack, depth); + }); + } + + getOrbit(orbitNum, channels) { + if (this.nodes[orbitNum] == null) { + this.nodes[orbitNum] = new Orbit(this.audioContext); + this.output.connectToDestination(this.nodes[orbitNum].output, channels); } + return this.nodes[orbitNum]; + } } diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 06e81f38c..4d25617fb 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -276,8 +276,8 @@ class TwoPoleFilter { s1 = 0; update(s, cutoff, resonance = 0) { // Out of bound values can produce NaNs - resonance = clamp(resonance, 0, 1) - cutoff = clamp(cutoff, 0, sampleRate / 2 - 1) + resonance = clamp(resonance, 0, 1); + cutoff = clamp(cutoff, 0, sampleRate / 2 - 1); const c = clamp(2 * Math.sin(cutoff * (_PI / sampleRate)), 0, 1.14); const r = Math.pow(0.5, (resonance + 0.125) / 0.125); const mrc = 1 - r * c; @@ -289,14 +289,12 @@ class TwoPoleFilter { class DJFProcessor extends AudioWorkletProcessor { static get parameterDescriptors() { - return [ - { name: 'value', defaultValue: 0.5 }, - ]; + return [{ name: 'value', defaultValue: 0.5 }]; } constructor() { super(); - this.filters = [new TwoPoleFilter(), new TwoPoleFilter()] + this.filters = [new TwoPoleFilter(), new TwoPoleFilter()]; } process(inputs, outputs, parameters) { @@ -307,38 +305,34 @@ class DJFProcessor extends AudioWorkletProcessor { this.started = hasInput; const value = clamp(parameters.value[0], 0, 1); - let filterType = 'none' - let cutoff + let filterType = 'none'; + let cutoff; let v = 1; if (value > 0.5) { - filterType = 'hipass' - v = (value - .5) * 2 + filterType = 'hipass'; + v = (value - 0.5) * 2; } else if (value < 0.5) { - filterType = 'lopass' - v = value * 2 + filterType = 'lopass'; + v = value * 2; } - cutoff = Math.pow((v * 11), 4) + cutoff = Math.pow(v * 11, 4); // let cutoff = parameters.frequency[0]; for (let i = 0; i < input.length; i++) { for (let n = 0; n < blockSize; n++) { if (filterType == 'none') { - output[i][n] = input[i][n] + output[i][n] = input[i][n]; } else { - this.filters[i].update(input[i][n], cutoff, 0.2) + this.filters[i].update(input[i][n], cutoff, 0.2); if (filterType === 'lopass') { - output[i][n] = this.filters[i].s1 + output[i][n] = this.filters[i].s1; } else if (filterType === 'hipass') { - output[i][n] = input[i][n] - this.filters[i].s1 + output[i][n] = input[i][n] - this.filters[i].s1; } else { - output[i][n] = input[i][n] + output[i][n] = input[i][n]; } } - - - - } } return true;