mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-10 08:21:58 -04:00
Resets and folding things into processorOptions
This commit is contained in:
@@ -237,6 +237,7 @@ export class StrudelMirror {
|
||||
cmEditor.style.backgroundColor = 'transparent';
|
||||
}
|
||||
const settings = codemirrorSettings.get();
|
||||
this.isPatternHighlightingEnabled = parseBooleans(settings.isPatternHighlightingEnabled);
|
||||
this.setFontSize(settings.fontSize);
|
||||
this.setFontFamily(settings.fontFamily);
|
||||
|
||||
@@ -321,6 +322,9 @@ export class StrudelMirror {
|
||||
flash(this.editor, ms);
|
||||
}
|
||||
highlight(haps, time) {
|
||||
if (!this.isPatternHighlightingEnabled) {
|
||||
return;
|
||||
}
|
||||
highlightMiniLocations(this.editor, time, haps);
|
||||
}
|
||||
setFontSize(size) {
|
||||
@@ -339,6 +343,9 @@ export class StrudelMirror {
|
||||
return;
|
||||
}
|
||||
value = parseBooleans(value);
|
||||
if (key === 'isPatternHighlightingEnabled') {
|
||||
this.isPatternHighlightingEnabled = value;
|
||||
}
|
||||
const newValue = extensions[key](value, this);
|
||||
this.editor.dispatch({
|
||||
effects: compartments[key].reconfigure(newValue),
|
||||
|
||||
@@ -41,21 +41,25 @@ const voicePools = {};
|
||||
|
||||
// Cycles through the pool attempting to find an inactive voice that hasn't been GC'd
|
||||
export const claimVoice = (key) => {
|
||||
voicePools[key] ??= [];
|
||||
voicePools[key] ??= {};
|
||||
const pool = voicePools[key];
|
||||
let node;
|
||||
while (pool.length && !node) {
|
||||
const ref = pool.pop();
|
||||
for (const [id, ref] of Object.entries(pool)) {
|
||||
node = ref?.deref?.();
|
||||
delete pool[id];
|
||||
if (node !== null) break;
|
||||
}
|
||||
node?.reset();
|
||||
return node;
|
||||
};
|
||||
|
||||
// Releases a voice from use and adds to the inactive pool
|
||||
export const releaseVoice = (key, node) => {
|
||||
voicePools[key] ??= [];
|
||||
voicePools[key].push(new WeakRef(node));
|
||||
export const releaseVoice = (key, id, node) => {
|
||||
voicePools[key] ??= {};
|
||||
voicePools[key][id] = new WeakRef(node);
|
||||
};
|
||||
|
||||
export const removeVoice = (key, id) => {
|
||||
delete voicePools[key][id];
|
||||
};
|
||||
|
||||
export const getParamADSR = (
|
||||
@@ -451,8 +455,8 @@ export function applyFM(param, value, begin) {
|
||||
const fastTanh = (x) => {
|
||||
const x2 = x * x;
|
||||
const y = (x * (27 + x2)) / (27 + 9 * x2);
|
||||
return y < -1 ? -1 : (y > 1 ? 1 : y);
|
||||
}
|
||||
return y < -1 ? -1 : y > 1 ? 1 : y;
|
||||
};
|
||||
const __squash = (x) => x / (1 + x); // [0, inf) to [0, 1)
|
||||
const _mod = (n, m) => ((n % m) + m) % m;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
getWorklet,
|
||||
setParams,
|
||||
releaseVoice,
|
||||
removeVoice,
|
||||
noises,
|
||||
webAudioTimeout,
|
||||
} from './helpers.mjs';
|
||||
@@ -173,15 +174,21 @@ export function registerSynthSounds() {
|
||||
begin,
|
||||
end,
|
||||
freqspread: detune,
|
||||
voices,
|
||||
panspread,
|
||||
};
|
||||
const processorOptions = {
|
||||
voices,
|
||||
};
|
||||
if (o == null) {
|
||||
console.log('Building');
|
||||
o = getWorklet(ac, 'supersaw-oscillator', params, {
|
||||
outputChannelCount: [2],
|
||||
processorOptions,
|
||||
});
|
||||
} else {
|
||||
console.log('Reusing');
|
||||
setParams(o, params);
|
||||
o.port.postMessage({ type: 'reset', payload: { processorOptions } });
|
||||
}
|
||||
const gainAdjustment = 1 / Math.sqrt(voices);
|
||||
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
||||
@@ -202,8 +209,14 @@ export function registerSynthSounds() {
|
||||
const timeoutNode = webAudioTimeout(
|
||||
ac,
|
||||
() => {
|
||||
const id = Math.floor(Math.random() * 10000);
|
||||
destroyAudioWorkletNode(o);
|
||||
releaseVoice('supersaw', o);
|
||||
releaseVoice('supersaw', id, o);
|
||||
o.port.onmessage = (e) => {
|
||||
if (e.data.type === 'finished') {
|
||||
removeVoice('supersaw', id);
|
||||
}
|
||||
};
|
||||
onended();
|
||||
fm?.stop();
|
||||
vibratoOscillator?.stop();
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getWorklet,
|
||||
setParams,
|
||||
releaseVoice,
|
||||
removeVoice,
|
||||
webAudioTimeout,
|
||||
destroyAudioWorkletNode,
|
||||
} from './helpers.mjs';
|
||||
@@ -237,20 +238,28 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
||||
position: value.wt,
|
||||
warp: value.warp,
|
||||
warpMode: warpmode,
|
||||
voices: Math.max(value.unison ?? 1, 1),
|
||||
panspread: value.spread,
|
||||
phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0,
|
||||
};
|
||||
const processorOptions = {
|
||||
phaseRand: value.wtphaserand ?? (value.unison > 1 ? 1 : 0),
|
||||
voices: Math.max(value.unison ?? 1, 1),
|
||||
};
|
||||
if (source == null) {
|
||||
source = getWorklet(ac, 'wavetable-oscillator-processor', params, { outputChannelCount: [2] });
|
||||
source = getWorklet(ac, 'wavetable-oscillator-processor', params, { outputChannelCount: [2], processorOptions });
|
||||
} else {
|
||||
setParams(source, params);
|
||||
source.port.postMessage({ type: 'reset', payload: { processorOptions } });
|
||||
}
|
||||
source.port.postMessage({ type: 'table', payload });
|
||||
if (ac.currentTime > t) {
|
||||
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
||||
destroyAudioWorkletNode(source);
|
||||
releaseVoice(voiceKey, source);
|
||||
source.port.onmessage = (e) => {
|
||||
if (e.data.type === 'finished') {
|
||||
removeVoice(voiceKey, id);
|
||||
}
|
||||
};
|
||||
return;
|
||||
}
|
||||
const posADSRParams = [value.wtattack, value.wtdecay, value.wtsustain, value.wtrelease];
|
||||
@@ -328,7 +337,13 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
||||
wtPosModulators?.disconnect();
|
||||
wtWarpModulators?.disconnect();
|
||||
destroyAudioWorkletNode(source);
|
||||
releaseVoice(voiceKey, source);
|
||||
const id = Math.floor(Math.random() * 10000);
|
||||
releaseVoice(voiceKey, id, source);
|
||||
source.port.onmessage = (e) => {
|
||||
if (e.data.type === 'finished') {
|
||||
removeVoice(voiceKey, id);
|
||||
}
|
||||
};
|
||||
onended();
|
||||
},
|
||||
t,
|
||||
|
||||
@@ -470,13 +470,17 @@ registerProcessor('distort-processor', DistortProcessor);
|
||||
|
||||
// SUPERSAW
|
||||
class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
constructor() {
|
||||
constructor(options) {
|
||||
super();
|
||||
this.phases = new Float32Array(MAX_VOICES);
|
||||
this.reset(options);
|
||||
this.reset(options.processorOptions);
|
||||
}
|
||||
reset(options) {
|
||||
this.phases.map(() => Math.random());
|
||||
const phases = this.phases;
|
||||
for (let i = 0; i < phases.length; i++) {
|
||||
phases[i] = Math.random();
|
||||
}
|
||||
this.voices = options.voices;
|
||||
}
|
||||
static get parameterDescriptors() {
|
||||
return [
|
||||
@@ -513,13 +517,6 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
},
|
||||
{
|
||||
name: 'voices',
|
||||
defaultValue: 5,
|
||||
min: 1,
|
||||
max: MAX_VOICES,
|
||||
automationRate: 'k-rate',
|
||||
},
|
||||
{
|
||||
name: 'postgain',
|
||||
defaultValue: 1,
|
||||
@@ -529,20 +526,20 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
process(_input, outputs, params) {
|
||||
const started = currentTime >= params.begin[0];
|
||||
const end = params.end[0];
|
||||
const ended = currentTime >= end + 1;
|
||||
const inGracePeriod = (currentTime >= end) && !ended;
|
||||
if (started|| inGracePeriod) {
|
||||
const ended = currentTime >= end + 5;
|
||||
const inGracePeriod = currentTime >= end && !ended;
|
||||
if (!started || inGracePeriod) {
|
||||
return true;
|
||||
} else if (ended) {
|
||||
this.port.postMessage({ type: 'finished' });
|
||||
return false;
|
||||
}
|
||||
const output = outputs[0];
|
||||
const voices = params.voices[0]; // k-rate
|
||||
// fast k-rate paths
|
||||
let freq, detune, freqspread, detuner;
|
||||
if (params.freqspread.length === 1) {
|
||||
freqspread = params.freqspread[0];
|
||||
detuner = getDetuner(voices, freqspread);
|
||||
detuner = getDetuner(this.voices, freqspread);
|
||||
}
|
||||
if (params.frequency.length === 1 && params.detune.length === 1) {
|
||||
freq = params.frequency[0];
|
||||
@@ -551,7 +548,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
let panspread, gainL, gainR;
|
||||
if (params.panspread.length === 1) {
|
||||
panspread = voices > 1 ? clamp(pv(params.panspread, i), 0, 1) : 0;
|
||||
panspread = this.voices > 1 ? clamp(params.panspread[0], 0, 1) : 0;
|
||||
gainL = Math.sqrt(0.5 - 0.5 * panspread);
|
||||
gainR = Math.sqrt(0.5 + 0.5 * panspread);
|
||||
}
|
||||
@@ -564,8 +561,8 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
detune ??= pv(params.detune, i);
|
||||
freqspread ??= pv(params.freqspread, i);
|
||||
freq ??= applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100);
|
||||
detuner ??= getDetuner(voices, freqspread);
|
||||
for (let n = 0; n < voices; n++) {
|
||||
detuner ??= getDetuner(this.voices, freqspread);
|
||||
for (let n = 0; n < this.voices; n++) {
|
||||
// Individual voice detuning
|
||||
const freqVoice = applyFastDetune(freq, detuner(n));
|
||||
// We must wrap this here because it is passed into sawblep below which
|
||||
@@ -574,12 +571,13 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
const v = waveshapes.sawblep(this.phases[n], dt);
|
||||
output[0][i] += v * gainL;
|
||||
output[1][i] += v * gainR;
|
||||
let pn = this.phase[n] + dt;
|
||||
let pn = this.phases[n] + dt;
|
||||
if (pn >= 1.0) pn -= 1.0;
|
||||
this.phases[n] = pn;
|
||||
// invert right and left gain
|
||||
const tmp = gainL;
|
||||
gainL = gainR;
|
||||
gainR = gainL;
|
||||
gainR = tmp;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -1160,7 +1158,6 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
{ name: 'position', defaultValue: 0, min: 0, max: 1 },
|
||||
{ name: 'warp', defaultValue: 0, min: 0, max: 1 },
|
||||
{ name: 'warpMode', defaultValue: 0 },
|
||||
{ name: 'voices', defaultValue: 1, min: 1, automationRate: 'k-rate' },
|
||||
{ name: 'panspread', defaultValue: 0.7, min: 0, max: 1 },
|
||||
{ name: 'postgain', defaultValue: 1, min: 0 },
|
||||
];
|
||||
@@ -1196,13 +1193,21 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
this.tables = tablesCache[key];
|
||||
this.numFrames = this.tables[0].length;
|
||||
} else if (type === 'reset') {
|
||||
this.reset(payload.processorOptions);
|
||||
}
|
||||
};
|
||||
this.reset(options);
|
||||
this.reset(options.processorOptions);
|
||||
}
|
||||
|
||||
reset(options) {
|
||||
this.phases.map(() => Math.random() * (options.processorOptions.phaseRand ?? 1));
|
||||
const phaseRand = options.phaseRand ?? 1;
|
||||
const phases = this.phases;
|
||||
for (let i = 0; i < phases.length; i++) {
|
||||
phases[i] = Math.random() * phaseRand;
|
||||
}
|
||||
this.voices = options.voices;
|
||||
this.voicesDenom = 1 / Math.sqrt(this.voices);
|
||||
}
|
||||
|
||||
_mirror(x) {
|
||||
@@ -1359,22 +1364,20 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
process(_inputs, outputs, params) {
|
||||
const started = currentTime >= params.begin[0];
|
||||
const end = params.end[0];
|
||||
const ended = currentTime >= end + 1;
|
||||
const inGracePeriod = (currentTime >= end) && !ended;
|
||||
if (!this.tables || started || inGracePeriod) {
|
||||
const ended = currentTime >= end + 5;
|
||||
const inGracePeriod = currentTime >= end && !ended;
|
||||
if (!this.tables || !started || inGracePeriod) {
|
||||
return true;
|
||||
} else if (ended) {
|
||||
return false;
|
||||
}
|
||||
const outL = outputs[0][0];
|
||||
const outR = outputs[0][1] || outputs[0][0];
|
||||
const voices = parameters.voices[0]; // k-rate
|
||||
const voicesDenom = 1 / Math.sqrt(voices);
|
||||
// fast k-rate paths
|
||||
let freq, detune, freqspread, detuner;
|
||||
if (params.freqspread.length === 1) {
|
||||
freqspread = params.freqspread[0];
|
||||
detuner = getDetuner(voices, freqspread);
|
||||
detuner = getDetuner(this.voices, freqspread);
|
||||
}
|
||||
if (params.frequency.length === 1 && params.detune.length === 1) {
|
||||
freq = params.frequency[0];
|
||||
@@ -1383,7 +1386,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
let panspread, gainL, gainR;
|
||||
if (params.panspread.length === 1) {
|
||||
panspread = voices > 1 ? clamp(pv(params.panspread, i), 0, 1) : 0;
|
||||
panspread = this.voices > 1 ? clamp(params.panspread[0], 0, 1) : 0;
|
||||
gainL = Math.sqrt(0.5 - 0.5 * panspread);
|
||||
gainR = Math.sqrt(0.5 + 0.5 * panspread);
|
||||
}
|
||||
@@ -1395,34 +1398,35 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
const warpAmount = clamp(pv(params.warp, i), 0, 1);
|
||||
const warpMode = pv(params.warpMode, i);
|
||||
const postgain = pv(params.postgain, i);
|
||||
const normalizer = postgain * voicesDenom;
|
||||
panspread ??= voices > 1 ? clamp(pv(params.panspread, i), 0, 1) : 0;
|
||||
const normalizer = postgain * this.voicesDenom;
|
||||
panspread ??= this.voices > 1 ? clamp(pv(params.panspread, i), 0, 1) : 0;
|
||||
gainL ??= Math.sqrt(0.5 - 0.5 * panspread);
|
||||
gainR ??= Math.sqrt(0.5 + 0.5 * panspread);
|
||||
detune ??= pv(params.detune, i);
|
||||
freqspread ??= pv(params.freqspread, i);
|
||||
freq ??= applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100); // overall detune
|
||||
detuner ??= getDetuner(voices, freqspread);
|
||||
for (let n = 0; n < voices; n++) {
|
||||
const fVoice = applyFastDetune(f, detuner(n)); // voice detune
|
||||
const dPhase = fVoice * INVSR;
|
||||
detuner ??= getDetuner(this.voices, freqspread);
|
||||
for (let n = 0; n < this.voices; n++) {
|
||||
const freqVoice = applyFastDetune(freq, detuner(n)); // voice detune
|
||||
const dPhase = freqVoice * INVSR;
|
||||
const level = this._chooseMip(dPhase);
|
||||
const table = this.tables[level];
|
||||
|
||||
// warp phase then sample
|
||||
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
||||
const ph = this._warpPhase(this.phases[n], warpAmount, warpMode);
|
||||
const s0 = this._sampleFrame(table[fIdx], ph);
|
||||
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
||||
let s = lerp(s0, s1, interpT);
|
||||
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
||||
if (warpMode === WarpMode.FLIP && this.phases[n] < warpAmount) {
|
||||
s = -s;
|
||||
}
|
||||
outL[i] += s * gainL * normalizer;
|
||||
outR[i] += s * gainR * normalizer;
|
||||
this.phase[n] = frac(this.phase[n] + dPhase);
|
||||
this.phases[n] = frac(this.phases[n] + dPhase);
|
||||
// invert right and left gain
|
||||
gainL = gainR;
|
||||
const tmp = gainL;
|
||||
gainR = gainL;
|
||||
gainL = gainR;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
Reference in New Issue
Block a user