settings panel

This commit is contained in:
Jade (Rose) Rowland 2025-03-30 20:22:24 -04:00
parent 6f9bcc53dc
commit d8e4055c6e
4 changed files with 40 additions and 14 deletions

View File

@ -14,9 +14,10 @@ import { map } from 'nanostores';
import { logger } from './logger.mjs'; import { logger } from './logger.mjs';
import { loadBuffer } from './sampler.mjs'; import { loadBuffer } from './sampler.mjs';
let maxPolyphony = 128; export const DEFAULT_MAX_POLYPHONY = 128;
let maxPolyphony = DEFAULT_MAX_POLYPHONY;
export function setMaxPolyphony(polyphony) { export function setMaxPolyphony(polyphony) {
maxPolyphony = polyphony; maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY;
} }
export const soundMap = map(); export const soundMap = map();
@ -166,8 +167,8 @@ function loadWorklets() {
// this function should be called on first user interaction (to avoid console warning) // this function should be called on first user interaction (to avoid console warning)
export async function initAudio(options = {}) { export async function initAudio(options = {}) {
const { disableWorklets = false, polyphony = maxPolyphony } = options; const { disableWorklets = false, maxPolyphony } = options;
setMaxPolyphony(polyphony); setMaxPolyphony(maxPolyphony);
if (typeof window === 'undefined') { if (typeof window === 'undefined') {
return; return;
} }
@ -487,7 +488,7 @@ export const superdough = async (value, t, hapDuration) => {
const ch = activeSoundSources.entries().next(); const ch = activeSoundSources.entries().next();
const source = ch.value[1]; const source = ch.value[1];
const chainID = ch.value[0]; const chainID = ch.value[0];
const endTime = t + .25; const endTime = t + 0.25;
source?.node?.gain?.linearRampToValueAtTime(0, endTime); source?.node?.gain?.linearRampToValueAtTime(0, endTime);
source?.stop?.(endTime); source?.stop?.(endTime);
activeSoundSources.delete(chainID); activeSoundSources.delete(chainID);

View File

@ -25,6 +25,10 @@ const getFrequencyFromValue = (value) => {
return Number(freq); return Number(freq);
}; };
function destroyAudioWorkletNode(node) {
node.disconnect();
node.parameters.get('end')?.setValueAtTime(0, 0);
}
const waveforms = ['triangle', 'square', 'sawtooth', 'sine']; const waveforms = ['triangle', 'square', 'sawtooth', 'sine'];
const noises = ['pink', 'white', 'brown', 'crackle']; const noises = ['pink', 'white', 'brown', 'crackle'];
@ -117,7 +121,7 @@ export function registerSynthSounds() {
let timeoutNode = webAudioTimeout( let timeoutNode = webAudioTimeout(
ac, ac,
() => { () => {
o.disconnect(); destroyAudioWorkletNode(o);
envGain.disconnect(); envGain.disconnect();
onended(); onended();
fm?.stop(); fm?.stop();
@ -173,13 +177,12 @@ export function registerSynthSounds() {
let envGain = gainNode(1); let envGain = gainNode(1);
envGain = o.connect(envGain); envGain = o.connect(envGain);
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear'); getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
let timeoutNode = webAudioTimeout( let timeoutNode = webAudioTimeout(
ac, ac,
() => { () => {
o.disconnect(); destroyAudioWorkletNode(o);
envGain.disconnect(); envGain.disconnect();
onended(); onended();
fm?.stop(); fm?.stop();
@ -237,7 +240,7 @@ export function registerSynthSounds() {
return { return {
node, node,
stop: (endTime) => { stop: (endTime) => {
stop(endTime) stop(endTime);
}, },
}; };
}, },
@ -308,7 +311,6 @@ export function getOscillator(s, t, value) {
return { return {
node: noiseMix?.node || o, node: noiseMix?.node || o,
stop: (time) => { stop: (time) => {
// console.info(time)
fmModulator.stop(time); fmModulator.stop(time);
vibratoOscillator?.stop(time); vibratoOscillator?.stop(time);
noiseMix?.stop(time); noiseMix?.stop(time);

View File

@ -663,9 +663,6 @@ registerProcessor('phase-vocoder-processor', PhaseVocoderProcessor);
class PulseOscillatorProcessor extends AudioWorkletProcessor { class PulseOscillatorProcessor extends AudioWorkletProcessor {
constructor() { constructor() {
super(); super();
// this.port.onmessage = (event) => {
// console.info(event)
// };
this.pi = _PI; this.pi = _PI;
this.phi = -this.pi; // phase this.phi = -this.pi; // phase
this.Y0 = 0; // feedback memories this.Y0 = 0; // feedback memories
@ -713,6 +710,9 @@ class PulseOscillatorProcessor extends AudioWorkletProcessor {
} }
process(inputs, outputs, params) { process(inputs, outputs, params) {
if (this.disconnected) {
return false;
}
if (currentTime <= params.begin[0]) { if (currentTime <= params.begin[0]) {
return true; return true;
} }

View File

@ -1,10 +1,12 @@
import { defaultSettings, settingsMap, useSettings } from '../../../settings.mjs'; import { defaultSettings, settingsMap, useSettings } from '../../../settings.mjs';
import { themes } from '@strudel/codemirror'; import { themes } from '@strudel/codemirror';
import { Textbox } from '../textbox/Textbox.jsx';
import { isUdels } from '../../util.mjs'; import { isUdels } from '../../util.mjs';
import { ButtonGroup } from './Forms.jsx'; import { ButtonGroup } from './Forms.jsx';
import { AudioDeviceSelector } from './AudioDeviceSelector.jsx'; import { AudioDeviceSelector } from './AudioDeviceSelector.jsx';
import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx'; import { AudioEngineTargetSelector } from './AudioEngineTargetSelector.jsx';
import { confirmDialog } from '../../util.mjs'; import { confirmDialog } from '../../util.mjs';
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony } from '@strudel/webaudio';
function Checkbox({ label, value, onChange, disabled = false }) { function Checkbox({ label, value, onChange, disabled = false }) {
return ( return (
@ -53,7 +55,7 @@ function NumberSlider({ value, onChange, step = 1, ...rest }) {
); );
} }
function FormItem({ label, children }) { function FormItem({ label, children, sublabel }) {
return ( return (
<div className="grid gap-2"> <div className="grid gap-2">
<label>{label}</label> <label>{label}</label>
@ -105,6 +107,7 @@ export function SettingsTab({ started }) {
audioDeviceName, audioDeviceName,
audioEngineTarget, audioEngineTarget,
togglePanelTrigger, togglePanelTrigger,
maxPolyphony,
} = useSettings(); } = useSettings();
const shouldAlwaysSync = isUdels(); const shouldAlwaysSync = isUdels();
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null; const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
@ -139,6 +142,26 @@ export function SettingsTab({ started }) {
}} }}
/> />
</FormItem> </FormItem>
<FormItem label="Maximum Polyphony">
<Textbox
min={1}
max={Infinity}
onBlur={(e) => {
let v = parseInt(e.target.value);
v = isNaN(v) ? DEFAULT_MAX_POLYPHONY : v;
setMaxPolyphony(v);
settingsMap.setKey('maxPolyphony', v);
}}
onChange={(v) => {
v = Math.max(1, parseInt(v));
settingsMap.setKey('maxPolyphony', isNaN(v) ? undefined : v);
}}
type="number"
placeholder=""
value={maxPolyphony ?? ''}
/>
</FormItem>
<FormItem label="Theme"> <FormItem label="Theme">
<SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} /> <SelectInput options={themeOptions} value={theme} onChange={(theme) => settingsMap.setKey('theme', theme)} />
</FormItem> </FormItem>