mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 05:05:26 -04:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c08bd1014 | |||
| e1fc5eec5b | |||
| 07c1a9eb23 | |||
| c0423c1757 | |||
| acd1f9e691 | |||
| 6429b0cb96 | |||
| 765de9daf3 | |||
| 132dea8478 | |||
| b889d19493 | |||
| 71d36b79ed | |||
| d7bf5db8be | |||
| 4374bdf1bd | |||
| 1225d04437 | |||
| f6f507dd29 | |||
| c7586e96ff | |||
| 46291bf85c |
@@ -7,6 +7,7 @@ import {
|
||||
getPitchEnvelope,
|
||||
getVibratoOscillator,
|
||||
onceEnded,
|
||||
releaseAudioNode,
|
||||
} from '@strudel/webaudio';
|
||||
import gm from './gm.mjs';
|
||||
|
||||
@@ -172,9 +173,8 @@ export function registerSoundfonts() {
|
||||
bufferSource.stop(envEnd);
|
||||
const stop = (releaseTime) => {};
|
||||
onceEnded(bufferSource, () => {
|
||||
bufferSource.disconnect();
|
||||
vibratoOscillator?.stop();
|
||||
node.disconnect();
|
||||
releaseAudioNode(bufferSource);
|
||||
releaseAudioNode(vibratoOscillator);
|
||||
onended();
|
||||
});
|
||||
return { node, stop };
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
audioGraph.mjs - Shadow web audio graph used for managing connections
|
||||
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/superdough/audioGraph.mjs>
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { logger } from './logger.mjs';
|
||||
|
||||
// This helper should be used instead of the `node.onended = callback` pattern
|
||||
// It adds a mechanism to help minimize gc retention
|
||||
export const onceEnded = (node, callback) => {
|
||||
const onended = callback;
|
||||
node.onended = function cleanup() {
|
||||
onended && onended();
|
||||
this.onended = null;
|
||||
};
|
||||
};
|
||||
|
||||
export const releaseAudioNode = (node) => {
|
||||
if (node == null) return;
|
||||
|
||||
// check we received an AudioNode
|
||||
if (!(node instanceof AudioNode)) {
|
||||
throw new Error('releaseAudioNode can only release an AudioNode');
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/disconnect
|
||||
node.disconnect();
|
||||
|
||||
// make sure all AudioScheduledSourceNodes are in a stopped state
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/AudioScheduledSourceNode
|
||||
if (node instanceof AudioScheduledSourceNode) {
|
||||
if (node.onended && node.onended.name !== 'cleanup') {
|
||||
logger(
|
||||
`[superdough] Deprecation warning: it seems your code path is setting 'node.onended = callback' instead of using the onceEnded helper`,
|
||||
);
|
||||
}
|
||||
try {
|
||||
node.stop();
|
||||
} catch (e) {
|
||||
// At the stage, `start` was not called on the node
|
||||
// but an `onended` callback releasing resources may exist
|
||||
// and we want it to fire :
|
||||
// - we force a start/stop cycle so that `onended` gets called
|
||||
// - we `lock` the node so that no-one can start it
|
||||
node.start(node.context.currentTime + 5); // will never happen
|
||||
node.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/webaudio-1.1/#AudioNode-actively-processing
|
||||
// An AudioWorkletNode is actively processing when its AudioWorkletProcessor's [[callable process]]
|
||||
// returns true and either its active source flag is true or
|
||||
// any AudioNode connected to one of its inputs is actively processing.
|
||||
if (node instanceof AudioWorkletNode) {
|
||||
// while `end` is not native to the web audio API, it is common practice in superdough
|
||||
// to use that param in the worklets to trigger returning false from the processor
|
||||
node.parameters.get('end')?.setValueAtTime(0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
// Once the `anchor` node has ended, release all nodes in `toCleanup`
|
||||
export const cleanupOnEnd = (anchor, toCleanup) => {
|
||||
onceEnded(anchor, () => toCleanup.forEach((n) => releaseAudioNode(n)));
|
||||
};
|
||||
|
||||
class Edge {
|
||||
constructor(from, to) {
|
||||
this.from = new WeakRef(from);
|
||||
this.to = new WeakRef(to);
|
||||
this.subGraphs = new Set();
|
||||
}
|
||||
disconnect() {
|
||||
const from = this.from.deref();
|
||||
const to = this.to.deref();
|
||||
from && to && from.disconnect(to);
|
||||
}
|
||||
release() {
|
||||
const from = this.from.deref();
|
||||
if (from instanceof AudioNode) {
|
||||
releaseAudioNode(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let audioGraph;
|
||||
class AudioGraph {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
this.activeSubGraphs = [];
|
||||
this.subGraphs = {};
|
||||
this.edges = [];
|
||||
this.subGraphCounter = 0;
|
||||
}
|
||||
|
||||
connect(from, to) {
|
||||
const edge = new Edge(from, to);
|
||||
for (const subGraph of this.activeSubGraphs) {
|
||||
// Track which subgraphs it's in
|
||||
edge.subGraphs.add(subGraph.id);
|
||||
// Add to the subgraph's `edges`
|
||||
subGraph.edges.push(edge);
|
||||
// Add to this' `edges`
|
||||
this.edges.push(edge);
|
||||
}
|
||||
// Make the actual connection
|
||||
return from.connect(to);
|
||||
}
|
||||
|
||||
// Introduces a context wherein all connections will be added to both this graph
|
||||
// and the subgraph and all edges will be tagged with the subgraph for tracking
|
||||
asSubGraph(fn) {
|
||||
const subGraphID = `${this.id}_${this.subGraphCounter}`;
|
||||
this.subGraphCounter++;
|
||||
const subGraph = new AudioGraph(subGraphID);
|
||||
this.subGraphs[subGraphID] = subGraph;
|
||||
this.activeSubGraphs.push(subGraph);
|
||||
try {
|
||||
return { subGraph, output: fn() };
|
||||
} finally {
|
||||
this.activeSubGraphs.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnects all from-to connections (rather than naked `from.disconnect()`)
|
||||
disconnect() {
|
||||
this.edges.forEach((edge) => edge.disconnect());
|
||||
this.edges = [];
|
||||
}
|
||||
|
||||
// Release this entire graph (nodes will be fully disconnected, stopped, etc)
|
||||
release() {
|
||||
this.edges.forEach((edge) => edge.release());
|
||||
this.edges = [];
|
||||
}
|
||||
}
|
||||
|
||||
export const getAudioGraph = () => {
|
||||
if (audioGraph === undefined) {
|
||||
audioGraph = new AudioGraph(0);
|
||||
}
|
||||
return audioGraph;
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import { getAudioGraph, onceEnded, releaseAudioNode } from './audioGraph.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
import { getNoiseBuffer } from './noise.mjs';
|
||||
import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs';
|
||||
@@ -189,7 +190,7 @@ export function applyParameterModulators(audioContext, param, start, end, envelo
|
||||
getParamADSR(param, attack, decay, sustain, release, min, max, start, holdEnd, curve);
|
||||
}
|
||||
const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
|
||||
return { lfo, disconnect: () => lfo?.disconnect() };
|
||||
return lfo;
|
||||
}
|
||||
export function createFilter(context, start, end, params, cps, cycle) {
|
||||
let {
|
||||
@@ -269,27 +270,20 @@ let wetfade = (d) => (d < 0.5 ? 1 : 1 - (d - 0.5) / 0.5);
|
||||
// still not too sure about how this could be used more generally...
|
||||
export function drywet(dry, wet, wetAmount = 0) {
|
||||
const ac = getAudioContext();
|
||||
const ag = getAudioGraph();
|
||||
if (!wetAmount) {
|
||||
return dry;
|
||||
}
|
||||
let dry_gain = ac.createGain();
|
||||
let wet_gain = ac.createGain();
|
||||
dry.connect(dry_gain);
|
||||
wet.connect(wet_gain);
|
||||
ag.connect(dry, dry_gain);
|
||||
ag.connect(wet, wet_gain);
|
||||
dry_gain.gain.value = wetfade(wetAmount);
|
||||
wet_gain.gain.value = wetfade(1 - wetAmount);
|
||||
let mix = ac.createGain();
|
||||
dry_gain.connect(mix);
|
||||
wet_gain.connect(mix);
|
||||
return {
|
||||
node: mix,
|
||||
onended: () => {
|
||||
dry_gain.disconnect(mix);
|
||||
wet_gain.disconnect(mix);
|
||||
dry.disconnect(dry_gain);
|
||||
wet.disconnect(wet_gain);
|
||||
},
|
||||
};
|
||||
const mix = ac.createGain();
|
||||
ag.connect(dry_gain, mix);
|
||||
ag.connect(wet_gain, mix);
|
||||
return { node: mix };
|
||||
}
|
||||
|
||||
let curves = ['linear', 'exponential'];
|
||||
@@ -317,17 +311,14 @@ export function getVibratoOscillator(param, value, t) {
|
||||
const { vibmod = 0.5, vib } = value;
|
||||
let vibratoOscillator;
|
||||
if (vib > 0) {
|
||||
const ag = getAudioGraph();
|
||||
vibratoOscillator = getAudioContext().createOscillator();
|
||||
vibratoOscillator.frequency.value = vib;
|
||||
const gain = getAudioContext().createGain();
|
||||
// Vibmod is the amount of vibrato, in semitones
|
||||
gain.gain.value = vibmod * 100;
|
||||
vibratoOscillator.connect(gain);
|
||||
gain.connect(param);
|
||||
vibratoOscillator.onended = () => {
|
||||
gain.disconnect(param);
|
||||
vibratoOscillator.disconnect(gain);
|
||||
};
|
||||
ag.connect(vibratoOscillator, gain);
|
||||
ag.connect(gain, param);
|
||||
vibratoOscillator.start(t);
|
||||
return vibratoOscillator;
|
||||
}
|
||||
@@ -383,7 +374,7 @@ const fm = (frequencyparam, harmonicityRatio, wave = 'sine') => {
|
||||
|
||||
export function applyFM(param, value, begin) {
|
||||
const ac = getAudioContext();
|
||||
const toStop = []; // fm oscillators we will expose `stop` for
|
||||
const ag = getAudioGraph();
|
||||
const fms = {};
|
||||
// Matrix
|
||||
for (let i = 1; i <= 8; i++) {
|
||||
@@ -410,8 +401,6 @@ export function applyFM(param, value, begin) {
|
||||
if (!fms[idx]) {
|
||||
const idxS = idx === 1 ? '' : idx;
|
||||
const { osc, freq } = fm(param, value[`fmh${idxS}`] ?? 1, value[`fmwave${idxS}`] ?? 'sine');
|
||||
toStop.push(osc);
|
||||
const toCleanup = [osc]; // nodes we want to cleanup after oscillator `stop`
|
||||
const adsr = ['attack', 'decay', 'sustain', 'release'].map((s) => value[`fm${s}${idxS}`]);
|
||||
let output = osc;
|
||||
if (adsr.some((v) => v !== undefined)) {
|
||||
@@ -431,15 +420,13 @@ export function applyFM(param, value, begin) {
|
||||
holdEnd,
|
||||
fmEnvelopeType === 'exp' ? 'exponential' : 'linear',
|
||||
);
|
||||
toCleanup.push(envGain);
|
||||
output = osc.connect(envGain);
|
||||
output = ag.connect(osc, envGain);
|
||||
}
|
||||
fms[idx] = { input: osc.frequency, output, freq, osc, toCleanup };
|
||||
fms[idx] = { input: osc.frequency, output, freq };
|
||||
}
|
||||
const { input, output, freq, osc, toCleanup } = fms[idx];
|
||||
const { input, output, freq } = fms[idx];
|
||||
const g = gainNode(amt * freq);
|
||||
io.push(isMod ? output.connect(g) : input);
|
||||
cleanupOnEnd(osc, [...toCleanup, g]);
|
||||
io.push(isMod ? ag.connect(output, g) : input);
|
||||
}
|
||||
if (!io[1]) {
|
||||
logger(
|
||||
@@ -448,12 +435,9 @@ export function applyFM(param, value, begin) {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
io[0].connect(io[1]);
|
||||
ag.connect(io[0], io[1]);
|
||||
}
|
||||
}
|
||||
return {
|
||||
stop: (t) => toStop.forEach((m) => m?.stop(t)),
|
||||
};
|
||||
}
|
||||
|
||||
// Saturation curves
|
||||
@@ -565,59 +549,3 @@ export const getFrequencyFromValue = (value, defaultNote = 36) => {
|
||||
freq *= Math.pow(2, octave);
|
||||
return Number(freq);
|
||||
};
|
||||
|
||||
// This helper should be used instead of the `node.onended = callback` pattern
|
||||
// It adds a mechanism to help minimize gc retention
|
||||
export const onceEnded = (node, callback) => {
|
||||
const onended = callback;
|
||||
node.onended = function cleanup() {
|
||||
onended && onended();
|
||||
this.onended = null;
|
||||
};
|
||||
};
|
||||
|
||||
export const releaseAudioNode = (node) => {
|
||||
if (node == null) return;
|
||||
|
||||
// check we received an AudioNode
|
||||
if (!(node instanceof AudioNode)) {
|
||||
throw new Error('releaseAudioNode can only release an AudioNode');
|
||||
}
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/AudioNode/disconnect
|
||||
node.disconnect();
|
||||
|
||||
// make sure all AudioScheduledSourceNodes are in a stopped state
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/AudioScheduledSourceNode
|
||||
if (node instanceof AudioScheduledSourceNode) {
|
||||
if (node.onended && node.onended.name !== 'cleanup') {
|
||||
logger(
|
||||
`[superdough] Deprecation warning: it seems your code path is setting 'node.onended = callback' instead of using the onceEnded helper`,
|
||||
);
|
||||
}
|
||||
try {
|
||||
node.stop();
|
||||
} catch (e) {
|
||||
// At the stage, `start` was not called on the node
|
||||
// but an `onended` callback releasing resources may exist
|
||||
// and we want it to fire :
|
||||
// - we force a start/stop cycle so that `onended` gets called
|
||||
// - we `lock` the node so that no-one can start it
|
||||
node.start(node.context.currentTime + 5); // will never happen
|
||||
node.stop();
|
||||
}
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/webaudio-1.1/#AudioNode-actively-processing
|
||||
// An AudioWorkletNode is actively processing when its AudioWorkletProcessor's [[callable process]]
|
||||
// returns true and either its active source flag is true or
|
||||
// any AudioNode connected to one of its inputs is actively processing.
|
||||
if (node instanceof AudioWorkletNode) {
|
||||
node.parameters.get('end')?.setValueAtTime(0, 0);
|
||||
}
|
||||
};
|
||||
|
||||
// Once the `anchor` node has ended, release all nodes in `toCleanup`
|
||||
export const cleanupOnEnd = (anchor, toCleanup) => {
|
||||
onceEnded(anchor, () => toCleanup.forEach((n) => releaseAudioNode(n)));
|
||||
};
|
||||
|
||||
@@ -4,12 +4,13 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export * from './superdough.mjs';
|
||||
export * from './sampler.mjs';
|
||||
export * from './helpers.mjs';
|
||||
export * from './synth.mjs';
|
||||
export * from './zzfx.mjs';
|
||||
export * from './logger.mjs';
|
||||
export * from './dspworklet.mjs';
|
||||
export * from './audioContext.mjs';
|
||||
export * from './audioGraph.mjs';
|
||||
export * from './dspworklet.mjs';
|
||||
export * from './helpers.mjs';
|
||||
export * from './logger.mjs';
|
||||
export * from './sampler.mjs';
|
||||
export * from './superdough.mjs';
|
||||
export * from './synth.mjs';
|
||||
export * from './wavetable.mjs';
|
||||
export * from './zzfx.mjs';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { drywet } from './helpers.mjs';
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import { getAudioGraph, onceEnded, releaseAudioNode } from './audioGraph.mjs';
|
||||
import { drywet } from './helpers.mjs';
|
||||
|
||||
let noiseCache = {};
|
||||
|
||||
@@ -63,11 +64,17 @@ export function getNoiseOscillator(type = 'white', t, density = 0.02) {
|
||||
}
|
||||
|
||||
export function getNoiseMix(inputNode, wet, t) {
|
||||
const ag = getAudioGraph();
|
||||
const noiseOscillator = getNoiseOscillator('pink', t);
|
||||
const noiseMix = drywet(inputNode, noiseOscillator.node, wet);
|
||||
noiseOscillator.node.onended = () => noiseMix.onended();
|
||||
const { subGraph, output } = ag.asSubGraph(() => {
|
||||
return drywet(inputNode, noiseOscillator.node, wet);
|
||||
});
|
||||
onceEnded(noiseOscillator.node, () => {
|
||||
releaseAudioNode(noiseOscillator.node);
|
||||
});
|
||||
return {
|
||||
node: noiseMix.node,
|
||||
node: output.node,
|
||||
stop: (time) => noiseOscillator?.stop(time),
|
||||
teardown: subGraph.release,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import { onceEnded, releaseAudioNode } from './audioGraph.mjs';
|
||||
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||
import { registerSound, registerWaveTable } from './index.mjs';
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import {
|
||||
getADSRValues,
|
||||
getParamADSR,
|
||||
getPitchEnvelope,
|
||||
getVibratoOscillator,
|
||||
onceEnded,
|
||||
releaseAudioNode,
|
||||
} from './helpers.mjs';
|
||||
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
|
||||
const bufferCache = {}; // string: Promise<ArrayBuffer>
|
||||
@@ -323,10 +317,10 @@ export async function onTriggerSample(t, value, onended, bank, resolveUrl) {
|
||||
const out = ac.createGain(); // we need a separate gain for the cutgroups because firefox...
|
||||
node.connect(out);
|
||||
onceEnded(bufferSource, function () {
|
||||
bufferSource.disconnect();
|
||||
vibratoOscillator?.stop();
|
||||
node.disconnect();
|
||||
out.disconnect();
|
||||
releaseAudioNode(bufferSource);
|
||||
releaseAudioNode(vibratoOscillator);
|
||||
releaseAudioNode(node);
|
||||
releaseAudioNode(out);
|
||||
onended();
|
||||
});
|
||||
let envEnd = holdEnd + release + 0.01;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { map } from 'nanostores';
|
||||
import { logger } from './logger.mjs';
|
||||
import { loadBuffer } from './sampler.mjs';
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import { releaseAudioNode } from './audioGraph.mjs';
|
||||
import { SuperdoughAudioController } from './superdoughoutput.mjs';
|
||||
|
||||
export const DEFAULT_MAX_POLYPHONY = 128;
|
||||
@@ -505,7 +506,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||
} else if (getSound(s)) {
|
||||
const { onTrigger } = getSound(s);
|
||||
const onEnded = () => {
|
||||
audioNodes.forEach((n) => n?.disconnect());
|
||||
audioNodes.forEach((n) => releaseAudioNode(n));
|
||||
activeSoundSources.delete(chainID);
|
||||
};
|
||||
const soundHandle = await onTrigger(t, value, onEnded, cps);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { clamp } from './util.mjs';
|
||||
import { registerSound, soundMap } from './superdough.mjs';
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import { getAudioGraph, onceEnded, releaseAudioNode } from './audioGraph.mjs';
|
||||
import {
|
||||
applyFM,
|
||||
gainNode,
|
||||
@@ -12,7 +13,6 @@ import {
|
||||
getVibratoOscillator,
|
||||
getWorklet,
|
||||
noises,
|
||||
releaseAudioNode,
|
||||
webAudioTimeout,
|
||||
} from './helpers.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
@@ -52,7 +52,7 @@ export function registerSynthSounds() {
|
||||
const g = gainNode(0.3);
|
||||
|
||||
let sound = getOscillator(s, t, value, () => {
|
||||
g.disconnect();
|
||||
releaseAudioNode(g);
|
||||
onended();
|
||||
});
|
||||
|
||||
@@ -110,15 +110,15 @@ export function registerSynthSounds() {
|
||||
|
||||
const mix = gainNode(mixGain);
|
||||
|
||||
o.onended = () => {
|
||||
o.disconnect();
|
||||
g.disconnect();
|
||||
sat.disconnect();
|
||||
noise.node.disconnect();
|
||||
noiseGain.disconnect();
|
||||
mix.disconnect();
|
||||
onceEnded(o, () => {
|
||||
releaseAudioNode(o);
|
||||
releaseAudioNode(g);
|
||||
releaseAudioNode(sat);
|
||||
releaseAudioNode(noise.node);
|
||||
releaseAudioNode(noiseGain);
|
||||
releaseAudioNode(mix);
|
||||
onended();
|
||||
};
|
||||
});
|
||||
|
||||
const node = o.connect(sat).connect(g).connect(mix);
|
||||
noise.node.connect(noiseGain).connect(mix);
|
||||
@@ -384,11 +384,11 @@ export function registerSynthSounds() {
|
||||
|
||||
const { duration } = value;
|
||||
|
||||
o.onended = () => {
|
||||
o.disconnect();
|
||||
g.disconnect();
|
||||
onceEnded(o, () => {
|
||||
releaseAudioNode(o);
|
||||
releaseAudioNode(g);
|
||||
onended();
|
||||
};
|
||||
});
|
||||
|
||||
const envGain = gainNode(1);
|
||||
let node = o.connect(g).connect(envGain);
|
||||
@@ -477,33 +477,25 @@ export function getOscillator(s, t, value, onended) {
|
||||
}
|
||||
// set frequency
|
||||
o.frequency.value = getFrequencyFromValue(value);
|
||||
const ag = getAudioGraph();
|
||||
const { subGraph, output } = ag.asSubGraph(() => {
|
||||
getVibratoOscillator(o.detune, value, t);
|
||||
// pitch envelope
|
||||
getPitchEnvelope(o.detune, value, t, t + duration);
|
||||
applyFM(o.frequency, value, t);
|
||||
let noiseMix;
|
||||
if (noise) {
|
||||
noiseMix = getNoiseMix(o, noise, t);
|
||||
}
|
||||
return { node: noiseMix?.node || o };
|
||||
});
|
||||
|
||||
let vibratoOscillator = getVibratoOscillator(o.detune, value, t);
|
||||
|
||||
// pitch envelope
|
||||
getPitchEnvelope(o.detune, value, t, t + duration);
|
||||
const fmModulator = applyFM(o.frequency, value, t);
|
||||
|
||||
let noiseMix;
|
||||
if (noise) {
|
||||
noiseMix = getNoiseMix(o, noise, t);
|
||||
}
|
||||
|
||||
o.onended = () => {
|
||||
noiseMix || o.disconnect();
|
||||
noiseMix?.node.disconnect();
|
||||
onended();
|
||||
};
|
||||
onceEnded(o, () => subGraph.release());
|
||||
o.start(t);
|
||||
|
||||
return {
|
||||
node: noiseMix?.node || o,
|
||||
stop: (time) => {
|
||||
fmModulator.stop(time);
|
||||
vibratoOscillator?.stop(time);
|
||||
noiseMix?.stop(time);
|
||||
o.stop(time);
|
||||
},
|
||||
node: output.node,
|
||||
stop: (time) => o.stop(time),
|
||||
triggerRelease: (time) => {
|
||||
// envGain?.stop(time);
|
||||
},
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { releaseAudioNode } from './audioGraph.mjs';
|
||||
import { getAudioContext, registerSound } from './index.mjs';
|
||||
import { getBaseURL, getCommonSampleInfo } from './util.mjs';
|
||||
import {
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
getPitchEnvelope,
|
||||
getVibratoOscillator,
|
||||
getWorklet,
|
||||
releaseAudioNode,
|
||||
webAudioTimeout,
|
||||
} from './helpers.mjs';
|
||||
import { logger } from './logger.mjs';
|
||||
@@ -320,10 +320,10 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
||||
ac,
|
||||
() => {
|
||||
releaseAudioNode(source);
|
||||
vibratoOscillator?.stop();
|
||||
releaseAudioNode(vibratoOscillator);
|
||||
fm?.stop();
|
||||
wtPosModulators?.disconnect();
|
||||
wtWarpModulators?.disconnect();
|
||||
releaseAudioNode(wtPosModulators);
|
||||
releaseAudioNode(wtWarpModulators);
|
||||
onended();
|
||||
},
|
||||
t,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//import { ZZFX } from 'zzfx';
|
||||
import { midiToFreq, noteToMidi } from './util.mjs';
|
||||
import { registerSound } from './superdough.mjs';
|
||||
import { getAudioContext } from './audioContext.mjs';
|
||||
import { onceEnded, releaseAudioNode } from './audioGraph.mjs';
|
||||
import { registerSound } from './superdough.mjs';
|
||||
import { midiToFreq, noteToMidi } from './util.mjs';
|
||||
import { buildSamples } from './zzfx_fork.mjs';
|
||||
import { onceEnded, releaseAudioNode } from './helpers.mjs';
|
||||
|
||||
export const getZZFX = (value, t) => {
|
||||
let {
|
||||
|
||||
Reference in New Issue
Block a user