From c6e1f758be97ee65994e798e811ec62f54764a18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20=28Netux=29=20Rodr=C3=ADguez?= Date: Sun, 18 Aug 2024 02:14:03 -0300 Subject: [PATCH] Add Audio Volume setting to REPL Allows for adjusting the global volume/gain of the REPL. Set default global value to 50%. Acts similarily to `gain()`, but without modifying the code, so passerbys who don't know how to use the tool can adjust the volume too). The volume slider uses a logarithmic scale, so it adjusts better to human sound perception. --- packages/superdough/superdough.mjs | 8 ++++++++ .../src/repl/components/panel/SettingsTab.jsx | 17 +++++++++++++++-- website/src/repl/useReplContext.jsx | 11 ++++++++--- website/src/repl/util.mjs | 9 ++++++++- website/src/settings.mjs | 1 + 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/packages/superdough/superdough.mjs b/packages/superdough/superdough.mjs index 01569788b..dda7072eb 100644 --- a/packages/superdough/superdough.mjs +++ b/packages/superdough/superdough.mjs @@ -143,6 +143,14 @@ export function initializeAudioOutput() { destinationGain.connect(audioContext.destination); } +export function setGlobalGain(gain) { + if (destinationGain == null) { + initializeAudioOutput(); + } + + destinationGain.gain.value = gain; +} + // input: AudioNode, channels: ?Array export const connectToDestination = (input, channels = [0, 1]) => { const ctx = getAudioContext(); diff --git a/website/src/repl/components/panel/SettingsTab.jsx b/website/src/repl/components/panel/SettingsTab.jsx index e1d047eaf..e2385dad6 100644 --- a/website/src/repl/components/panel/SettingsTab.jsx +++ b/website/src/repl/components/panel/SettingsTab.jsx @@ -1,6 +1,6 @@ import { defaultSettings, settingsMap, useSettings } from '../../../settings.mjs'; import { themes } from '@strudel/codemirror'; -import { isUdels } from '../../util.mjs'; +import { isUdels, setGlobalAudioVolume } from '../../util.mjs'; import { ButtonGroup } from './Forms.jsx'; import { AudioDeviceSelector } from './AudioDeviceSelector.jsx'; @@ -42,7 +42,7 @@ function NumberSlider({ value, onChange, step = 1, ...rest }) { /> onChange(Number(e.target.value))} @@ -96,6 +96,7 @@ export function SettingsTab({ started }) { fontFamily, panelPosition, audioDeviceName, + audioVolume, } = useSettings(); const shouldAlwaysSync = isUdels(); return ( @@ -109,6 +110,18 @@ export function SettingsTab({ started }) { /> )} + + { + settingsMap.setKey('audioVolume', audioVolume); + setGlobalAudioVolume(audioVolume); + }} + min={0} + max={100} + step={.1} + /> + settingsMap.setKey('theme', theme)} /> diff --git a/website/src/repl/useReplContext.jsx b/website/src/repl/useReplContext.jsx index d61699b6d..ef684aa3f 100644 --- a/website/src/repl/useReplContext.jsx +++ b/website/src/repl/useReplContext.jsx @@ -15,7 +15,7 @@ import { initAudioOnFirstClick, } from '@strudel/webaudio'; import { defaultAudioDeviceName } from '../settings.mjs'; -import { getAudioDevices, setAudioDevice, setVersionDefaultsFrom } from './util.mjs'; +import { getAudioDevices, setAudioDevice, setGlobalAudioVolume, setVersionDefaultsFrom } from './util.mjs'; import { StrudelMirror, defaultSettings } from '@strudel/codemirror'; import { clearHydra } from '@strudel/hydra'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -154,9 +154,11 @@ export function useReplContext() { editorRef.current?.updateSettings(editorSettings); }, [_settings]); - // on first load, set stored audio device if possible + // on first load... useEffect(() => { - const { audioDeviceName } = _settings; + const { audioDeviceName, audioVolume } = _settings; + + // set stored audio device if possible if (audioDeviceName !== defaultAudioDeviceName) { getAudioDevices().then((devices) => { const deviceID = devices.get(audioDeviceName); @@ -166,6 +168,9 @@ export function useReplContext() { setAudioDevice(deviceID); }); } + + // set stored audio volume + setGlobalAudioVolume(audioVolume); }, []); // diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index 4aa61fb03..9371adc68 100644 --- a/website/src/repl/util.mjs +++ b/website/src/repl/util.mjs @@ -1,6 +1,6 @@ import { evalScope, hash2code, logger } from '@strudel/core'; import { settingPatterns, defaultAudioDeviceName } from '../settings.mjs'; -import { getAudioContext, initializeAudioOutput, setDefaultAudioContext, setVersionDefaults } from '@strudel/webaudio'; +import { getAudioContext, initializeAudioOutput, setDefaultAudioContext, setGlobalGain, setVersionDefaults } from '@strudel/webaudio'; import { getMetadata } from '../metadata_parser'; import { isTauri } from '../tauri.mjs'; @@ -180,6 +180,13 @@ export const setAudioDevice = async (id) => { initializeAudioOutput(); }; +export const setGlobalAudioVolume = (volume) => { + // Adjust user visible volume to a gain value in the range [0, 1] + // Pow is used to also adjust the volume to a logarithmic scale (as perceived by us humans) + const gain = Math.pow(volume / 100, 2); + setGlobalGain(gain); +} + export function setVersionDefaultsFrom(code) { try { const metadata = getMetadata(code); diff --git a/website/src/settings.mjs b/website/src/settings.mjs index f9b9e2810..9b73d38b6 100644 --- a/website/src/settings.mjs +++ b/website/src/settings.mjs @@ -28,6 +28,7 @@ export const defaultSettings = { panelPosition: 'right', userPatterns: '{}', audioDeviceName: defaultAudioDeviceName, + audioVolume: 50 }; let search = null;