mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-11 05:38:34 +00:00
Merge pull request #1344 from daslyfe/multichannelorbits
feat: Multi Channel Orbits
This commit is contained in:
commit
2b44fc48a4
@ -22,6 +22,12 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let multiChannelOrbits = false;
|
||||||
|
export function setMultiChannelOrbits(bool) {
|
||||||
|
multiChannelOrbits = bool == true;
|
||||||
|
}
|
||||||
|
|
||||||
export const soundMap = map();
|
export const soundMap = map();
|
||||||
|
|
||||||
export function registerSound(key, onTrigger, data = {}) {
|
export function registerSound(key, onTrigger, data = {}) {
|
||||||
@ -195,8 +201,15 @@ 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, maxPolyphony, audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME } = options;
|
const {
|
||||||
|
disableWorklets = false,
|
||||||
|
maxPolyphony,
|
||||||
|
audioDeviceName = DEFAULT_AUDIO_DEVICE_NAME,
|
||||||
|
multiChannelOrbits = false,
|
||||||
|
} = options;
|
||||||
|
|
||||||
setMaxPolyphony(maxPolyphony);
|
setMaxPolyphony(maxPolyphony);
|
||||||
|
setMultiChannelOrbits(multiChannelOrbits);
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -277,7 +290,7 @@ export const connectToDestination = (input, channels = [0, 1]) => {
|
|||||||
});
|
});
|
||||||
stereoMix.connect(splitter);
|
stereoMix.connect(splitter);
|
||||||
channels.forEach((ch, i) => {
|
channels.forEach((ch, i) => {
|
||||||
splitter.connect(channelMerger, i % stereoMix.channelCount, clamp(ch, 0, ctx.destination.channelCount - 1));
|
splitter.connect(channelMerger, i % stereoMix.channelCount, ch % ctx.destination.channelCount);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -290,7 +303,7 @@ export const panic = () => {
|
|||||||
channelMerger == null;
|
channelMerger == null;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDelay(orbit, delaytime, delayfeedback, t) {
|
function getDelay(orbit, delaytime, delayfeedback, t, channels) {
|
||||||
if (delayfeedback > maxfeedback) {
|
if (delayfeedback > maxfeedback) {
|
||||||
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
|
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
|
||||||
}
|
}
|
||||||
@ -299,7 +312,7 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
|
|||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
||||||
dly.start?.(t); // for some reason, this throws when audion extension is installed..
|
dly.start?.(t); // for some reason, this throws when audion extension is installed..
|
||||||
connectToDestination(dly, [0, 1]);
|
connectToDestination(dly, channels);
|
||||||
delays[orbit] = dly;
|
delays[orbit] = dly;
|
||||||
}
|
}
|
||||||
delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t);
|
delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t);
|
||||||
@ -356,12 +369,12 @@ function getFilterType(ftype) {
|
|||||||
|
|
||||||
let reverbs = {};
|
let reverbs = {};
|
||||||
let hasChanged = (now, before) => now !== undefined && now !== before;
|
let hasChanged = (now, before) => now !== undefined && now !== before;
|
||||||
function getReverb(orbit, duration, fade, lp, dim, ir) {
|
function getReverb(orbit, duration, fade, lp, dim, ir, channels) {
|
||||||
// If no reverb has been created for a given orbit, create one
|
// If no reverb has been created for a given orbit, create one
|
||||||
if (!reverbs[orbit]) {
|
if (!reverbs[orbit]) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
||||||
connectToDestination(reverb, [0, 1]);
|
connectToDestination(reverb, channels);
|
||||||
reverbs[orbit] = reverb;
|
reverbs[orbit] = reverb;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@ -428,6 +441,11 @@ export function resetGlobalEffects() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let activeSoundSources = new Map();
|
let activeSoundSources = new Map();
|
||||||
|
//music programs/audio gear usually increments inputs/outputs from 1, we need to subtract 1 from the input because the webaudio API channels start at 0
|
||||||
|
|
||||||
|
function mapChannelNumbers(channels) {
|
||||||
|
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||||
|
}
|
||||||
|
|
||||||
export const superdough = async (value, t, hapDuration) => {
|
export const superdough = async (value, t, hapDuration) => {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
@ -490,7 +508,7 @@ export const superdough = async (value, t, hapDuration) => {
|
|||||||
bpsustain,
|
bpsustain,
|
||||||
bprelease,
|
bprelease,
|
||||||
bandq = getDefaultValue('bandq'),
|
bandq = getDefaultValue('bandq'),
|
||||||
channels = getDefaultValue('channels'),
|
|
||||||
//phaser
|
//phaser
|
||||||
phaserrate: phaser,
|
phaserrate: phaser,
|
||||||
phaserdepth = getDefaultValue('phaserdepth'),
|
phaserdepth = getDefaultValue('phaserdepth'),
|
||||||
@ -526,6 +544,11 @@ export const superdough = async (value, t, hapDuration) => {
|
|||||||
compressorRelease,
|
compressorRelease,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
|
const orbitChannels = mapChannelNumbers(
|
||||||
|
multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'),
|
||||||
|
);
|
||||||
|
const channels = value.channels != null ? mapChannelNumbers(value.channels) : orbitChannels;
|
||||||
|
|
||||||
gain = applyGainCurve(nanFallback(gain, 1));
|
gain = applyGainCurve(nanFallback(gain, 1));
|
||||||
postgain = applyGainCurve(postgain);
|
postgain = applyGainCurve(postgain);
|
||||||
shapevol = applyGainCurve(shapevol);
|
shapevol = applyGainCurve(shapevol);
|
||||||
@ -547,9 +570,6 @@ export const superdough = async (value, t, hapDuration) => {
|
|||||||
activeSoundSources.delete(chainID);
|
activeSoundSources.delete(chainID);
|
||||||
}
|
}
|
||||||
|
|
||||||
//music programs/audio gear usually increments inputs/outputs from 1, so imitate that behavior
|
|
||||||
channels = (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
|
||||||
|
|
||||||
let audioNodes = [];
|
let audioNodes = [];
|
||||||
|
|
||||||
if (bank && s) {
|
if (bank && s) {
|
||||||
@ -699,7 +719,7 @@ export const superdough = async (value, t, hapDuration) => {
|
|||||||
// delay
|
// delay
|
||||||
let delaySend;
|
let delaySend;
|
||||||
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
||||||
const delyNode = getDelay(orbit, delaytime, delayfeedback, t);
|
const delyNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
|
||||||
delaySend = effectSend(post, delyNode, delay);
|
delaySend = effectSend(post, delyNode, delay);
|
||||||
audioNodes.push(delaySend);
|
audioNodes.push(delaySend);
|
||||||
}
|
}
|
||||||
@ -717,7 +737,7 @@ export const superdough = async (value, t, hapDuration) => {
|
|||||||
}
|
}
|
||||||
roomIR = await loadBuffer(url, ac, ir, 0);
|
roomIR = await loadBuffer(url, ac, ir, 0);
|
||||||
}
|
}
|
||||||
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR);
|
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, orbitChannels);
|
||||||
reverbSend = effectSend(post, reverbNode, room);
|
reverbSend = effectSend(post, reverbNode, room);
|
||||||
audioNodes.push(reverbSend);
|
audioNodes.push(reverbSend);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ 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';
|
import { DEFAULT_MAX_POLYPHONY, setMaxPolyphony, setMultiChannelOrbits } from '@strudel/webaudio';
|
||||||
|
|
||||||
function Checkbox({ label, value, onChange, disabled = false }) {
|
function Checkbox({ label, value, onChange, disabled = false }) {
|
||||||
return (
|
return (
|
||||||
@ -108,6 +108,7 @@ export function SettingsTab({ started }) {
|
|||||||
audioEngineTarget,
|
audioEngineTarget,
|
||||||
togglePanelTrigger,
|
togglePanelTrigger,
|
||||||
maxPolyphony,
|
maxPolyphony,
|
||||||
|
multiChannelOrbits,
|
||||||
} = useSettings();
|
} = useSettings();
|
||||||
const shouldAlwaysSync = isUdels();
|
const shouldAlwaysSync = isUdels();
|
||||||
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
const canChangeAudioDevice = AudioContext.prototype.setSinkId != null;
|
||||||
@ -162,6 +163,22 @@ export function SettingsTab({ started }) {
|
|||||||
value={maxPolyphony ?? ''}
|
value={maxPolyphony ?? ''}
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
<FormItem>
|
||||||
|
<Checkbox
|
||||||
|
label="Multi Channel Orbits"
|
||||||
|
onChange={(cbEvent) => {
|
||||||
|
const val = cbEvent.target.checked;
|
||||||
|
confirmDialog(RELOAD_MSG).then((r) => {
|
||||||
|
if (r == true) {
|
||||||
|
settingsMap.setKey('multiChannelOrbits', val);
|
||||||
|
setMultiChannelOrbits(val);
|
||||||
|
return window.location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
value={multiChannelOrbits}
|
||||||
|
/>
|
||||||
|
</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>
|
||||||
|
|||||||
@ -18,7 +18,7 @@ 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';
|
||||||
import { settingsMap, useSettings } from '../settings.mjs';
|
import { parseBoolean, settingsMap, useSettings } from '../settings.mjs';
|
||||||
import {
|
import {
|
||||||
setActivePattern,
|
setActivePattern,
|
||||||
setLatestCode,
|
setLatestCode,
|
||||||
@ -36,11 +36,15 @@ 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, audioDeviceName } = settingsMap.get();
|
const { latestCode, maxPolyphony, audioDeviceName, multiChannelOrbits } = 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, audioDeviceName });
|
audioReady = initAudioOnFirstClick({
|
||||||
|
maxPolyphony,
|
||||||
|
audioDeviceName,
|
||||||
|
multiChannelOrbits: parseBoolean(multiChannelOrbits),
|
||||||
|
});
|
||||||
modulesLoading = loadModules();
|
modulesLoading = loadModules();
|
||||||
presets = prebake();
|
presets = prebake();
|
||||||
drawContext = getDrawContext();
|
drawContext = getDrawContext();
|
||||||
|
|||||||
@ -38,6 +38,7 @@ export const defaultSettings = {
|
|||||||
isButtonRowHidden: false,
|
isButtonRowHidden: false,
|
||||||
isCSSAnimationDisabled: false,
|
isCSSAnimationDisabled: false,
|
||||||
maxPolyphony: 128,
|
maxPolyphony: 128,
|
||||||
|
multiChannelOrbits: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
let search = null;
|
let search = null;
|
||||||
@ -50,7 +51,7 @@ const settings_key = `strudel-settings${instance > 0 ? instance : ''}`;
|
|||||||
|
|
||||||
export const settingsMap = persistentMap(settings_key, defaultSettings);
|
export const settingsMap = persistentMap(settings_key, defaultSettings);
|
||||||
|
|
||||||
const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
|
export const parseBoolean = (booleanlike) => ([true, 'true'].includes(booleanlike) ? true : false);
|
||||||
|
|
||||||
export function useSettings() {
|
export function useSettings() {
|
||||||
const state = useStore(settingsMap);
|
const state = useStore(settingsMap);
|
||||||
@ -81,6 +82,7 @@ export function useSettings() {
|
|||||||
isPanelPinned: parseBoolean(state.isPanelPinned),
|
isPanelPinned: parseBoolean(state.isPanelPinned),
|
||||||
isPanelOpen: parseBoolean(state.isPanelOpen),
|
isPanelOpen: parseBoolean(state.isPanelOpen),
|
||||||
userPatterns: userPatterns,
|
userPatterns: userPatterns,
|
||||||
|
multiChannelOrbits: parseBoolean(state.multiChannelOrbits),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user