mirror of
https://codeberg.org/uzu/strudel
synced 2026-08-01 13:29:25 -04:00
Single silencer node; postgain on the worklets
This commit is contained in:
@@ -339,6 +339,17 @@ export function scheduleAtTime(callback, targetTime, audioContext = getAudioCont
|
||||
const currentTime = audioContext.currentTime;
|
||||
webAudioTimeout(audioContext, callback, currentTime, targetTime);
|
||||
}
|
||||
|
||||
let silencer;
|
||||
function getSilencer() {
|
||||
const ac = getAudioContext();
|
||||
if (!silencer) {
|
||||
silencer = gainNode(0);
|
||||
silencer.connect(ac.destination);
|
||||
}
|
||||
return silencer;
|
||||
}
|
||||
|
||||
// ConstantSource inherits AudioScheduledSourceNode, which has scheduling abilities
|
||||
// a bit of a hack, but it works very well :)
|
||||
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||
@@ -346,18 +357,11 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||
|
||||
// Certain browsers requires audio nodes to be connected in order for their onended events
|
||||
// to fire, so we _mute it_ and then connect it to the destination
|
||||
const zeroGain = gainNode(0);
|
||||
zeroGain.connect(audioContext.destination);
|
||||
const zeroGain = getSilencer();
|
||||
constantNode.connect(zeroGain);
|
||||
|
||||
// Schedule the `onComplete` callback to occur at `stopTime`
|
||||
constantNode.onended = () => {
|
||||
// Ensure garbage collection
|
||||
try {
|
||||
zeroGain.disconnect();
|
||||
} catch {
|
||||
// pass
|
||||
}
|
||||
try {
|
||||
constantNode.disconnect();
|
||||
} catch {
|
||||
|
||||
@@ -183,17 +183,23 @@ export function registerSynthSounds() {
|
||||
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
||||
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
||||
const fm = applyFM(o.parameters.get('frequency'), value, begin);
|
||||
let envGain = gainNode(1);
|
||||
envGain = o.connect(envGain);
|
||||
|
||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear');
|
||||
|
||||
getParamADSR(
|
||||
o.parameters.get('postgain'),
|
||||
attack,
|
||||
decay,
|
||||
sustain,
|
||||
release,
|
||||
0,
|
||||
0.3 * gainAdjustment,
|
||||
begin,
|
||||
holdend,
|
||||
'linear',
|
||||
);
|
||||
const timeoutNode = webAudioTimeout(
|
||||
ac,
|
||||
() => {
|
||||
destroyAudioWorkletNode(o);
|
||||
releaseVoice('supersaw', o);
|
||||
envGain.disconnect();
|
||||
onended();
|
||||
fm?.stop();
|
||||
vibratoOscillator?.stop();
|
||||
@@ -203,7 +209,7 @@ export function registerSynthSounds() {
|
||||
);
|
||||
|
||||
return {
|
||||
node: envGain,
|
||||
node: o,
|
||||
stop: timeoutNode.stop,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -249,6 +249,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
||||
source.port.postMessage({ type: 'table', payload });
|
||||
if (ac.currentTime > t) {
|
||||
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
||||
destroyAudioWorkletNode(source);
|
||||
releaseVoice(voiceKey, source);
|
||||
return;
|
||||
}
|
||||
@@ -316,17 +317,14 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
||||
);
|
||||
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
|
||||
const fm = applyFM(source.parameters.get('frequency'), value, t);
|
||||
const envGain = ac.createGain();
|
||||
const node = source.connect(envGain);
|
||||
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
|
||||
getParamADSR(source.parameters.get('postgain'), attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
|
||||
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
|
||||
const handle = { node, source };
|
||||
const handle = { node: source };
|
||||
const timeoutNode = webAudioTimeout(
|
||||
ac,
|
||||
() => {
|
||||
vibratoOscillator?.stop();
|
||||
fm?.stop();
|
||||
node.disconnect();
|
||||
wtPosModulators?.disconnect();
|
||||
wtWarpModulators?.disconnect();
|
||||
destroyAudioWorkletNode(source);
|
||||
|
||||
@@ -498,13 +498,16 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
},
|
||||
|
||||
{
|
||||
name: 'voices',
|
||||
defaultValue: 5,
|
||||
min: 1,
|
||||
automationRate: 'k-rate',
|
||||
},
|
||||
{
|
||||
name: 'postgain',
|
||||
defaultValue: 1,
|
||||
},
|
||||
];
|
||||
}
|
||||
process(_input, outputs, params) {
|
||||
@@ -520,10 +523,11 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||
const voices = params.voices[0]; // k-rate
|
||||
for (let i = 0; i < output[0].length; i++) {
|
||||
const detune = pv(params.detune, i);
|
||||
const postgain = pv(params.postgain, i);
|
||||
const freqspread = pv(params.freqspread, i);
|
||||
const panspread = pv(params.panspread, i) * 0.5 + 0.5;
|
||||
let gainL = Math.sqrt(1 - panspread);
|
||||
let gainR = Math.sqrt(panspread);
|
||||
let gainL = Math.sqrt(1 - panspread) * postgain;
|
||||
let gainR = Math.sqrt(panspread) * postgain;
|
||||
let freq = pv(params.frequency, i);
|
||||
// Main detuning
|
||||
freq = applySemitoneDetuneToFrequency(freq, detune / 100);
|
||||
@@ -1129,6 +1133,7 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
{ name: 'voices', defaultValue: 1, min: 1, automationRate: 'k-rate' },
|
||||
{ name: 'panspread', defaultValue: 0.7, min: 0, max: 1 },
|
||||
{ name: 'phaserand', defaultValue: 0, min: 0, max: 1 },
|
||||
{ name: 'postgain', defaultValue: 1, min: 0 },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1318,22 +1323,23 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
return level;
|
||||
}
|
||||
|
||||
_zeroOutputs(outL, outR) {
|
||||
outL.fill(0);
|
||||
outR.fill(0);
|
||||
_zeroOutputs(outputs) {
|
||||
outputs[0][0].fill(0);
|
||||
outputs[0][1].fill(0);
|
||||
}
|
||||
|
||||
process(_inputs, outputs, parameters) {
|
||||
const outL = outputs[0][0];
|
||||
const outR = outputs[0][1] || outputs[0][0];
|
||||
const begin = parameters.begin[0];
|
||||
const end = parameters.end[0];
|
||||
|
||||
if (!this.tables || currentTime <= begin || currentTime >= end) {
|
||||
this._zeroOutputs();
|
||||
this._zeroOutputs(outputs);
|
||||
return true;
|
||||
}
|
||||
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);
|
||||
for (let i = 0; i < outL.length; i++) {
|
||||
const detune = pv(parameters.detune, i);
|
||||
const freqspread = pv(parameters.freqspread, i);
|
||||
@@ -1343,13 +1349,14 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
||||
const interpT = idx - fIdx;
|
||||
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
|
||||
const warpMode = pv(parameters.warpMode, i);
|
||||
const postgain = pv(parameters.postgain, i);
|
||||
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
|
||||
const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0;
|
||||
const gain1 = Math.sqrt(0.5 - 0.5 * panspread);
|
||||
const gain2 = Math.sqrt(0.5 + 0.5 * panspread);
|
||||
let f = pv(parameters.frequency, i);
|
||||
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
|
||||
const normalizer = 1 / Math.sqrt(voices);
|
||||
const normalizer = postgain * voicesDenom;
|
||||
const detuner = getDetuner(voices, freqspread);
|
||||
for (let n = 0; n < voices; n++) {
|
||||
const isOdd = (n & 1) == 1;
|
||||
|
||||
Reference in New Issue
Block a user