From 56d3436fdaf14cd6c2bd3615db42e08d77e8f9fc Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 7 May 2025 01:08:00 -0400 Subject: [PATCH 1/4] asconst --- packages/superdough/superdough.mjs | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index a1dc30b7..214b4d11 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -22,6 +22,12 @@ let maxPolyphony = DEFAULT_MAX_POLYPHONY; export function setMaxPolyphony(polyphony) { maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY; } + +let multiChannelOrbits = false +export function setMultiChannelOrbits(bool) { + multiChannelOrbits = (bool == true); +} + export const soundMap = map(); export function registerSound(key, onTrigger, data = {}) { @@ -195,7 +201,7 @@ function loadWorklets() { // this function should be called on first user interaction (to avoid console warning) export async function initAudio(options = {}) { - const { disableWorklets = false, maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME } = options; + const { disableWorklets = false, maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME, multiChannelOrbits = false } = options; setMaxPolyphony(maxPolyphony); if (typeof window === 'undefined') { return; @@ -277,7 +283,7 @@ export const connectToDestination = (input, channels = [0, 1]) => { }); stereoMix.connect(splitter); channels.forEach((ch, i) => { - splitter.connect(channelMerger, i % stereoMix.channelCount, clamp(ch, 0, ctx.destination.channelCount - 1)); + splitter.connect(channelMerger, i % stereoMix.channelCount, ch % ctx.destination.channelCount); }); }; @@ -290,7 +296,7 @@ export const panic = () => { channelMerger == null; }; -function getDelay(orbit, delaytime, delayfeedback, t) { +function getDelay(orbit, delaytime, delayfeedback, t, channels) { if (delayfeedback > maxfeedback) { //logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`); } @@ -299,7 +305,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) { const ac = getAudioContext(); const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback); dly.start?.(t); // for some reason, this throws when audion extension is installed.. - connectToDestination(dly, [0, 1]); + connectToDestination(dly, channels); delays[orbit] = dly; } delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t); @@ -356,12 +362,12 @@ function getFilterType(ftype) { let reverbs = {}; let hasChanged = (now, before) => now !== undefined && now !== before; -function getReverb(orbit, duration, fade, lp, dim, ir) { +function getReverb(orbit, duration, fade, lp, dim, ir, channels) { // If no reverb has been created for a given orbit, create one if (!reverbs[orbit]) { const ac = getAudioContext(); const reverb = ac.createReverb(duration, fade, lp, dim, ir); - connectToDestination(reverb, [0, 1]); + connectToDestination(reverb, channels); reverbs[orbit] = reverb; } if ( @@ -490,7 +496,7 @@ export const superdough = async (value, t, hapDuration) => { bpsustain, bprelease, bandq = getDefaultValue('bandq'), - channels = getDefaultValue('channels'), + //phaser phaserrate: phaser, phaserdepth = getDefaultValue('phaserdepth'), @@ -526,6 +532,9 @@ export const superdough = async (value, t, hapDuration) => { compressorRelease, } = value; + const orbitChannels = multiChannelOrbits ? [(orbit * 2) - 1, orbit * 2] : getDefaultValue('channels') + const channels = value.channels ?? orbitChannels; + gain = applyGainCurve(nanFallback(gain, 1)); postgain = applyGainCurve(postgain); shapevol = applyGainCurve(shapevol); @@ -699,7 +708,7 @@ export const superdough = async (value, t, hapDuration) => { // delay let delaySend; if (delay > 0 && delaytime > 0 && delayfeedback > 0) { - const delyNode = getDelay(orbit, delaytime, delayfeedback, t); + const delyNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels); delaySend = effectSend(post, delyNode, delay); audioNodes.push(delaySend); } @@ -717,7 +726,7 @@ export const superdough = async (value, t, hapDuration) => { } roomIR = await loadBuffer(url, ac, ir, 0); } - const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR); + const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, orbitChannels); reverbSend = effectSend(post, reverbNode, room); audioNodes.push(reverbSend); } From 59bdfd2cb8f360c6c5cc5e438f4576e6c3b0975e Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 7 May 2025 11:12:05 -0400 Subject: [PATCH 2/4] format --- packages/superdough/superdough.mjs | 28 +++++++++++++------ .../src/repl/components/panel/SettingsTab.jsx | 14 +++++++++- website/src/repl/useReplContext.jsx | 10 +++++-- website/src/settings.mjs | 4 ++- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 214b4d11..90f0d369 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -23,9 +23,9 @@ export function setMaxPolyphony(polyphony) { maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY; } -let multiChannelOrbits = false +let multiChannelOrbits = false; export function setMultiChannelOrbits(bool) { - multiChannelOrbits = (bool == true); + multiChannelOrbits = bool == true; } export const soundMap = map(); @@ -201,8 +201,15 @@ function loadWorklets() { // this function should be called on first user interaction (to avoid console warning) export async function initAudio(options = {}) { - const { disableWorklets = false, maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME, multiChannelOrbits = false } = options; + const { + disableWorklets = false, + maxPolyphony, + audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME, + multiChannelOrbits = false, + } = options; + setMaxPolyphony(maxPolyphony); + setMultiChannelOrbits(multiChannelOrbits); if (typeof window === 'undefined') { return; } @@ -496,7 +503,7 @@ export const superdough = async (value, t, hapDuration) => { bpsustain, bprelease, bandq = getDefaultValue('bandq'), - + //phaser phaserrate: phaser, phaserdepth = getDefaultValue('phaserdepth'), @@ -532,8 +539,14 @@ export const superdough = async (value, t, hapDuration) => { compressorRelease, } = value; - const orbitChannels = multiChannelOrbits ? [(orbit * 2) - 1, orbit * 2] : getDefaultValue('channels') - const channels = value.channels ?? orbitChannels; + //music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0 + const orbitChannels = ( + multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels') + ).map((ch) => ch - 1); + const channels = + value.channels != null + ? (Array.isArray(value.channels) ? value.channels : [value.channels]).map((ch) => ch - 1) + : orbitChannels; gain = applyGainCurve(nanFallback(gain, 1)); postgain = applyGainCurve(postgain); @@ -556,9 +569,6 @@ export const superdough = async (value, t, hapDuration) => { activeSoundSources.delete(chainID); } - //music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior - channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); - let audioNodes = []; if (bank && s) { diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index c2faf07f..dd372555 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -6,7 +6,7 @@ import { ButtonGroup } from './Forms.jsx'; import { AudioDeviceSelector } from './AudioDeviceSelector.jsx'; import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx'; import { confirmDialog } from '../../util.mjs'; -import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony } from '@strudel/webaudio'; +import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio'; function Checkbox({ label, value, onChange, disabled = false }) { return ( @@ -108,6 +108,7 @@ export function SettingsTab({ started }) { audioEngineTarget, togglePanelTrigger, maxPolyphony, + multiChannelOrbits, } = useSettings(); const shouldAlwaysSync = isUdels(); const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; @@ -162,6 +163,17 @@ export function SettingsTab({ started }) { value={maxPolyphony ?? ''} /> + + { + const val = cbEvent.target.checked; + settingsMap.setKey('multiChannelOrbits', val); + setMultiChannelOrbits(val); + }} + value={multiChannelOrbits} + /> + settingsMap.setKey('theme', theme)} /> diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index f0895aaa..f88e5a6e 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -18,7 +18,7 @@ import { setVersionDefaultsFrom } from './util.mjs'; import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; import { clearHydra } from '@strudel/hydra'; import { useCallback, useEffect, useRef, useState } from 'react'; -import { settingsMap, useSettings } from '../settings.mjs'; +import { parseBoolean, settingsMap, useSettings } from '../settings.mjs'; import { setActivePattern, setLatestCode, @@ -36,11 +36,15 @@ import './Repl.css'; import { setInterval, clearInterval } from 'worker-timers'; import { getMetadata } from '../metadata_parser'; -const { latestCode, maxPolyphony, audioDeviceName } = settingsMap.get(); +const { latestCode, maxPolyphony, audioDeviceName, multiChannelOrbits } = settingsMap.get(); let modulesLoading, presets, drawContext, clearCanvas, audioReady; if (typeof window !== 'undefined') { - audioReady = initAudioOnFirstClick({ maxPolyphony, audioDeviceName }); + audioReady = initAudioOnFirstClick({ + maxPolyphony, + audioDeviceName, + multiChannelOrbits: parseBoolean(multiChannelOrbits), + }); modulesLoading = loadModules(); presets = prebake(); drawContext = getDrawContext(); diff --git a/website/src/settings.mjs b/website/src/settings.mjs index 1b4ba33f..84b43314 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -38,6 +38,7 @@ export const defaultSettings = { isButtonRowHidden: false, isCSSAnimationDisabled: false, maxPolyphony: 128, + multiChannelOrbits: false, }; let search = null; @@ -50,7 +51,7 @@ const settings_key = `strudel-settings${instance > 0 ? instance : ''}`; export const settingsMap = persistentMap(settings_key, defaultSettings); -const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false); +export const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false); export function useSettings() { const state = useStore(settingsMap); @@ -81,6 +82,7 @@ export function useSettings() { isPanelPinned: parseBoolean(state.isPanelPinned), isPanelOpen: parseBoolean(state.isPanelOpen), userPatterns: userPatterns, + multiChannelOrbits: parseBoolean(state.multiChannelOrbits), }; } From 099d321dbd1489b885e2e1f8b639b41b39863d34 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 7 May 2025 11:25:34 -0400 Subject: [PATCH 3/4] format --- packages/superdough/superdough.mjs | 17 +++++++++-------- .../src/repl/components/panel/SettingsTab.jsx | 9 +++++++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 90f0d369..f774ca60 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -441,6 +441,11 @@ export function resetGlobalEffects() { } let activeSoundSources = new Map(); +//music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0 + +function mapChannelNumbers(channels) { + return (Array.isArray(channels) ? channels : [value.channels]).map((ch) => ch - 1); +} export const superdough = async (value, t, hapDuration) => { const ac = getAudioContext(); @@ -539,14 +544,10 @@ export const superdough = async (value, t, hapDuration) => { compressorRelease, } = value; - //music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0 - const orbitChannels = ( - multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels') - ).map((ch) => ch - 1); - const channels = - value.channels != null - ? (Array.isArray(value.channels) ? value.channels : [value.channels]).map((ch) => ch - 1) - : orbitChannels; + const orbitChannels = mapChannelNumbers( + multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'), + ); + const channels = value.channels != null ? mapChannelNumbers(value.channels) : orbitChannels; gain = applyGainCurve(nanFallback(gain, 1)); postgain = applyGainCurve(postgain); diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index dd372555..5be9b602 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -168,8 +168,13 @@ export function SettingsTab({ started }) { label="Multi Channel Orbits" onChange={(cbEvent) => { const val = cbEvent.target.checked; - settingsMap.setKey('multiChannelOrbits', val); - setMultiChannelOrbits(val); + confirmDialog(RELOAD_MSG).then((r) => { + if (r == true) { + settingsMap.setKey('multiChannelOrbits', val); + setMultiChannelOrbits(val); + return window.location.reload(); + } + }); }} value={multiChannelOrbits} /> From 64ebfb57c3d923acf0641b8391cc48b8243eda03 Mon Sep 17 00:00:00 2001 From: "Jade (Rose) Rowland" Date: Wed, 7 May 2025 11:30:42 -0400 Subject: [PATCH 4/4] lint --- packages/superdough/superdough.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index f774ca60..ffdb577c 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -444,7 +444,7 @@ let activeSoundSources = new Map(); //music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0 function mapChannelNumbers(channels) { - return (Array.isArray(channels) ? channels : [value.channels]).map((ch) => ch - 1); + return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); } export const superdough = async (value, t, hapDuration) => {