Working version of LFOs
This commit is contained in:
parent
f17a4d045f
commit
4a70ac5a99
3 changed files with 141 additions and 17 deletions
|
|
@ -1430,6 +1430,11 @@ export const { panorient } = registerControl('panorient');
|
||||||
// ['portamento'],
|
// ['portamento'],
|
||||||
// TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
|
// TODO: LFO rate see https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
|
||||||
export const { rate } = registerControl('rate');
|
export const { rate } = registerControl('rate');
|
||||||
|
export const { lfoTarget } = registerControl('lfoTarget');
|
||||||
|
export const { lfoParam } = registerControl('lfoParam');
|
||||||
|
export const { lfoNum } = registerControl('lfoNum');
|
||||||
|
export const { lfoBipolar } = registerControl('lfoBipolar');
|
||||||
|
export const { lfoShape } = registerControl('lfoShape');
|
||||||
// TODO: slide param for certain synths
|
// TODO: slide param for certain synths
|
||||||
export const { slide } = registerControl('slide');
|
export const { slide } = registerControl('slide');
|
||||||
// TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
|
// TODO: detune? https://tidalcycles.org/docs/patternlib/tutorials/synthesizers/#supersquare
|
||||||
|
|
|
||||||
|
|
@ -510,6 +510,66 @@ export function resetGlobalEffects() {
|
||||||
orbits = {};
|
orbits = {};
|
||||||
analysers = {};
|
analysers = {};
|
||||||
analysersData = {};
|
analysersData = {};
|
||||||
|
lfos = {};
|
||||||
|
nodes = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getNodeParam(node, name) {
|
||||||
|
// Worklet case
|
||||||
|
if (node?.parameters) {
|
||||||
|
const p = node.parameters.get(name);
|
||||||
|
if (p instanceof AudioParam) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Built-in node case
|
||||||
|
const p = node?.[name];
|
||||||
|
if (p instanceof AudioParam) {
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getNodeParams(node) {
|
||||||
|
const params = new Set();
|
||||||
|
// Worklet case
|
||||||
|
if (node?.parameters) {
|
||||||
|
node.parameters.forEach((_v, k) => params.add(k));
|
||||||
|
}
|
||||||
|
// Guesses based on common parameters
|
||||||
|
["gain", "frequency", "detune", "Q", "pan", "playbackRate", "delayTime"]
|
||||||
|
.forEach((k) => { if (node?.[k] instanceof AudioParam) params.add(k); });
|
||||||
|
return Array.from(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectLFO(lfoNum, target, param, frequency, depth, shape, bipolar, start, end) {
|
||||||
|
debugger;
|
||||||
|
let lfoNode = lfos[lfoNum];
|
||||||
|
const params = {
|
||||||
|
frequency,
|
||||||
|
depth,
|
||||||
|
}
|
||||||
|
if (lfoNode == null) {
|
||||||
|
const ac = getAudioContext();
|
||||||
|
const dcoffset = bipolar > 0.5 ? -0.5 : 0;
|
||||||
|
lfoNode = getLfo(ac, start, 1e9, { frequency, depth, shape, dcoffset});
|
||||||
|
lfos[lfoNum] = lfoNode;
|
||||||
|
}
|
||||||
|
lfoNode.disconnect();
|
||||||
|
const targetNodes = nodes[target];
|
||||||
|
targetNodes.forEach((targetNode) => {
|
||||||
|
if (targetNode === undefined) {
|
||||||
|
const keys = Object.keys(nodes);
|
||||||
|
errorLogger(new Error(`Could not connect to target ${target} -- it does not exist. Available options are ${keys.join(", ")}`), 'superdough');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetParam = _getNodeParam(targetNode, param);
|
||||||
|
if (targetParam === undefined) {
|
||||||
|
const parameters = _getNodeParams(targetNode);
|
||||||
|
errorLogger(new Error(`Could not connect to parameter ${param} on node ${target}. Available parameters are ${parameters.join(", ")}`), 'superdough');
|
||||||
|
}
|
||||||
|
lfoNode.connect(targetParam);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeSoundSources = new Map();
|
let activeSoundSources = new Map();
|
||||||
|
|
@ -519,6 +579,9 @@ function mapChannelNumbers(channels) {
|
||||||
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nodes = {};
|
||||||
|
let lfos = {};
|
||||||
|
|
||||||
export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => {
|
export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5) => {
|
||||||
// new: t is always expected to be the absolute target onset time
|
// new: t is always expected to be the absolute target onset time
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
|
|
@ -628,6 +691,13 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
compressorKnee,
|
compressorKnee,
|
||||||
compressorAttack,
|
compressorAttack,
|
||||||
compressorRelease,
|
compressorRelease,
|
||||||
|
lfo,
|
||||||
|
lfoNum,
|
||||||
|
rate,
|
||||||
|
lfoTarget,
|
||||||
|
lfoParam,
|
||||||
|
lfoBipolar,
|
||||||
|
lfoShape,
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
||||||
|
|
@ -681,7 +751,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
let sourceNode;
|
let sourceNode;
|
||||||
if (source) {
|
if (source) {
|
||||||
sourceNode = source(t, value, hapDuration, cps);
|
sourceNode = source(t, value, hapDuration, cps);
|
||||||
|
nodes['source'] = [sourceNode];
|
||||||
} else if (getSound(s)) {
|
} else if (getSound(s)) {
|
||||||
|
debugger;
|
||||||
const { onTrigger } = getSound(s);
|
const { onTrigger } = getSound(s);
|
||||||
const onEnded = () => {
|
const onEnded = () => {
|
||||||
audioNodes.forEach((n) => n?.disconnect());
|
audioNodes.forEach((n) => n?.disconnect());
|
||||||
|
|
@ -692,6 +764,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
if (soundHandle) {
|
if (soundHandle) {
|
||||||
sourceNode = soundHandle.node;
|
sourceNode = soundHandle.node;
|
||||||
activeSoundSources.set(chainID, soundHandle);
|
activeSoundSources.set(chainID, soundHandle);
|
||||||
|
nodes['source'] = [soundHandle.oscillator];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`sound ${s} not found! Is it loaded?`);
|
throw new Error(`sound ${s} not found! Is it loaded?`);
|
||||||
|
|
@ -711,12 +784,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
stretch !== undefined && chain.push(getWorklet(ac, 'phase-vocoder-processor', { pitchFactor: stretch }));
|
||||||
|
|
||||||
// gain stage
|
// gain stage
|
||||||
chain.push(gainNode(gain));
|
const initialGain = gainNode(gain);
|
||||||
|
nodes['gain'] = [initialGain];
|
||||||
|
chain.push(initialGain);
|
||||||
|
|
||||||
//filter
|
//filter
|
||||||
const ftype = getFilterType(value.ftype);
|
const ftype = getFilterType(value.ftype);
|
||||||
if (cutoff !== undefined) {
|
if (cutoff !== undefined) {
|
||||||
let lp = () =>
|
const lp = () =>
|
||||||
createFilter(
|
createFilter(
|
||||||
ac,
|
ac,
|
||||||
'lowpass',
|
'lowpass',
|
||||||
|
|
@ -733,14 +808,18 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
ftype,
|
ftype,
|
||||||
drive,
|
drive,
|
||||||
);
|
);
|
||||||
chain.push(lp());
|
const lp1 = lp();
|
||||||
|
nodes['lpf'] = [lp1];
|
||||||
|
chain.push(lp1);
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(lp());
|
const lp2 = lp();
|
||||||
|
nodes['lpf'].push(lp2);
|
||||||
|
chain.push(lp2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hcutoff !== undefined) {
|
if (hcutoff !== undefined) {
|
||||||
let hp = () =>
|
const hp = () =>
|
||||||
createFilter(
|
createFilter(
|
||||||
ac,
|
ac,
|
||||||
'highpass',
|
'highpass',
|
||||||
|
|
@ -755,31 +834,56 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
end,
|
end,
|
||||||
fanchor,
|
fanchor,
|
||||||
);
|
);
|
||||||
chain.push(hp());
|
const hp1 = hp();
|
||||||
|
nodes['hpf'] = [hp1];
|
||||||
|
chain.push(hp1);
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(hp());
|
const hp2 = hp();
|
||||||
|
nodes['hpf'].push(hp1);
|
||||||
|
chain.push(hp2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bandf !== undefined) {
|
if (bandf !== undefined) {
|
||||||
let bp = () =>
|
let bp = () =>
|
||||||
createFilter(ac, 'bandpass', bandf, bandq, bpattack, bpdecay, bpsustain, bprelease, bpenv, t, end, fanchor);
|
createFilter(ac, 'bandpass', bandf, bandq, bpattack, bpdecay, bpsustain, bprelease, bpenv, t, end, fanchor);
|
||||||
chain.push(bp());
|
const bp1 = bp();
|
||||||
|
nodes['bpf'] = [bp1];
|
||||||
|
chain.push(bp1);
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(bp());
|
const bp2 = bp();
|
||||||
|
nodes['bpf'].push(bp2);
|
||||||
|
chain.push(bp2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vowel !== undefined) {
|
if (vowel !== undefined) {
|
||||||
const vowelFilter = ac.createVowelFilter(vowel);
|
const vowelFilter = ac.createVowelFilter(vowel);
|
||||||
|
nodes['vowel'] = vowelFilter;
|
||||||
chain.push(vowelFilter);
|
chain.push(vowelFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// effects
|
// effects
|
||||||
coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse }));
|
if (coarse !== undefined) {
|
||||||
crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush }));
|
const coarseNode = getWorklet(ac, 'coarse-processor', { coarse });
|
||||||
shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol }));
|
nodes['coarse'] = coarseNode;
|
||||||
distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol }));
|
chain.push(coarseNode);
|
||||||
|
}
|
||||||
|
if (crush !== undefined) {
|
||||||
|
const crushNode = getWorklet(ac, 'crush-processor', { crush });
|
||||||
|
nodes['crush'] = crushNode;
|
||||||
|
chain.push(crushNode);
|
||||||
|
}
|
||||||
|
if (shape !== undefined) {
|
||||||
|
const shapeNode = getWorklet(ac, 'shape-processor', { shape });
|
||||||
|
nodes['shape'] = shapeNode;
|
||||||
|
chain.push(shapeNode);
|
||||||
|
}
|
||||||
|
if (distort !== undefined) {
|
||||||
|
const distortNode = getWorklet(ac, 'distort-processor', { distort });
|
||||||
|
nodes['distort'] = distortNode;
|
||||||
|
chain.push(distortNode);
|
||||||
|
}
|
||||||
|
|
||||||
if (tremolosync != null) {
|
if (tremolosync != null) {
|
||||||
tremolo = cps * tremolosync;
|
tremolo = cps * tremolosync;
|
||||||
|
|
@ -808,10 +912,11 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
chain.push(amGain);
|
chain.push(amGain);
|
||||||
}
|
}
|
||||||
|
|
||||||
compressorThreshold !== undefined &&
|
if (compressorThreshold !== undefined) {
|
||||||
chain.push(
|
const compressorNode = getCompressor(ac, compressorThreshold, compressorRatio, compressorKnee, compressorAttack, compressorRelease);
|
||||||
getCompressor(ac, compressorThreshold, compressorRatio, compressorKnee, compressorAttack, compressorRelease),
|
nodes['compressor'] = compressorNode;
|
||||||
);
|
chain.push(compressorNode);
|
||||||
|
}
|
||||||
|
|
||||||
// panning
|
// panning
|
||||||
if (pan !== undefined) {
|
if (pan !== undefined) {
|
||||||
|
|
@ -822,17 +927,20 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
// phaser
|
// phaser
|
||||||
if (phaser !== undefined && phaserdepth > 0) {
|
if (phaser !== undefined && phaserdepth > 0) {
|
||||||
const phaserFX = getPhaser(t, endWithRelease, phaser, phaserdepth, phasercenter, phasersweep);
|
const phaserFX = getPhaser(t, endWithRelease, phaser, phaserdepth, phasercenter, phasersweep);
|
||||||
|
nodes['phaser'] = phaserFX;
|
||||||
chain.push(phaserFX);
|
chain.push(phaserFX);
|
||||||
}
|
}
|
||||||
|
|
||||||
// last gain
|
// last gain
|
||||||
const post = new GainNode(ac, { gain: postgain });
|
const post = new GainNode(ac, { gain: postgain });
|
||||||
|
nodes['post'] = post;
|
||||||
chain.push(post);
|
chain.push(post);
|
||||||
|
|
||||||
// delay
|
// delay
|
||||||
let delaySend;
|
let delaySend;
|
||||||
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
if (delay > 0 && delaytime > 0 && delayfeedback > 0) {
|
||||||
const delayNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
|
const delayNode = getDelay(orbit, delaytime, delayfeedback, t, orbitChannels);
|
||||||
|
nodes['delay'] = delayNode;
|
||||||
delaySend = effectSend(post, delayNode, delay);
|
delaySend = effectSend(post, delayNode, delay);
|
||||||
audioNodes.push(delaySend);
|
audioNodes.push(delaySend);
|
||||||
}
|
}
|
||||||
|
|
@ -851,6 +959,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
roomIR = await loadBuffer(url, ac, ir, 0);
|
roomIR = await loadBuffer(url, ac, ir, 0);
|
||||||
}
|
}
|
||||||
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, orbitChannels);
|
const reverbNode = getReverb(orbit, roomsize, roomfade, roomlp, roomdim, roomIR, orbitChannels);
|
||||||
|
nodes['room'] = reverbNode;
|
||||||
reverbSend = effectSend(post, reverbNode, room);
|
reverbSend = effectSend(post, reverbNode, room);
|
||||||
audioNodes.push(reverbSend);
|
audioNodes.push(reverbSend);
|
||||||
}
|
}
|
||||||
|
|
@ -874,6 +983,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
// connect chain elements together
|
// connect chain elements together
|
||||||
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
||||||
audioNodes = audioNodes.concat(chain);
|
audioNodes = audioNodes.concat(chain);
|
||||||
|
|
||||||
|
// finally, now that `nodes` is populated, set up LFOs
|
||||||
|
connectLFO(lfoNum, lfoTarget, lfoParam, rate, lfo, lfoBipolar, lfoShape, t, endWithRelease);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const superdoughTrigger = (t, hap, ct, cps) => {
|
export const superdoughTrigger = (t, hap, ct, cps) => {
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ export function registerSynthSounds() {
|
||||||
stop(envEnd);
|
stop(envEnd);
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
|
oscillator: o,
|
||||||
stop: (endTime) => {
|
stop: (endTime) => {
|
||||||
stop(endTime);
|
stop(endTime);
|
||||||
},
|
},
|
||||||
|
|
@ -156,6 +157,7 @@ export function registerSynthSounds() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
|
oscillator: o,
|
||||||
stop: (endTime) => {
|
stop: (endTime) => {
|
||||||
o.stop(endTime);
|
o.stop(endTime);
|
||||||
},
|
},
|
||||||
|
|
@ -222,6 +224,7 @@ export function registerSynthSounds() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node: envGain,
|
node: envGain,
|
||||||
|
oscillator: o,
|
||||||
stop: (time) => {
|
stop: (time) => {
|
||||||
timeoutNode.stop(time);
|
timeoutNode.stop(time);
|
||||||
},
|
},
|
||||||
|
|
@ -298,6 +301,7 @@ export function registerSynthSounds() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node: envGain,
|
node: envGain,
|
||||||
|
oscillator: o,
|
||||||
stop: (time) => {
|
stop: (time) => {
|
||||||
timeoutNode.stop(time);
|
timeoutNode.stop(time);
|
||||||
},
|
},
|
||||||
|
|
@ -375,6 +379,7 @@ export function registerSynthSounds() {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node: envGain,
|
node: envGain,
|
||||||
|
oscillator: o,
|
||||||
stop: (time) => {
|
stop: (time) => {
|
||||||
timeoutNode.stop(time);
|
timeoutNode.stop(time);
|
||||||
},
|
},
|
||||||
|
|
@ -420,6 +425,7 @@ export function registerSynthSounds() {
|
||||||
stop(envEnd);
|
stop(envEnd);
|
||||||
return {
|
return {
|
||||||
node,
|
node,
|
||||||
|
oscillator: o,
|
||||||
stop: (endTime) => {
|
stop: (endTime) => {
|
||||||
stop(endTime);
|
stop(endTime);
|
||||||
},
|
},
|
||||||
|
|
@ -492,6 +498,7 @@ export function getOscillator(s, t, value) {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
node: noiseMix?.node || o,
|
node: noiseMix?.node || o,
|
||||||
|
oscillator: o,
|
||||||
stop: (time) => {
|
stop: (time) => {
|
||||||
fmModulator.stop(time);
|
fmModulator.stop(time);
|
||||||
vibratoOscillator?.stop(time);
|
vibratoOscillator?.stop(time);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue