From 7aa217828b5ee8cfc553a98ba8f7cdebff01b033 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Mon, 20 Nov 2023 23:06:35 -0500 Subject: [PATCH 01/14] testing --- packages/superdough/superdough.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 00e2f42c..be9a3df9 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -30,7 +30,11 @@ let audioContext; export const getAudioContext = () => { if (!audioContext) { audioContext = new AudioContext(); + var maxChannelCount = audioContext.destination.maxChannelCount; + audioContext.destination.channelCount = maxChannelCount; } + //console.log(audioContext.destination.maxChannelCount); + return audioContext; }; @@ -39,7 +43,10 @@ const getDestination = () => { const ctx = getAudioContext(); if (!destination) { destination = ctx.createGain(); - destination.connect(ctx.destination); + var merger = ctx.createChannelMerger(ctx.destination.channelCount); + merger.connect(ctx.destination); + destination.connect(merger, 0, 4); + destination.connect(merger, 0, 5); } return destination; }; From f0b458cd1775e8ed3f82c10d048d00a560e57c58 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Wed, 22 Nov 2023 00:03:34 -0500 Subject: [PATCH 02/14] need to figure out channels input + seperating left right channels from source --- packages/core/controls.mjs | 13 +++++++ packages/superdough/superdough.mjs | 60 ++++++++++++++++++++++-------- 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index a03658a8..a4db48cb 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -381,6 +381,19 @@ const generic_params = [ */ ['coarse'], + /** + * Allows you to set the output channels on the interface + * + * @name channels + * @synonyms ch + * + * @param {!Float32Array} channels Array + * @example + * note("e a d b g").channels([2, 3]).room(1) + * + */ + ['channels', 'ch'], + ['phaserrate', 'phasr'], // superdirt only /** diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index be9a3df9..ef77db8a 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -27,6 +27,7 @@ export function getSound(s) { export const resetLoadedSounds = () => soundMap.set({}); let audioContext; + export const getAudioContext = () => { if (!audioContext) { audioContext = new AudioContext(); @@ -37,23 +38,39 @@ export const getAudioContext = () => { return audioContext; }; - -let destination; -const getDestination = () => { +// outputs: Map +const outputs = new Map(); +let channelMerger; +// channels: Array +const getDestinations = (channels) => { const ctx = getAudioContext(); - if (!destination) { - destination = ctx.createGain(); - var merger = ctx.createChannelMerger(ctx.destination.channelCount); - merger.connect(ctx.destination); - destination.connect(merger, 0, 4); - destination.connect(merger, 0, 5); + if (channelMerger == null) { + channelMerger = ctx.createChannelMerger(ctx.destination.channelCount); + channelMerger.connect(ctx.destination); } - return destination; + channels.forEach((ch) => { + if (!outputs.has(ch)) { + const gain = ctx.createGain(); + gain.channelInterpretation = 'discrete'; + gain.connect(channelMerger, 0, ch); + outputs.set(ch, gain); + } + }); + // if (!destination) { + // destination = ctx.createGain(); + // channelMerger = ctx.createChannelMerger(ctx.destination.channelCount); + // channelMerger.connect(ctx.destination); + // destination.connect(channelMerger, 0, 4); + // destination.connect(channelMerger, 0, 5); + // } + return channels.map((ch) => outputs.get(ch)); }; export const panic = () => { - getDestination().gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01); - destination = null; + outputs.forEach((output) => { + return output.gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01); + }); + outputs.clear(); }; let workletsLoading; @@ -101,6 +118,15 @@ export async function initAudioOnFirstClick(options) { let delays = {}; const maxfeedback = 0.98; +//input: audioNode, channels: Array +const connectToOutputs = (input, channels = [0, 1]) => { + const outputs = getDestinations(channels); + console.log(input); + + outputs.forEach((output, i) => { + input.connect(output, 0); + }); +}; function getDelay(orbit, delaytime, delayfeedback, t) { if (delayfeedback > maxfeedback) { @@ -111,7 +137,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.. - dly.connect(getDestination()); + connectToOutputs(dly, [0, 1]); delays[orbit] = dly; } delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t); @@ -170,7 +196,7 @@ function getReverb(orbit, duration, fade, lp, dim, ir) { if (!reverbs[orbit]) { const ac = getAudioContext(); const reverb = ac.createReverb(duration, fade, lp, dim, ir); - reverb.connect(getDestination()); + connectToOutputs(reverb, [0, 1]); reverbs[orbit] = reverb; } if ( @@ -275,7 +301,7 @@ export const superdough = async (value, deadline, hapDuration) => { bpsustain = 1, bprelease = 0.01, bandq = 1, - + channels = [0, 1], //phaser phaser, phaserdepth = 0.75, @@ -442,7 +468,9 @@ export const superdough = async (value, deadline, hapDuration) => { // last gain const post = gainNode(postgain); chain.push(post); - post.connect(getDestination()); + console.log(channels); + // this should be an array but is getting interpreted as an int for some reason... + connectToOutputs(post, [channels]); // delay let delaySend; From e41973371690d4cc2ac4b6b1123182d63a0feb34 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Thu, 23 Nov 2023 01:04:53 -0500 Subject: [PATCH 03/14] channel splitting and channels parameter --- packages/core/controls.mjs | 35 +++++++++++++++---------- packages/superdough/superdough.mjs | 41 +++++++++++++++--------------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index a4db48cb..899e57e9 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { Pattern, register, sequence } from './pattern.mjs'; +import { Pattern, register, reify, sequence } from './pattern.mjs'; import { zipWith } from './util.mjs'; const controls = {}; @@ -381,18 +381,18 @@ const generic_params = [ */ ['coarse'], - /** - * Allows you to set the output channels on the interface - * - * @name channels - * @synonyms ch - * - * @param {!Float32Array} channels Array - * @example - * note("e a d b g").channels([2, 3]).room(1) - * - */ - ['channels', 'ch'], + // /** + // * Allows you to set the output channels on the interface + // * + // * @name channels + // * @synonyms ch + // * + // * @param {!Float32Array} channels Array + // * @example + // * note("e a d b g").channels([2, 3]).room(1) + // * + // */ + // ['channels', 'ch'], ['phaserrate', 'phasr'], // superdirt only @@ -1390,4 +1390,13 @@ controls.ds = register('ds', (ds, pat) => { return pat.set({ decay, sustain }); }); +controls.ch = register(['channels', 'ch'], (channels, pat) => { + channels = !Array.isArray(channels) ? [channels] : channels; + + // channels = channels.map(reify).map((channelPat) => { + // // How do I return the current value of the channel pattern here? + // }); + return pat.set({ channels }); +}); + export default controls; diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index ef77db8a..3cccfc96 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -31,38 +31,33 @@ let audioContext; export const getAudioContext = () => { if (!audioContext) { audioContext = new AudioContext(); - var maxChannelCount = audioContext.destination.maxChannelCount; + const maxChannelCount = audioContext.destination.maxChannelCount; audioContext.destination.channelCount = maxChannelCount; } - //console.log(audioContext.destination.maxChannelCount); - return audioContext; }; -// outputs: Map +// outputs: Map const outputs = new Map(); let channelMerger; // channels: Array const getDestinations = (channels) => { const ctx = getAudioContext(); if (channelMerger == null) { - channelMerger = ctx.createChannelMerger(ctx.destination.channelCount); + channelMerger = new ChannelMergerNode(ctx, { numberOfInputs: ctx.destination.channelCount }); channelMerger.connect(ctx.destination); } channels.forEach((ch) => { if (!outputs.has(ch)) { - const gain = ctx.createGain(); - gain.channelInterpretation = 'discrete'; + const gain = new GainNode(ctx, { + channelInterpretation: 'discrete', + channelCount: 1, + channelCountMode: 'explicit', + }); gain.connect(channelMerger, 0, ch); outputs.set(ch, gain); } }); - // if (!destination) { - // destination = ctx.createGain(); - // channelMerger = ctx.createChannelMerger(ctx.destination.channelCount); - // channelMerger.connect(ctx.destination); - // destination.connect(channelMerger, 0, 4); - // destination.connect(channelMerger, 0, 5); - // } + return channels.map((ch) => outputs.get(ch)); }; @@ -121,10 +116,15 @@ const maxfeedback = 0.98; //input: audioNode, channels: Array const connectToOutputs = (input, channels = [0, 1]) => { const outputs = getDestinations(channels); - console.log(input); - + const ctx = getAudioContext(); + const center = new StereoPannerNode(ctx); + input.connect(center); + const splitter = new ChannelSplitterNode(ctx, { + numberOfOutputs: input.channelCount, + }); + center.connect(splitter); outputs.forEach((output, i) => { - input.connect(output, 0); + splitter.connect(output, i % input.channelCount, 0); }); }; @@ -333,6 +333,7 @@ export const superdough = async (value, deadline, hapDuration) => { compressorAttack, compressorRelease, } = value; + gain *= velocity; // legacy fix for velocity let toDisconnect = []; // audio nodes that will be disconnected when the source has ended const onended = () => { @@ -466,11 +467,9 @@ export const superdough = async (value, deadline, hapDuration) => { } // last gain - const post = gainNode(postgain); + const post = new GainNode(ac, { gain: postgain }); chain.push(post); - console.log(channels); - // this should be an array but is getting interpreted as an int for some reason... - connectToOutputs(post, [channels]); + connectToOutputs(post, channels); // delay let delaySend; From 3fd5fdf1eec830b5d5ebba660ed1aab979b65c6c Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Thu, 23 Nov 2023 12:43:51 -0500 Subject: [PATCH 04/14] preview outputs should be stereo --- packages/core/controls.mjs | 38 +++++++++++++++--------------- packages/superdough/superdough.mjs | 21 +++++++++++++---- packages/superdough/synth.mjs | 6 ++--- website/src/repl/Footer.jsx | 4 ++-- 4 files changed, 40 insertions(+), 29 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 899e57e9..01b46a9e 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -381,18 +381,18 @@ const generic_params = [ */ ['coarse'], - // /** - // * Allows you to set the output channels on the interface - // * - // * @name channels - // * @synonyms ch - // * - // * @param {!Float32Array} channels Array - // * @example - // * note("e a d b g").channels([2, 3]).room(1) - // * - // */ - // ['channels', 'ch'], + /** + * Allows you to set the output channels on the interface + * + * @name channels + * @synonyms ch + * + * @param {number | Pattern} channels pattern the output channels + * @example + * note("e a d b g").channels("2:3").room(1) + * + */ + ['channels', 'ch'], ['phaserrate', 'phasr'], // superdirt only @@ -1390,13 +1390,13 @@ controls.ds = register('ds', (ds, pat) => { return pat.set({ decay, sustain }); }); -controls.ch = register(['channels', 'ch'], (channels, pat) => { - channels = !Array.isArray(channels) ? [channels] : channels; +// controls.ch = register(['channels', 'ch'], (channels, pat) => { +// channels = !Array.isArray(channels) ? [channels] : channels; - // channels = channels.map(reify).map((channelPat) => { - // // How do I return the current value of the channel pattern here? - // }); - return pat.set({ channels }); -}); +// // channels = channels.map(reify).map((channelPat) => { +// // // How do I return the current value of the channel pattern here? +// // }); +// return pat.set({ channels }); +// }); export default controls; diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 3cccfc96..4f931304 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -23,6 +23,13 @@ export function registerSound(key, onTrigger, data = {}) { export function getSound(s) { return soundMap.get()[s]; } +// sounds with only one active channel will get upmixed to two, stereo sounds should remain unaffected +// There might be a better way to do this +export const upMixToStereo = (ctx, node) => { + const stereo = new StereoPannerNode(ctx); + node.connect(stereo); + return stereo; +}; export const resetLoadedSounds = () => soundMap.set({}); @@ -117,14 +124,16 @@ const maxfeedback = 0.98; const connectToOutputs = (input, channels = [0, 1]) => { const outputs = getDestinations(channels); const ctx = getAudioContext(); - const center = new StereoPannerNode(ctx); - input.connect(center); + + //This upmix can be avoided if correct channel counts are set throughout the app, + //could theoretically support surround sound audio files if that work was done + const stereo = upMixToStereo(ctx, input); const splitter = new ChannelSplitterNode(ctx, { - numberOfOutputs: input.channelCount, + numberOfOutputs: stereo.channelCount, }); - center.connect(splitter); + stereo.connect(splitter); outputs.forEach((output, i) => { - splitter.connect(output, i % input.channelCount, 0); + splitter.connect(output, i % stereo.channelCount, 0); }); }; @@ -334,6 +343,8 @@ export const superdough = async (value, deadline, hapDuration) => { compressorRelease, } = value; + channels = Array.isArray(channels) ? channels : [channels]; + gain *= velocity; // legacy fix for velocity let toDisconnect = []; // audio nodes that will be disconnected when the source has ended const onended = () => { diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index dafc2e7c..fad67b75 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -71,8 +71,7 @@ export function waveformN(partials, type) { const real = new Float32Array(partials + 1); const imag = new Float32Array(partials + 1); const ac = getAudioContext(); - const osc = ac.createOscillator(); - + const osc = new OscillatorNode(ac, { channelCount: 1, channelCountMode: 'explicit' }); const terms = { sawtooth: (n) => [0, -1 / n], square: (n) => [0, n % 2 === 0 ? 0 : 1 / n], @@ -125,7 +124,8 @@ export function getOscillator( let o; // If no partials are given, use stock waveforms if (!partials || s === 'sine') { - o = getAudioContext().createOscillator(); + o = new OscillatorNode(ac, { channelCount: 1, channelCountMode: 'explicit' }); + console.log(o); o.type = s || 'triangle'; } // generate custom waveform if partials are given diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index b03f62d0..87e42dd7 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -7,7 +7,7 @@ import React, { useMemo, useCallback, useLayoutEffect, useRef, useState } from ' import { Reference } from './Reference'; import { themes } from './themes.mjs'; import { useSettings, settingsMap, setActiveFooter, defaultSettings } from '../settings.mjs'; -import { getAudioContext, soundMap } from '@strudel.cycles/webaudio'; +import { getAudioContext, soundMap, upMixToStereo } from '@strudel.cycles/webaudio'; import { useStore } from '@nanostores/react'; import { FilesTab } from './FilesTab'; @@ -271,7 +271,7 @@ function SoundsTab() { const onended = () => trigRef.current?.node?.disconnect(); trigRef.current = Promise.resolve(onTrigger(time, params, onended)); trigRef.current.then((ref) => { - ref?.node.connect(ctx.destination); + upMixToStereo(ctx, ref?.node).connect(ctx.destination); }); }} > From 1f62d85d8b10ab6234169c03469389830983ec3b Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Thu, 23 Nov 2023 13:29:22 -0500 Subject: [PATCH 05/14] cleaning up --- packages/superdough/superdough.mjs | 63 ++++++++++-------------------- website/src/repl/Footer.jsx | 4 +- 2 files changed, 22 insertions(+), 45 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 4f931304..a1b81edd 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -23,13 +23,6 @@ export function registerSound(key, onTrigger, data = {}) { export function getSound(s) { return soundMap.get()[s]; } -// sounds with only one active channel will get upmixed to two, stereo sounds should remain unaffected -// There might be a better way to do this -export const upMixToStereo = (ctx, node) => { - const stereo = new StereoPannerNode(ctx); - node.connect(stereo); - return stereo; -}; export const resetLoadedSounds = () => soundMap.set({}); @@ -45,28 +38,6 @@ export const getAudioContext = () => { }; // outputs: Map const outputs = new Map(); -let channelMerger; -// channels: Array -const getDestinations = (channels) => { - const ctx = getAudioContext(); - if (channelMerger == null) { - channelMerger = new ChannelMergerNode(ctx, { numberOfInputs: ctx.destination.channelCount }); - channelMerger.connect(ctx.destination); - } - channels.forEach((ch) => { - if (!outputs.has(ch)) { - const gain = new GainNode(ctx, { - channelInterpretation: 'discrete', - channelCount: 1, - channelCountMode: 'explicit', - }); - gain.connect(channelMerger, 0, ch); - outputs.set(ch, gain); - } - }); - - return channels.map((ch) => outputs.get(ch)); -}; export const panic = () => { outputs.forEach((output) => { @@ -120,20 +91,26 @@ export async function initAudioOnFirstClick(options) { let delays = {}; const maxfeedback = 0.98; -//input: audioNode, channels: Array -const connectToOutputs = (input, channels = [0, 1]) => { - const outputs = getDestinations(channels); - const ctx = getAudioContext(); - //This upmix can be avoided if correct channel counts are set throughout the app, - //could theoretically support surround sound audio files if that work was done - const stereo = upMixToStereo(ctx, input); +let channelMerger; +// input: AudioNode, channels: ?Array +export const connectToDestination = (input, channels = [0, 1]) => { + const ctx = getAudioContext(); + if (channelMerger == null) { + channelMerger = new ChannelMergerNode(ctx, { numberOfInputs: ctx.destination.channelCount }); + channelMerger.connect(ctx.destination); + } + //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(ctx); + input.connect(stereoMix); + const splitter = new ChannelSplitterNode(ctx, { - numberOfOutputs: stereo.channelCount, + numberOfOutputs: stereoMix.channelCount, }); - stereo.connect(splitter); - outputs.forEach((output, i) => { - splitter.connect(output, i % stereo.channelCount, 0); + stereoMix.connect(splitter); + channels.map((ch, i) => { + splitter.connect(channelMerger, i % stereoMix.channelCount, ch); }); }; @@ -146,7 +123,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.. - connectToOutputs(dly, [0, 1]); + connectToDestination(dly, [0, 1]); delays[orbit] = dly; } delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t); @@ -205,7 +182,7 @@ function getReverb(orbit, duration, fade, lp, dim, ir) { if (!reverbs[orbit]) { const ac = getAudioContext(); const reverb = ac.createReverb(duration, fade, lp, dim, ir); - connectToOutputs(reverb, [0, 1]); + connectToDestination(reverb, [0, 1]); reverbs[orbit] = reverb; } if ( @@ -480,7 +457,7 @@ export const superdough = async (value, deadline, hapDuration) => { // last gain const post = new GainNode(ac, { gain: postgain }); chain.push(post); - connectToOutputs(post, channels); + connectToDestination(post, channels); // delay let delaySend; diff --git a/website/src/repl/Footer.jsx b/website/src/repl/Footer.jsx index 87e42dd7..35625093 100644 --- a/website/src/repl/Footer.jsx +++ b/website/src/repl/Footer.jsx @@ -7,7 +7,7 @@ import React, { useMemo, useCallback, useLayoutEffect, useRef, useState } from ' import { Reference } from './Reference'; import { themes } from './themes.mjs'; import { useSettings, settingsMap, setActiveFooter, defaultSettings } from '../settings.mjs'; -import { getAudioContext, soundMap, upMixToStereo } from '@strudel.cycles/webaudio'; +import { getAudioContext, soundMap, connectToDestination } from '@strudel.cycles/webaudio'; import { useStore } from '@nanostores/react'; import { FilesTab } from './FilesTab'; @@ -271,7 +271,7 @@ function SoundsTab() { const onended = () => trigRef.current?.node?.disconnect(); trigRef.current = Promise.resolve(onTrigger(time, params, onended)); trigRef.current.then((ref) => { - upMixToStereo(ctx, ref?.node).connect(ctx.destination); + connectToDestination(ref?.node); }); }} > From 88e933ee2506c4ee948b4f09d8028db1c60416de Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Thu, 23 Nov 2023 13:31:00 -0500 Subject: [PATCH 06/14] cleaning up --- packages/core/controls.mjs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 01b46a9e..31e6ea77 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see . */ -import { Pattern, register, reify, sequence } from './pattern.mjs'; +import { Pattern, register, sequence } from './pattern.mjs'; import { zipWith } from './util.mjs'; const controls = {}; @@ -389,7 +389,7 @@ const generic_params = [ * * @param {number | Pattern} channels pattern the output channels * @example - * note("e a d b g").channels("2:3").room(1) + * note("e a d b g").channels("2:3") * */ ['channels', 'ch'], @@ -1390,13 +1390,4 @@ controls.ds = register('ds', (ds, pat) => { return pat.set({ decay, sustain }); }); -// controls.ch = register(['channels', 'ch'], (channels, pat) => { -// channels = !Array.isArray(channels) ? [channels] : channels; - -// // channels = channels.map(reify).map((channelPat) => { -// // // How do I return the current value of the channel pattern here? -// // }); -// return pat.set({ channels }); -// }); - export default controls; From 64c2e7c1e8071e1676e9958d4f8f3a7f840f8b37 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Thu, 23 Nov 2023 13:45:58 -0500 Subject: [PATCH 07/14] cleaning up --- packages/superdough/synth.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/superdough/synth.mjs b/packages/superdough/synth.mjs index fad67b75..dafc2e7c 100644 --- a/packages/superdough/synth.mjs +++ b/packages/superdough/synth.mjs @@ -71,7 +71,8 @@ export function waveformN(partials, type) { const real = new Float32Array(partials + 1); const imag = new Float32Array(partials + 1); const ac = getAudioContext(); - const osc = new OscillatorNode(ac, { channelCount: 1, channelCountMode: 'explicit' }); + const osc = ac.createOscillator(); + const terms = { sawtooth: (n) => [0, -1 / n], square: (n) => [0, n % 2 === 0 ? 0 : 1 / n], @@ -124,8 +125,7 @@ export function getOscillator( let o; // If no partials are given, use stock waveforms if (!partials || s === 'sine') { - o = new OscillatorNode(ac, { channelCount: 1, channelCountMode: 'explicit' }); - console.log(o); + o = getAudioContext().createOscillator(); o.type = s || 'triangle'; } // generate custom waveform if partials are given From 886b16d6fd906c1a1f917679bed0eb4011e42371 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Thu, 23 Nov 2023 14:07:32 -0500 Subject: [PATCH 08/14] fixed panic function --- packages/superdough/superdough.mjs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index a1b81edd..2eb4a64f 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -36,15 +36,6 @@ export const getAudioContext = () => { } return audioContext; }; -// outputs: Map -const outputs = new Map(); - -export const panic = () => { - outputs.forEach((output) => { - return output.gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01); - }); - outputs.clear(); -}; let workletsLoading; @@ -92,13 +83,16 @@ export async function initAudioOnFirstClick(options) { let delays = {}; const maxfeedback = 0.98; -let channelMerger; +let channelMerger, destinationGain; + // input: AudioNode, channels: ?Array export const connectToDestination = (input, channels = [0, 1]) => { const ctx = getAudioContext(); if (channelMerger == null) { channelMerger = new ChannelMergerNode(ctx, { numberOfInputs: ctx.destination.channelCount }); - channelMerger.connect(ctx.destination); + destinationGain = new GainNode(ctx); + channelMerger.connect(destinationGain); + destinationGain.connect(ctx.destination); } //This upmix can be removed if correct channel counts are set throughout the app, // and then strudel could theoretically support surround sound audio files @@ -114,6 +108,11 @@ export const connectToDestination = (input, channels = [0, 1]) => { }); }; +export const panic = () => { + destinationGain.gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01); + destinationGain = null; +}; + function getDelay(orbit, delaytime, delayfeedback, t) { if (delayfeedback > maxfeedback) { //logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`); From e7376c4fc8753be908aedcfa81a4ca239abdc3ec Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Thu, 23 Nov 2023 14:17:17 -0500 Subject: [PATCH 09/14] panic function update --- packages/superdough/superdough.mjs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 2eb4a64f..dfea5dc1 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -109,6 +109,9 @@ export const connectToDestination = (input, channels = [0, 1]) => { }; export const panic = () => { + if (destinationGain == null) { + return; + } destinationGain.gain.linearRampToValueAtTime(0, getAudioContext().currentTime + 0.01); destinationGain = null; }; From 25b2eb5783a09697ed66c096dbdb483fa0549f0a Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sat, 25 Nov 2023 16:20:12 +0100 Subject: [PATCH 10/14] snapshot --- test/__snapshots__/examples.test.mjs.snap | 25 +++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 34f7a850..09a1ecec 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1038,6 +1038,31 @@ exports[`runs examples > example "ceil" example index 0 1`] = ` ] `; +exports[`runs examples > example "channels" example index 0 1`] = ` +[ + "[ 0/1 → 1/5 | note:e channels:[2 3] ]", + "[ 1/5 → 2/5 | note:a channels:[2 3] ]", + "[ 2/5 → 3/5 | note:d channels:[2 3] ]", + "[ 3/5 → 4/5 | note:b channels:[2 3] ]", + "[ 4/5 → 1/1 | note:g channels:[2 3] ]", + "[ 1/1 → 6/5 | note:e channels:[2 3] ]", + "[ 6/5 → 7/5 | note:a channels:[2 3] ]", + "[ 7/5 → 8/5 | note:d channels:[2 3] ]", + "[ 8/5 → 9/5 | note:b channels:[2 3] ]", + "[ 9/5 → 2/1 | note:g channels:[2 3] ]", + "[ 2/1 → 11/5 | note:e channels:[2 3] ]", + "[ 11/5 → 12/5 | note:a channels:[2 3] ]", + "[ 12/5 → 13/5 | note:d channels:[2 3] ]", + "[ 13/5 → 14/5 | note:b channels:[2 3] ]", + "[ 14/5 → 3/1 | note:g channels:[2 3] ]", + "[ 3/1 → 16/5 | note:e channels:[2 3] ]", + "[ 16/5 → 17/5 | note:a channels:[2 3] ]", + "[ 17/5 → 18/5 | note:d channels:[2 3] ]", + "[ 18/5 → 19/5 | note:b channels:[2 3] ]", + "[ 19/5 → 4/1 | note:g channels:[2 3] ]", +] +`; + exports[`runs examples > example "chooseCycles" example index 0 1`] = ` [ "[ 0/1 → 1/4 | s:bd ]", From 9aec18b3a0c7cbf86f15b45e0d41cbdf14726fb2 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 3 Dec 2023 18:25:30 -0500 Subject: [PATCH 11/14] add 1 to channel selection --- packages/core/controls.mjs | 2 +- packages/superdough/superdough.mjs | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 6eca59d8..d979ff23 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -389,7 +389,7 @@ const generic_params = [ * * @param {number | Pattern} channels pattern the output channels * @example - * note("e a d b g").channels("2:3") + * note("e a d b g").channels("3:4") * */ ['channels', 'ch'], diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 8449c5c7..1291c192 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -103,8 +103,8 @@ export const connectToDestination = (input, channels = [0, 1]) => { numberOfOutputs: stereoMix.channelCount, }); stereoMix.connect(splitter); - channels.map((ch, i) => { - splitter.connect(channelMerger, i % stereoMix.channelCount, ch); + channels.forEach((ch, i) => { + splitter.connect(channelMerger, i % stereoMix.channelCount, clamp(ch, 0, ctx.destination.channelCount - 1)); }); }; @@ -290,7 +290,7 @@ export const superdough = async (value, deadline, hapDuration) => { bpsustain = 1, bprelease = 0.01, bandq = 1, - channels = [0, 1], + channels = [1, 2], //phaser phaser, phaserdepth = 0.75, @@ -323,7 +323,8 @@ export const superdough = async (value, deadline, hapDuration) => { compressorRelease, } = value; - channels = Array.isArray(channels) ? channels : [channels]; + //music programs/audio gear usually labels channels 0,1 as 1,2 so imitate that behavior + channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); gain *= velocity; // legacy fix for velocity let toDisconnect = []; // audio nodes that will be disconnected when the source has ended From f51109cb266dcef35536d84f367789b028cb0118 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 3 Dec 2023 18:26:48 -0500 Subject: [PATCH 12/14] comment --- 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 1291c192..2305ab44 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -323,7 +323,7 @@ export const superdough = async (value, deadline, hapDuration) => { compressorRelease, } = value; - //music programs/audio gear usually labels channels 0,1 as 1,2 so imitate that behavior + //music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1); gain *= velocity; // legacy fix for velocity From 9be10890e1465a936ba3ad2e7d1d7d8a40308cd5 Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Sun, 3 Dec 2023 18:31:01 -0500 Subject: [PATCH 13/14] update tests --- test/__snapshots__/examples.test.mjs.snap | 40 +++++++++++------------ test/__snapshots__/tunes.test.mjs.snap | 4 +-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 09a1ecec..bd5e9cb9 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -1040,26 +1040,26 @@ exports[`runs examples > example "ceil" example index 0 1`] = ` exports[`runs examples > example "channels" example index 0 1`] = ` [ - "[ 0/1 → 1/5 | note:e channels:[2 3] ]", - "[ 1/5 → 2/5 | note:a channels:[2 3] ]", - "[ 2/5 → 3/5 | note:d channels:[2 3] ]", - "[ 3/5 → 4/5 | note:b channels:[2 3] ]", - "[ 4/5 → 1/1 | note:g channels:[2 3] ]", - "[ 1/1 → 6/5 | note:e channels:[2 3] ]", - "[ 6/5 → 7/5 | note:a channels:[2 3] ]", - "[ 7/5 → 8/5 | note:d channels:[2 3] ]", - "[ 8/5 → 9/5 | note:b channels:[2 3] ]", - "[ 9/5 → 2/1 | note:g channels:[2 3] ]", - "[ 2/1 → 11/5 | note:e channels:[2 3] ]", - "[ 11/5 → 12/5 | note:a channels:[2 3] ]", - "[ 12/5 → 13/5 | note:d channels:[2 3] ]", - "[ 13/5 → 14/5 | note:b channels:[2 3] ]", - "[ 14/5 → 3/1 | note:g channels:[2 3] ]", - "[ 3/1 → 16/5 | note:e channels:[2 3] ]", - "[ 16/5 → 17/5 | note:a channels:[2 3] ]", - "[ 17/5 → 18/5 | note:d channels:[2 3] ]", - "[ 18/5 → 19/5 | note:b channels:[2 3] ]", - "[ 19/5 → 4/1 | note:g channels:[2 3] ]", + "[ 0/1 → 1/5 | note:e channels:[3 4] ]", + "[ 1/5 → 2/5 | note:a channels:[3 4] ]", + "[ 2/5 → 3/5 | note:d channels:[3 4] ]", + "[ 3/5 → 4/5 | note:b channels:[3 4] ]", + "[ 4/5 → 1/1 | note:g channels:[3 4] ]", + "[ 1/1 → 6/5 | note:e channels:[3 4] ]", + "[ 6/5 → 7/5 | note:a channels:[3 4] ]", + "[ 7/5 → 8/5 | note:d channels:[3 4] ]", + "[ 8/5 → 9/5 | note:b channels:[3 4] ]", + "[ 9/5 → 2/1 | note:g channels:[3 4] ]", + "[ 2/1 → 11/5 | note:e channels:[3 4] ]", + "[ 11/5 → 12/5 | note:a channels:[3 4] ]", + "[ 12/5 → 13/5 | note:d channels:[3 4] ]", + "[ 13/5 → 14/5 | note:b channels:[3 4] ]", + "[ 14/5 → 3/1 | note:g channels:[3 4] ]", + "[ 3/1 → 16/5 | note:e channels:[3 4] ]", + "[ 16/5 → 17/5 | note:a channels:[3 4] ]", + "[ 17/5 → 18/5 | note:d channels:[3 4] ]", + "[ 18/5 → 19/5 | note:b channels:[3 4] ]", + "[ 19/5 → 4/1 | note:g channels:[3 4] ]", ] `; diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap index 8d696d75..79fb2690 100644 --- a/test/__snapshots__/tunes.test.mjs.snap +++ b/test/__snapshots__/tunes.test.mjs.snap @@ -7831,8 +7831,8 @@ exports[`renders tunes > tune: hyperpop 1`] = ` "[ 3/2 ⇜ (11/6 → 15/8) | note:C#5 s:sawtooth gain:0.3422847385870941 attack:0.001 decay:0.2 sustain:0 hcutoff:5845.47833980621 cutoff:4000 ]", "[ 15/8 → 2/1 | note:D3 s:sawtooth gain:0.3 attack:0.01 decay:0.1 sustain:0.5 cutoff:2151.2782118349805 lpattack:0.1 lpenv:2 ftype:24db ]", "[ 15/8 → 2/1 | note:D3 s:square gain:0.3 attack:0.01 decay:0.1 sustain:0.5 cutoff:2151.2782118349805 lpattack:0.1 lpenv:2 ftype:24db ]", - "[ (15/8 → 2/1) ⇝ 9/4 | note:A5 s:sawtooth gain:0.35343108171056004 attack:0.001 decay:0.2 sustain:0 hcutoff:5796.978025372246 cutoff:4000 ]", - "[ (15/8 → 2/1) ⇝ 9/4 | note:C#5 s:sawtooth gain:0.35343108171056004 attack:0.001 decay:0.2 sustain:0 hcutoff:5796.978025372246 cutoff:4000 ]", + "[ (15/8 → 2/1) ⇝ 9/4 | note:A5 s:sawtooth gain:0.35343108171056015 attack:0.001 decay:0.2 sustain:0 hcutoff:5796.978025372246 cutoff:4000 ]", + "[ (15/8 → 2/1) ⇝ 9/4 | note:C#5 s:sawtooth gain:0.35343108171056015 attack:0.001 decay:0.2 sustain:0 hcutoff:5796.978025372246 cutoff:4000 ]", "[ (15/8 → 2/1) ⇝ 19/8 | note:F#3 s:square gain:0.7 attack:0.01 decay:0.1 sustain:0 cutoff:2151.2782118349805 ]", "[ (15/8 → 2/1) ⇝ 19/8 | note:A3 s:square gain:0.7 attack:0.01 decay:0.1 sustain:0 cutoff:2151.2782118349805 ]", "[ 13/8 ⇜ (23/12 → 2/1) | note:F#5 s:sawtooth gain:0.3479759264430665 attack:0.001 decay:0.2 sustain:0 hcutoff:5822.02388217981 cutoff:4000 ]", From 1190f6e0910ca1235ed6b969d0d0fe1bf80691ce Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Tue, 5 Dec 2023 18:17:33 +0100 Subject: [PATCH 14/14] fix weird rounding change... --- test/__snapshots__/tunes.test.mjs.snap | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap index 79fb2690..8d696d75 100644 --- a/test/__snapshots__/tunes.test.mjs.snap +++ b/test/__snapshots__/tunes.test.mjs.snap @@ -7831,8 +7831,8 @@ exports[`renders tunes > tune: hyperpop 1`] = ` "[ 3/2 ⇜ (11/6 → 15/8) | note:C#5 s:sawtooth gain:0.3422847385870941 attack:0.001 decay:0.2 sustain:0 hcutoff:5845.47833980621 cutoff:4000 ]", "[ 15/8 → 2/1 | note:D3 s:sawtooth gain:0.3 attack:0.01 decay:0.1 sustain:0.5 cutoff:2151.2782118349805 lpattack:0.1 lpenv:2 ftype:24db ]", "[ 15/8 → 2/1 | note:D3 s:square gain:0.3 attack:0.01 decay:0.1 sustain:0.5 cutoff:2151.2782118349805 lpattack:0.1 lpenv:2 ftype:24db ]", - "[ (15/8 → 2/1) ⇝ 9/4 | note:A5 s:sawtooth gain:0.35343108171056015 attack:0.001 decay:0.2 sustain:0 hcutoff:5796.978025372246 cutoff:4000 ]", - "[ (15/8 → 2/1) ⇝ 9/4 | note:C#5 s:sawtooth gain:0.35343108171056015 attack:0.001 decay:0.2 sustain:0 hcutoff:5796.978025372246 cutoff:4000 ]", + "[ (15/8 → 2/1) ⇝ 9/4 | note:A5 s:sawtooth gain:0.35343108171056004 attack:0.001 decay:0.2 sustain:0 hcutoff:5796.978025372246 cutoff:4000 ]", + "[ (15/8 → 2/1) ⇝ 9/4 | note:C#5 s:sawtooth gain:0.35343108171056004 attack:0.001 decay:0.2 sustain:0 hcutoff:5796.978025372246 cutoff:4000 ]", "[ (15/8 → 2/1) ⇝ 19/8 | note:F#3 s:square gain:0.7 attack:0.01 decay:0.1 sustain:0 cutoff:2151.2782118349805 ]", "[ (15/8 → 2/1) ⇝ 19/8 | note:A3 s:square gain:0.7 attack:0.01 decay:0.1 sustain:0 cutoff:2151.2782118349805 ]", "[ 13/8 ⇜ (23/12 → 2/1) | note:F#5 s:sawtooth gain:0.3479759264430665 attack:0.001 decay:0.2 sustain:0 hcutoff:5822.02388217981 cutoff:4000 ]",