Another major cleanup 90% of first pass
This commit is contained in:
parent
7ccdefe1c6
commit
442f487973
2 changed files with 100 additions and 161 deletions
|
|
@ -376,8 +376,7 @@ export function resetGlobalEffects() {
|
||||||
controller?.reset();
|
controller?.reset();
|
||||||
analysers = {};
|
analysers = {};
|
||||||
analysersData = {};
|
analysersData = {};
|
||||||
lfos = {};
|
idToNodes = {};
|
||||||
nodes = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getNodeParam(node, name) {
|
function _getNodeParam(node, name) {
|
||||||
|
|
@ -409,66 +408,21 @@ function _getNodeParams(node) {
|
||||||
return Array.from(params);
|
return Array.from(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function _getTargetParams(nodes, target) {
|
||||||
* Split parameters (which might be arrays -- implying multiple-parameter modulation -- or a single number) into independent
|
const targetNodes = nodes[target];
|
||||||
* objects which only account for single-parameter modulation
|
|
||||||
*
|
|
||||||
* @param {Object} params - Dictionary of modulation parameters.
|
|
||||||
* @returns {Object[]} - Array of parameter objects, one per parameter modulation
|
|
||||||
*/
|
|
||||||
function _splitParams(params) {
|
|
||||||
const num = ['num', 'target', 'param'] // names used to indicate individual parameter modulations
|
|
||||||
.map((k) => [params[k] ?? 0].flat().length)
|
|
||||||
.reduce((a, v) => Math.max(a, v), 1);
|
|
||||||
|
|
||||||
const individualParams = [];
|
|
||||||
for (let i = 0; i < num; i++) {
|
|
||||||
const paramsI = {};
|
|
||||||
for (const k in params) {
|
|
||||||
const flatV = [params[k]].flat();
|
|
||||||
if (flatV.length !== num && flatV.length !== 1) {
|
|
||||||
errorLogger(
|
|
||||||
new Error(
|
|
||||||
`Could not set up modulations. We derived ${num} items, but ${k}: ${JSON.stringify(flatV)} has length ${flatV.length} (needs 1 or ${num}).`,
|
|
||||||
),
|
|
||||||
'superdough',
|
|
||||||
);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
paramsI[k] = flatV[i] ?? flatV[0];
|
|
||||||
}
|
|
||||||
individualParams.push(paramsI);
|
|
||||||
}
|
|
||||||
return individualParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given a node name and the name of a parameter on that node, attempt to retrieve
|
|
||||||
* all nodes corresponding to the name and their associated parameters
|
|
||||||
*
|
|
||||||
* Note that we say nodes, plural, because some nodes have multiple sub-nodes, like a
|
|
||||||
* 24db filter which is two filters in series
|
|
||||||
*
|
|
||||||
* @param {string} targetName - Name of the node to modulate parameters on (e.g. `lpf`, `source`, etc.)
|
|
||||||
* @param {string} paramName - Name of the parameter to modulate on that node
|
|
||||||
* @returns {AudioParam[]} - Array of audio parameter objects for modulation
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
function _getTargetParams(targetName, paramName) {
|
|
||||||
const targetNodes = nodes[targetName];
|
|
||||||
if (!targetNodes) {
|
if (!targetNodes) {
|
||||||
const keys = Object.keys(nodes);
|
const keys = Object.keys(nodes);
|
||||||
errorLogger(
|
errorLogger(
|
||||||
new Error(
|
new Error(
|
||||||
`Could not connect to target '${targetName}' — it does not exist. Available targets: ${keys.join(', ')}`,
|
`Could not connect to target '${target}' — it does not exist. Available targets: ${keys.join(', ')}`,
|
||||||
),
|
),
|
||||||
'superdough',
|
'superdough',
|
||||||
);
|
);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const audioParams = [];
|
const audioParams = [];
|
||||||
targetNodes.forEach((targetNode) => {
|
targetNodes.forEach((targetNode) => {
|
||||||
|
const paramName = guessParamName(blah);
|
||||||
const targetParam = _getNodeParam(targetNode, paramName);
|
const targetParam = _getNodeParam(targetNode, paramName);
|
||||||
if (!targetParam) {
|
if (!targetParam) {
|
||||||
const available = _getNodeParams(targetNode);
|
const available = _getNodeParams(targetNode);
|
||||||
|
|
@ -485,64 +439,57 @@ function _getTargetParams(targetName, paramName) {
|
||||||
return audioParams;
|
return audioParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _setWorkletParamsAtTime(audioParams, params, time) {
|
function connectLFO(idx, params, nodeTracker) {
|
||||||
for (const [name, value] of params) {
|
|
||||||
if (value == null) continue;
|
|
||||||
const p = audioParams.get(name);
|
|
||||||
if (p.cancelAndHoldAtTime) p.cancelAndHoldAtTime(time);
|
|
||||||
else p.cancelScheduledValues(time);
|
|
||||||
p.setValueAtTime(value, time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let lfos = {};
|
|
||||||
function _connectLFO(params, nodeTracker) {
|
|
||||||
const {
|
const {
|
||||||
frequency = 1,
|
rate = 1,
|
||||||
synced = 0,
|
sync,
|
||||||
cps = 0.5,
|
cps,
|
||||||
num = 1, // default to LFO 1
|
|
||||||
target,
|
target,
|
||||||
param,
|
|
||||||
begin,
|
|
||||||
...filteredParams
|
...filteredParams
|
||||||
} = params;
|
} = params;
|
||||||
filteredParams['frequency'] = synced ? frequency / cps : frequency;
|
filteredParams['frequency'] = sync !== undefined ? sync / cps : rate;
|
||||||
let lfoNode = lfos[num];
|
const ac = getAudioContext();
|
||||||
if (lfoNode == null) {
|
lfoNode = getLfo(ac, filteredParams);
|
||||||
const ac = getAudioContext();
|
nodeTracker[`lfo${idx}`] = [lfoNode];
|
||||||
lfoNode = getLfo(ac, filteredParams);
|
_getTargetParams(target).forEach(lfoNode.connect);
|
||||||
lfos[num] = lfoNode;
|
|
||||||
nodeTracker[`lfo${num}`] = [lfoNode];
|
|
||||||
}
|
|
||||||
const targets = _getTargetParams(target, param);
|
|
||||||
targets.forEach((target) => lfoNode.connect(target));
|
|
||||||
_setWorkletParamsAtTime(lfoNode.parameters, Object.entries(filteredParams), begin);
|
|
||||||
return lfoNode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _connectEnvelope(params) {
|
function connectEnvelope(idx, params, nodeTracker) {
|
||||||
const { target, param, envDepth, begin, end, attack, decay, sustain, release, curve, ...filteredParams } = params;
|
const { target, ...filteredParams } = params;
|
||||||
const targets = _getTargetParams(target, param);
|
const ac = getAudioContext();
|
||||||
const [att, dec, sus, rel] = getADSRValues([attack, decay, sustain, release], curve, [0.005, 0.14, 0, 0.1]);
|
envNode = getEnvelope(ac, filteredParams);
|
||||||
targets.forEach((targetParam) => {
|
nodeTracker[`env${idx}`] = [envNode];
|
||||||
const currentValue = targetParam.value;
|
_getTargetParams(nodeTracker, target).forEach(envNode.connect);
|
||||||
const min = currentValue;
|
|
||||||
const max = currentValue + envDepth;
|
|
||||||
getParamADSR(targetParam, att, dec, sus, rel, min, max, begin, end, curve);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectModulators(params, modulatorType, nodeTracker) {
|
function connectSendModulator(params, signal, nodeTracker, pendingConnections) {
|
||||||
// We break down params specifying multiple modulators into a set of parameters for
|
const dc = new ConstantSourceNode(ac, { offset: params.dc ?? 0 });
|
||||||
// a single one
|
dc.start(t);
|
||||||
const individualParams = _splitParams(params);
|
const offset = new ConstantSourceNode(ac, { offset: params.offset ?? 0 });
|
||||||
if (modulatorType === 'lfo') {
|
offset.start(t);
|
||||||
individualParams.map((p) => _connectLFO(p, nodeTracker));
|
const raw = dc.connect(gainNode(1));
|
||||||
} else if (modulatorType === 'envelope') {
|
const modulator = post
|
||||||
individualParams.forEach(_connectEnvelope);
|
.connect(raw)
|
||||||
}
|
.connect(gainNode((params.depth ?? 1) / 0.3))
|
||||||
return [];
|
.connect(gainNode(1));
|
||||||
|
offset.connect(modulator);
|
||||||
|
webAudioTimeout(
|
||||||
|
ac,
|
||||||
|
() => {
|
||||||
|
_getTargetParams(nodeTracker, params.target).forEach(signal.connect);;
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
params.begin,
|
||||||
|
);
|
||||||
|
webAudioTimeout(
|
||||||
|
ac,
|
||||||
|
() => {
|
||||||
|
signal.disconnect();
|
||||||
|
delete pendingConnections[params.id][chainID];
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
params.end + 0.05,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeSoundSources = new Map();
|
let activeSoundSources = new Map();
|
||||||
|
|
@ -552,9 +499,10 @@ 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 idToNodes = {};
|
||||||
|
let pendingConnections = {};
|
||||||
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) => {
|
||||||
|
const nodes = {};
|
||||||
// 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();
|
||||||
const audioController = getSuperdoughAudioController();
|
const audioController = getSuperdoughAudioController();
|
||||||
|
|
@ -644,24 +592,6 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
compressorKnee,
|
compressorKnee,
|
||||||
compressorAttack,
|
compressorAttack,
|
||||||
compressorRelease,
|
compressorRelease,
|
||||||
lfoNum,
|
|
||||||
lfoTarget,
|
|
||||||
lfoParam,
|
|
||||||
lfoRate,
|
|
||||||
lfoDepth,
|
|
||||||
lfoDCOffset,
|
|
||||||
lfoShape,
|
|
||||||
lfoSkew,
|
|
||||||
lfoCurve,
|
|
||||||
lfoSynced,
|
|
||||||
envTarget,
|
|
||||||
envParam,
|
|
||||||
envAttack,
|
|
||||||
envDecay,
|
|
||||||
envSustain,
|
|
||||||
envRelease,
|
|
||||||
envCurve,
|
|
||||||
envDepth,
|
|
||||||
} = value;
|
} = value;
|
||||||
|
|
||||||
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
delaytime = delaytime ?? cycleToSeconds(delaysync, cps);
|
||||||
|
|
@ -991,45 +921,56 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
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 and envelopes
|
// finally, now that `nodes` is populated, set up modulators
|
||||||
if (lfoTarget !== undefined && lfoParam !== undefined) {
|
if (value.lfo) {
|
||||||
connectModulators(
|
for (const [params, idx] of Object.entries(value.lfo)) {
|
||||||
{
|
connectLFO(
|
||||||
num: lfoNum ?? 1,
|
idx,
|
||||||
target: lfoTarget,
|
{
|
||||||
param: lfoParam,
|
...params,
|
||||||
frequency: lfoRate,
|
cps,
|
||||||
depth: lfoDepth,
|
begin: t,
|
||||||
dcoffset: lfoDCOffset ?? 0, // override default value of 0.5
|
end: endWithRelease,
|
||||||
shape: lfoShape,
|
},
|
||||||
skew: lfoSkew,
|
'lfo',
|
||||||
curve: lfoCurve,
|
nodes,
|
||||||
persistent: 1,
|
);
|
||||||
begin: t,
|
}
|
||||||
synced: lfoSynced,
|
|
||||||
cps: cps,
|
|
||||||
},
|
|
||||||
'lfo',
|
|
||||||
nodes,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (envTarget !== undefined && envParam !== undefined) {
|
if (value.env) {
|
||||||
connectModulators(
|
for (const [params, idx] of Object.entries(value.env)) {
|
||||||
{
|
connectEnvelope(
|
||||||
target: envTarget,
|
idx,
|
||||||
param: envParam,
|
{
|
||||||
envDepth,
|
...params,
|
||||||
attack: envAttack,
|
begin: t,
|
||||||
decay: envDecay,
|
end: endWithRelease,
|
||||||
sustain: envSustain,
|
},
|
||||||
release: envRelease,
|
'envelope',
|
||||||
curve: envCurve,
|
nodes,
|
||||||
begin: t,
|
);
|
||||||
end: endWithRelease,
|
}
|
||||||
},
|
|
||||||
'envelope',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
if (value.id) {
|
||||||
|
idToNodes[value.id] = new WeakRef(nodes);
|
||||||
|
}
|
||||||
|
if (value.send) {
|
||||||
|
for (const p of value.env) {
|
||||||
|
const modNodes = idToNodes[p.id];
|
||||||
|
if (!modNodes) {
|
||||||
|
logger(
|
||||||
|
`[superdough] Could not connect to pattern ${p.id} -- make sure a pattern with this name exists. Available targets: ${Object.keys(idToNodes).join(', ')}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
connectSendModulator(params, post);
|
||||||
|
pendingConnections[p.id] ??= {};
|
||||||
|
pendingConnections[p.id][chainID] = [modulator, p.target, p.param, endWithRelease];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (applySends) {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const superdoughTrigger = (t, hap, ct, cps) => {
|
export const superdoughTrigger = (t, hap, ct, cps) => {
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,6 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||||
{ name: 'dcoffset', defaultValue: 0 },
|
{ name: 'dcoffset', defaultValue: 0 },
|
||||||
{ name: 'min', defaultValue: 0 },
|
{ name: 'min', defaultValue: 0 },
|
||||||
{ name: 'max', defaultValue: 1 },
|
{ name: 'max', defaultValue: 1 },
|
||||||
{ name: 'persistent', defaultValue: 0, min: 0, max: 1 }, // whether to ignore end
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,8 +141,7 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||||
process(_inputs, outputs, parameters) {
|
process(_inputs, outputs, parameters) {
|
||||||
const begin = parameters['begin'][0];
|
const begin = parameters['begin'][0];
|
||||||
const end = parameters['end'][0];
|
const end = parameters['end'][0];
|
||||||
const persistent = parameters['persistent'][0];
|
if (currentTime >= end) {
|
||||||
if (persistent < 0.5 && currentTime >= end) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (currentTime <= begin) {
|
if (currentTime <= begin) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue