mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 13:48:40 +00:00
Merge pull request #1322 from daslyfe/jade/fixmultichannelaudio
FIX: Multichannel Audio
This commit is contained in:
commit
b64daf6284
@ -15,6 +15,8 @@ import { logger } from './logger.mjs';
|
|||||||
import { loadBuffer } from './sampler.mjs';
|
import { loadBuffer } from './sampler.mjs';
|
||||||
|
|
||||||
export const DEFAULT_MAX_POLYPHONY = 128;
|
export const DEFAULT_MAX_POLYPHONY = 128;
|
||||||
|
const DEFAULT_AUDIO_DEVICE_NAME = 'System Standard';
|
||||||
|
|
||||||
let maxPolyphony = DEFAULT_MAX_POLYPHONY;
|
let maxPolyphony = DEFAULT_MAX_POLYPHONY;
|
||||||
export function setMaxPolyphony(polyphony) {
|
export function setMaxPolyphony(polyphony) {
|
||||||
maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY;
|
maxPolyphony = parseInt(polyphony) ?? DEFAULT_MAX_POLYPHONY;
|
||||||
@ -91,6 +93,18 @@ export function getSound(s) {
|
|||||||
return soundMap.get()[s.toLowerCase()];
|
return soundMap.get()[s.toLowerCase()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getAudioDevices = async () => {
|
||||||
|
await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
|
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
|
||||||
|
mediaDevices = mediaDevices.filter((device) => device.kind === 'audiooutput' && device.deviceId !== 'default');
|
||||||
|
const devicesMap = new Map();
|
||||||
|
devicesMap.set(DEFAULT_AUDIO_DEVICE_NAME, '');
|
||||||
|
mediaDevices.forEach((device) => {
|
||||||
|
devicesMap.set(device.label, device.deviceId);
|
||||||
|
});
|
||||||
|
return devicesMap;
|
||||||
|
};
|
||||||
|
|
||||||
const defaultDefaultValues = {
|
const defaultDefaultValues = {
|
||||||
s: 'triangle',
|
s: 'triangle',
|
||||||
gain: 0.8,
|
gain: 0.8,
|
||||||
@ -161,19 +175,40 @@ export function getAudioContextCurrentTime() {
|
|||||||
let workletsLoading;
|
let workletsLoading;
|
||||||
function loadWorklets() {
|
function loadWorklets() {
|
||||||
if (!workletsLoading) {
|
if (!workletsLoading) {
|
||||||
workletsLoading = getAudioContext().audioWorklet.addModule(workletsUrl);
|
const audioCtx = getAudioContext();
|
||||||
|
workletsLoading = audioCtx.audioWorklet.addModule(workletsUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return workletsLoading;
|
return workletsLoading;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, maxPolyphony } = options;
|
const { disableWorklets = false, maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME } = options;
|
||||||
setMaxPolyphony(maxPolyphony);
|
setMaxPolyphony(maxPolyphony);
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await getAudioContext().resume();
|
|
||||||
|
const audioCtx = getAudioContext();
|
||||||
|
|
||||||
|
if (audioDeviceName != null && audioDeviceName != DEFAULT_AUDIO_DEVICE_NAME) {
|
||||||
|
try {
|
||||||
|
const devices = await getAudioDevices();
|
||||||
|
const id = devices.get(audioDeviceName);
|
||||||
|
const isValidID = (id ?? '').length > 0;
|
||||||
|
if (audioCtx.sinkId !== id && isValidID) {
|
||||||
|
await audioCtx.setSinkId(id);
|
||||||
|
}
|
||||||
|
logger(
|
||||||
|
`[superdough] Audio Device set to ${audioDeviceName}, it might take a few seconds before audio plays on all output channels`,
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
logger('[superdough] failed to set audio interface', 'warning');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await audioCtx.resume();
|
||||||
if (disableWorklets) {
|
if (disableWorklets) {
|
||||||
logger('[superdough]: AudioWorklets disabled with disableWorklets');
|
logger('[superdough]: AudioWorklets disabled with disableWorklets');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { getAudioDevices, setAudioDevice } from '../../util.mjs';
|
|
||||||
import { SelectInput } from './SelectInput';
|
import { SelectInput } from './SelectInput';
|
||||||
|
import { getAudioDevices } from '@strudel/webaudio';
|
||||||
|
|
||||||
const initdevices = new Map();
|
const initdevices = new Map();
|
||||||
|
|
||||||
@ -21,9 +22,7 @@ export function AudioDeviceSelector({ audioDeviceName, onChange, isDisabled }) {
|
|||||||
if (!devicesInitialized) {
|
if (!devicesInitialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const deviceID = devices.get(deviceName);
|
|
||||||
onChange(deviceName);
|
onChange(deviceName);
|
||||||
setAudioDevice(deviceID);
|
|
||||||
};
|
};
|
||||||
const options = new Map();
|
const options = new Map();
|
||||||
Array.from(devices.keys()).forEach((deviceName) => {
|
Array.from(devices.keys()).forEach((deviceName) => {
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
resetLoadedSounds,
|
resetLoadedSounds,
|
||||||
initAudioOnFirstClick,
|
initAudioOnFirstClick,
|
||||||
} from '@strudel/webaudio';
|
} from '@strudel/webaudio';
|
||||||
import { getAudioDevices, setAudioDevice, setVersionDefaultsFrom } from './util.mjs';
|
import { setVersionDefaultsFrom } from './util.mjs';
|
||||||
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
import { StrudelMirror, defaultSettings } from '@strudel/codemirror';
|
||||||
import { clearHydra } from '@strudel/hydra';
|
import { clearHydra } from '@strudel/hydra';
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
@ -28,7 +28,7 @@ import {
|
|||||||
setViewingPatternData,
|
setViewingPatternData,
|
||||||
} from '../user_pattern_utils.mjs';
|
} from '../user_pattern_utils.mjs';
|
||||||
import { superdirtOutput } from '@strudel/osc/superdirtoutput';
|
import { superdirtOutput } from '@strudel/osc/superdirtoutput';
|
||||||
import { audioEngineTargets, defaultAudioDeviceName } from '../settings.mjs';
|
import { audioEngineTargets } from '../settings.mjs';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { prebake } from './prebake.mjs';
|
import { prebake } from './prebake.mjs';
|
||||||
import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs';
|
import { getRandomTune, initCode, loadModules, shareCode } from './util.mjs';
|
||||||
@ -36,11 +36,11 @@ import './Repl.css';
|
|||||||
import { setInterval, clearInterval } from 'worker-timers';
|
import { setInterval, clearInterval } from 'worker-timers';
|
||||||
import { getMetadata } from '../metadata_parser';
|
import { getMetadata } from '../metadata_parser';
|
||||||
|
|
||||||
const { latestCode, maxPolyphony } = settingsMap.get();
|
const { latestCode, maxPolyphony, audioDeviceName } = settingsMap.get();
|
||||||
let modulesLoading, presets, drawContext, clearCanvas, audioReady;
|
let modulesLoading, presets, drawContext, clearCanvas, audioReady;
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
audioReady = initAudioOnFirstClick({ maxPolyphony });
|
audioReady = initAudioOnFirstClick({ maxPolyphony, audioDeviceName });
|
||||||
modulesLoading = loadModules();
|
modulesLoading = loadModules();
|
||||||
presets = prebake();
|
presets = prebake();
|
||||||
drawContext = getDrawContext();
|
drawContext = getDrawContext();
|
||||||
@ -159,20 +159,6 @@ export function useReplContext() {
|
|||||||
editorRef.current?.updateSettings(editorSettings);
|
editorRef.current?.updateSettings(editorSettings);
|
||||||
}, [_settings]);
|
}, [_settings]);
|
||||||
|
|
||||||
// on first load, set stored audio device if possible
|
|
||||||
useEffect(() => {
|
|
||||||
const { audioDeviceName } = _settings;
|
|
||||||
if (audioDeviceName !== defaultAudioDeviceName) {
|
|
||||||
getAudioDevices().then((devices) => {
|
|
||||||
const deviceID = devices.get(audioDeviceName);
|
|
||||||
if (deviceID == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setAudioDevice(deviceID);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// UI Actions
|
// UI Actions
|
||||||
//
|
//
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { evalScope, hash2code, logger } from '@strudel/core';
|
import { evalScope, hash2code, logger } from '@strudel/core';
|
||||||
import { settingPatterns, defaultAudioDeviceName } from '../settings.mjs';
|
import { settingPatterns } from '../settings.mjs';
|
||||||
import { getAudioContext, initializeAudioOutput, setDefaultAudioContext, setVersionDefaults } from '@strudel/webaudio';
|
import { setVersionDefaults } from '@strudel/webaudio';
|
||||||
import { getMetadata } from '../metadata_parser';
|
import { getMetadata } from '../metadata_parser';
|
||||||
import { isTauri } from '../tauri.mjs';
|
import { isTauri } from '../tauri.mjs';
|
||||||
import './Repl.css';
|
import './Repl.css';
|
||||||
@ -159,38 +159,6 @@ export const isUdels = () => {
|
|||||||
return window.top?.location?.pathname.includes('udels');
|
return window.top?.location?.pathname.includes('udels');
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAudioDevices = async () => {
|
|
||||||
await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
||||||
let mediaDevices = await navigator.mediaDevices.enumerateDevices();
|
|
||||||
mediaDevices = mediaDevices.filter((device) => device.kind === 'audiooutput' && device.deviceId !== 'default');
|
|
||||||
const devicesMap = new Map();
|
|
||||||
devicesMap.set(defaultAudioDeviceName, '');
|
|
||||||
mediaDevices.forEach((device) => {
|
|
||||||
devicesMap.set(device.label, device.deviceId);
|
|
||||||
});
|
|
||||||
return devicesMap;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setAudioDevice = async (id) => {
|
|
||||||
let audioCtx = getAudioContext();
|
|
||||||
if (audioCtx.sinkId === id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await audioCtx.suspend();
|
|
||||||
await audioCtx.close();
|
|
||||||
audioCtx = setDefaultAudioContext();
|
|
||||||
await audioCtx.resume();
|
|
||||||
const isValidID = (id ?? '').length > 0;
|
|
||||||
if (isValidID) {
|
|
||||||
try {
|
|
||||||
await audioCtx.setSinkId(id);
|
|
||||||
} catch {
|
|
||||||
logger('failed to set audio interface', 'warning');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
initializeAudioOutput();
|
|
||||||
};
|
|
||||||
|
|
||||||
export function setVersionDefaultsFrom(code) {
|
export function setVersionDefaultsFrom(code) {
|
||||||
try {
|
try {
|
||||||
const metadata = getMetadata(code);
|
const metadata = getMetadata(code);
|
||||||
|
|||||||
@ -3,8 +3,6 @@ import { useStore } from '@nanostores/react';
|
|||||||
import { register } from '@strudel/core';
|
import { register } from '@strudel/core';
|
||||||
import { isUdels } from './repl/util.mjs';
|
import { isUdels } from './repl/util.mjs';
|
||||||
|
|
||||||
export const defaultAudioDeviceName = 'System Standard';
|
|
||||||
|
|
||||||
export const audioEngineTargets = {
|
export const audioEngineTargets = {
|
||||||
webaudio: 'webaudio',
|
webaudio: 'webaudio',
|
||||||
osc: 'osc',
|
osc: 'osc',
|
||||||
@ -36,7 +34,6 @@ export const defaultSettings = {
|
|||||||
isPanelOpen: true,
|
isPanelOpen: true,
|
||||||
togglePanelTrigger: 'click', //click | hover
|
togglePanelTrigger: 'click', //click | hover
|
||||||
userPatterns: '{}',
|
userPatterns: '{}',
|
||||||
audioDeviceName: defaultAudioDeviceName,
|
|
||||||
audioEngineTarget: audioEngineTargets.webaudio,
|
audioEngineTarget: audioEngineTargets.webaudio,
|
||||||
isButtonRowHidden: false,
|
isButtonRowHidden: false,
|
||||||
isCSSAnimationDisabled: false,
|
isCSSAnimationDisabled: false,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user