From 8b66002dd090d0c9178fb30db6ee974b6b975552 Mon Sep 17 00:00:00 2001 From: Aria Date: Sat, 22 Nov 2025 22:07:10 -0600 Subject: [PATCH] Attempt to block animation when pattern highlighting disabled --- packages/codemirror/codemirror.mjs | 20 +++++++++++++++----- packages/superdough/synth.mjs | 17 ++++------------- packages/superdough/wavetable.mjs | 7 +++++-- packages/superdough/worklets.mjs | 20 ++++++-------------- 4 files changed, 30 insertions(+), 34 deletions(-) diff --git a/packages/codemirror/codemirror.mjs b/packages/codemirror/codemirror.mjs index e91e6f259..33ea3968a 100644 --- a/packages/codemirror/codemirror.mjs +++ b/packages/codemirror/codemirror.mjs @@ -162,6 +162,7 @@ export class StrudelMirror { this.onDraw = onDraw || this.draw; this.id = id || s4(); this.solo = solo; + this.hasPainters = false; this.drawer = new Drawer((haps, time, _, painters) => { const currentFrame = haps.filter((hap) => hap.isActive(time)); @@ -177,7 +178,7 @@ export class StrudelMirror { onToggle: (started) => { replOptions?.onToggle?.(started); if (started) { - this.drawer.start(this.repl.scheduler); + this.shouldAnimate() && this.drawer.start(this.repl.scheduler); if (this.solo) { // stop other repls when this one is started document.dispatchEvent( @@ -207,9 +208,10 @@ export class StrudelMirror { updateWidgets(this.editor, widgets); updateMiniLocations(this.editor, this.miniLocations); replOptions?.afterEval?.(options); + this.hasPainters = options.pattern.getPainters().length > 0; // if no painters are set (.onPaint was not called), then we only need // the present moment (for highlighting) - const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0]; + const drawTime = this.hasPainters ? this.drawTime : [0, 0]; this.drawer.setDrawTime(drawTime); // invalidate drawer after we've set the appropriate drawTime this.drawer.invalidate(this.repl.scheduler); @@ -327,6 +329,12 @@ export class StrudelMirror { } highlightMiniLocations(this.editor, time, haps); } + shouldAnimate() { + return ( + this.repl.scheduler.started && + (this.isPatternHighlightingEnabled || this.hasPainters || this.onDraw !== this.draw) + ); + } setFontSize(size) { this.root.style.fontSize = size + 'px'; } @@ -343,13 +351,15 @@ export class StrudelMirror { return; } value = parseBooleans(value); - if (key === 'isPatternHighlightingEnabled') { - this.isPatternHighlightingEnabled = value; - } const newValue = extensions[key](value, this); this.editor.dispatch({ effects: compartments[key].reconfigure(newValue), }); + if (key === 'isPatternHighlightingEnabled') { + this.isPatternHighlightingEnabled = value; + this.drawer.stop(); + this.shouldAnimate() && this.drawer.start(this.repl.scheduler); + } if (key === 'theme') { activateTheme(value); } diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index 5d114f3d6..b24e28ee7 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -191,22 +191,13 @@ export function registerSynthSounds() { getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend); const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin); const fm = applyFM(o.parameters.get('frequency'), value, begin); - getParamADSR( - o.parameters.get('postgain'), - attack, - decay, - sustain, - release, - 0, - 0.3 * gainAdjustment, - begin, - holdend, - 'linear', - ); + const envGain = o.connect(gainNode(1)); + getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear'); const timeoutNode = webAudioTimeout( ac, () => { destroyAudioWorkletNode(o); + envGain.disconnect(); releaseVoice('supersaw', o); onended(); fm?.stop(); @@ -217,7 +208,7 @@ export function registerSynthSounds() { ); return { - node: o, + node: envGain, stop: (time) => { timeoutNode.stop(time); }, diff --git a/packages/superdough/wavetable.mjs b/packages/superdough/wavetable.mjs index d3c08e575..b0e86ebe7 100644 --- a/packages/superdough/wavetable.mjs +++ b/packages/superdough/wavetable.mjs @@ -4,6 +4,7 @@ import { applyFM, applyParameterModulators, claimVoice, + gainNode, getADSRValues, getFrequencyFromValue, getParamADSR, @@ -320,9 +321,10 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { ); const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t); const fm = applyFM(source.parameters.get('frequency'), value, t); - getParamADSR(source.parameters.get('postgain'), attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear'); + const envGain = source.connect(gainNode(1)); + getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear'); getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd); - const handle = { node: source }; + const handle = { node: envGain }; const timeoutNode = webAudioTimeout( ac, () => { @@ -330,6 +332,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) { fm?.stop(); wtPosModulators?.disconnect(); wtWarpModulators?.disconnect(); + envGain.disconnect(); destroyAudioWorkletNode(source); releaseVoice(voiceKey, source); onended(); diff --git a/packages/superdough/worklets.mjs b/packages/superdough/worklets.mjs index 09262f94a..781cf03fd 100644 --- a/packages/superdough/worklets.mjs +++ b/packages/superdough/worklets.mjs @@ -43,7 +43,7 @@ const getDetuner = (unison, detune) => { function fastPow2(x) { // Taylor approximation of 2 ^ x const a = x * 0.6931471805599453; // ln(2) - return 1 + a * (1 + a * (0.8333333333333333 + a)); + return 1 + a * (1 + a * (0.5 + (a / 6))); } const applySemitoneDetuneToFrequency = (frequency, detune) => { @@ -517,10 +517,6 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { defaultValue: 0, min: 0, }, - { - name: 'postgain', - defaultValue: 1, - }, ]; } process(_input, outputs, params) { @@ -548,10 +544,9 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor { gainR = Math.sqrt(0.5 + 0.5 * panspread); } for (let i = 0; i < output[0].length; i++) { - const postgain = pv(params.postgain, i); const panspread = pv(params.panspread, i) * 0.5 + 0.5; - gainL = Math.sqrt(1 - panspread) * postgain; - gainR = Math.sqrt(panspread) * postgain; + gainL = Math.sqrt(1 - panspread); + gainR = Math.sqrt(panspread); // Main detuning (a-rate path) detune ??= pv(params.detune, i); freqspread ??= pv(params.freqspread, i); @@ -1154,7 +1149,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { { name: 'warp', defaultValue: 0, min: 0, max: 1 }, { name: 'warpMode', defaultValue: 0 }, { name: 'panspread', defaultValue: 0.7, min: 0, max: 1 }, - { name: 'postgain', defaultValue: 1, min: 0 }, ]; } @@ -1187,7 +1181,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { phases[i] = Math.random() * phaseRand; } this.voices = options.voices; - this.voicesDenom = 1 / Math.sqrt(this.voices); + this.normalizer = 1 / Math.sqrt(this.voices); } _mirror(x) { @@ -1364,8 +1358,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { const interpT = idx - fIdx; const warpAmount = clamp(pv(params.warp, i), 0, 1); const warpMode = pv(params.warpMode, i); - const postgain = pv(params.postgain, i); - const normalizer = postgain * this.voicesDenom; panspread ??= this.voices > 1 ? clamp(pv(params.panspread, i), 0, 1) : 0; gainL ??= Math.sqrt(0.5 - 0.5 * panspread); gainR ??= Math.sqrt(0.5 + 0.5 * panspread); @@ -1384,8 +1376,8 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor { if (warpMode === WarpMode.FLIP && this.phases[n] < warpAmount) { s = -s; } - outL[i] += s * gainL * normalizer; - outR[i] += s * gainR * normalizer; + outL[i] += s * gainL * this.normalizer; + outR[i] += s * gainR * this.normalizer; this.phases[n] = frac(this.phases[n] + dPhase); // invert right and left gain const tmp = gainL;