From 3fd5fdf1eec830b5d5ebba660ed1aab979b65c6c Mon Sep 17 00:00:00 2001 From: Jade Rowland Date: Thu, 23 Nov 2023 12:43:51 -0500 Subject: [PATCH] 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); }); }} >