Switch to omod
This commit is contained in:
parent
32924b3fcf
commit
ad43259353
4 changed files with 52 additions and 100 deletions
|
|
@ -3726,7 +3726,8 @@ const resolveConfigKey = (funcName, key) => {
|
||||||
addConfigAlias('lfo', 'target', 't');
|
addConfigAlias('lfo', 'target', 't');
|
||||||
addConfigAlias('lfo', 'param', 'p');
|
addConfigAlias('lfo', 'param', 'p');
|
||||||
addConfigAlias('lfo', 'rate', 'r');
|
addConfigAlias('lfo', 'rate', 'r');
|
||||||
addConfigAlias('lfo', 'depth', 'dep', 'dp', 'd');
|
addConfigAlias('lfo', 'depth', 'dep', 'dr');
|
||||||
|
addConfigAlias('lfo', 'depthabs', 'da');
|
||||||
addConfigAlias('lfo', 'dcoffset', 'dc');
|
addConfigAlias('lfo', 'dcoffset', 'dc');
|
||||||
addConfigAlias('lfo', 'shape', 'sh');
|
addConfigAlias('lfo', 'shape', 'sh');
|
||||||
addConfigAlias('lfo', 'skew', 'sk');
|
addConfigAlias('lfo', 'skew', 'sk');
|
||||||
|
|
@ -3737,21 +3738,23 @@ addConfigAlias('env', 'attack', 'att', 'a');
|
||||||
addConfigAlias('env', 'decay', 'dec', 'd');
|
addConfigAlias('env', 'decay', 'dec', 'd');
|
||||||
addConfigAlias('env', 'sustain', 'sus', 's');
|
addConfigAlias('env', 'sustain', 'sus', 's');
|
||||||
addConfigAlias('env', 'release', 'rel', 'r');
|
addConfigAlias('env', 'release', 'rel', 'r');
|
||||||
addConfigAlias('env', 'depth', 'dep', 'dp');
|
addConfigAlias('env', 'depth', 'dep', 'dr');
|
||||||
|
addConfigAlias('env', 'depthabs', 'da');
|
||||||
addConfigAlias('env', 'acurve', 'ac');
|
addConfigAlias('env', 'acurve', 'ac');
|
||||||
addConfigAlias('env', 'dcurve', 'dc');
|
addConfigAlias('env', 'dcurve', 'dc');
|
||||||
addConfigAlias('env', 'rcurve', 'rc');
|
addConfigAlias('env', 'rcurve', 'rc');
|
||||||
addConfigAlias('send', 'id');
|
addConfigAlias('omod', 'orbit', 'o');
|
||||||
addConfigAlias('send', 'target', 't');
|
addConfigAlias('omod', 'target', 't');
|
||||||
addConfigAlias('send', 'depth', 'dep', 'dp', 'd');
|
addConfigAlias('omod', 'depth', 'dep', 'dr');
|
||||||
addConfigAlias('send', 'dc');
|
addConfigAlias('omod', 'depthabs', 'da');
|
||||||
|
addConfigAlias('omod', 'dc');
|
||||||
|
|
||||||
Pattern.prototype.modulate = function (type, config, idx) {
|
Pattern.prototype.modulate = function (type, config, idx) {
|
||||||
if (config == null || typeof config !== 'object') {
|
if (config == null || typeof config !== 'object') {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
if (!['lfo', 'send', 'env'].includes(type)) {
|
if (!['lfo', 'env', 'omod'].includes(type)) {
|
||||||
logger(`[core] Modulation type ${type} not found. Please use one of 'lfo', 'env', 'send'`);
|
logger(`[core] Modulation type ${type} not found. Please use one of 'lfo', 'env', 'omod'`);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
let output = this;
|
let output = this;
|
||||||
|
|
@ -3775,19 +3778,18 @@ Pattern.prototype.modulate = function (type, config, idx) {
|
||||||
/**
|
/**
|
||||||
* Configures an LFO. Can be called in sequence like pat.lfo(...).lfo(...) to set up multiple LFOs
|
* Configures an LFO. Can be called in sequence like pat.lfo(...).lfo(...) to set up multiple LFOs
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @name lfo
|
* @name lfo
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @param {Object} config LFO configuration.
|
* @param {Object} config LFO configuration.
|
||||||
* @param {string | Pattern} [config.target] Node (and parameter if specified like `lpf.frequency`) to modulate. Aliases: target, t
|
* @param {string | Pattern} [config.target] Node (and parameter if specified like `lpf.frequency`) to modulate. Aliases: t
|
||||||
* @param {number | Pattern} [config.rate] Modulation rate. Aliases: rate, r
|
* @param {number | Pattern} [config.rate] Modulation rate. Aliases: rate, r
|
||||||
* @param {number | Pattern} [config.depth] Modulation depth. Aliases: dep, dp, d
|
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
||||||
|
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
||||||
* @param {number | Pattern} [config.dcoffset] DC offset / bias for the waveform. Aliases: dc
|
* @param {number | Pattern} [config.dcoffset] DC offset / bias for the waveform. Aliases: dc
|
||||||
* @param {number | Pattern} [config.shape] Waveform shape index. Aliases: sh
|
* @param {number | Pattern} [config.shape] Waveform shape index. Aliases: sh
|
||||||
* @param {number | Pattern} [config.skew] Waveform skew amount. Aliases: sk
|
* @param {number | Pattern} [config.skew] Waveform skew amount. Aliases: sk
|
||||||
* @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: c
|
* @param {number | Pattern} [config.curve] Exponential curve amount. Aliases: c
|
||||||
* @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s
|
* @param {number | Pattern} [config.sync] Tempo-synced modulation rate. Aliases: s
|
||||||
* @param {number | null} idx Index of the LFO slot to overwrite. Omit to append a new LFO
|
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.lfo = function (config, idx) {
|
Pattern.prototype.lfo = function (config, idx) {
|
||||||
|
|
@ -3798,12 +3800,12 @@ export const lfo = (config) => pure({}).lfo(config);
|
||||||
/**
|
/**
|
||||||
* Configures an envelope. Can be called in sequence like pat.env(...).env(...) to set up multiple envelopes
|
* Configures an envelope. Can be called in sequence like pat.env(...).env(...) to set up multiple envelopes
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @name env
|
* @name env
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @param {Object} config Envelope configuration.
|
* @param {Object} config Envelope configuration.
|
||||||
* @param {string | Pattern} [config.target] Node (and parameter if specified like `lpf.frequency`) to modulate. Aliases: target, t
|
* @param {string | Pattern} [config.target] Node (and parameter if specified like `lpf.frequency`) to modulate. Aliases: t
|
||||||
* @param {number | Pattern} [config.depth] Modulation depth. Aliases: dep, dp
|
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
||||||
|
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
||||||
* @param {number | Pattern} [config.attack] Time to reach depth. Aliases: att, a
|
* @param {number | Pattern} [config.attack] Time to reach depth. Aliases: att, a
|
||||||
* @param {number | Pattern} [config.decay] Time to reach sustain. Aliases: dec, d
|
* @param {number | Pattern} [config.decay] Time to reach sustain. Aliases: dec, d
|
||||||
* @param {number | Pattern} [config.sustain] Sustain depth. Aliases: sus, s
|
* @param {number | Pattern} [config.sustain] Sustain depth. Aliases: sus, s
|
||||||
|
|
@ -3811,7 +3813,6 @@ export const lfo = (config) => pure({}).lfo(config);
|
||||||
* @param {number | Pattern} [config.acurve] Snappiness of attack curve (-1 = relaxed, 1 = snappy). Aliases: ac
|
* @param {number | Pattern} [config.acurve] Snappiness of attack curve (-1 = relaxed, 1 = snappy). Aliases: ac
|
||||||
* @param {number | Pattern} [config.dcurve] Snappiness of decay curve (-1 = relaxed, 1 = snappy). Aliases: dc
|
* @param {number | Pattern} [config.dcurve] Snappiness of decay curve (-1 = relaxed, 1 = snappy). Aliases: dc
|
||||||
* @param {number | Pattern} [config.rcurve] Snappiness of release curve (-1 = relaxed, 1 = snappy). Aliases: rc
|
* @param {number | Pattern} [config.rcurve] Snappiness of release curve (-1 = relaxed, 1 = snappy). Aliases: rc
|
||||||
* @param {number | null} idx Index of the envelope slot to overwrite. Omit to append a new envelope
|
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.env = function (config, idx) {
|
Pattern.prototype.env = function (config, idx) {
|
||||||
|
|
@ -3820,21 +3821,21 @@ Pattern.prototype.env = function (config, idx) {
|
||||||
export const env = (config) => pure({}).env(config);
|
export const env = (config) => pure({}).env(config);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the output of this pattern to a parameter on another pattern.
|
* Modulates with the output from a given `orbit`
|
||||||
* Can be called in sequence like pat.send(...).send(...) to send to multiple parameters
|
* Can be called in sequence like pat.obus(...).obus(...) to set up multiple modulators
|
||||||
*
|
*
|
||||||
*
|
* @name omod
|
||||||
* @name send
|
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @param {Object} config Send configuration.
|
* @param {Object} config Orbit bus configuration.
|
||||||
* @param {string | Pattern} [config.id] Pattern id to modulate
|
* @param {string | Pattern} [config.orbit] Orbit to get modulation signal from
|
||||||
* @param {string | Pattern} [config.target] Node (and parameter if specified like `lpf.frequency`) to modulate. Aliases: target, t
|
* @param {string | Pattern} [config.target] Node (and parameter if specified like `lpf.frequency`) to modulate. Aliases: t
|
||||||
* @param {number | Pattern} [config.depth] Modulation depth. Aliases:dep, dp, d
|
* @param {number | Pattern} [config.depth] Relative modulation depth. Aliases: dep, dr
|
||||||
|
* @param {number | Pattern} [config.depthabs] Absolute modulation depth. Aliases: da
|
||||||
|
* @param {number | Pattern} [config.ratio] Modulation ratio. Aliases: rat
|
||||||
* @param {number | Pattern} [config.dc] DC offset prior to application
|
* @param {number | Pattern} [config.dc] DC offset prior to application
|
||||||
* @param {number | null} idx Index of the send slot to overwrite. Omit to append a new send
|
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
*/
|
*/
|
||||||
Pattern.prototype.send = function (config, idx) {
|
Pattern.prototype.omod = function (config, idx) {
|
||||||
return this.modulate('send', config, idx);
|
return this.modulate('omod', config, idx);
|
||||||
};
|
};
|
||||||
export const send = (config) => pure({}).send(config);
|
export const omod = (config) => pure({}).omod(config);
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,7 @@ export function registerSoundfonts() {
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
node.disconnect();
|
node.disconnect();
|
||||||
onended();
|
onended();
|
||||||
};
|
});
|
||||||
return { node, stop, source: bufferSource };
|
return { node, stop, source: bufferSource };
|
||||||
},
|
},
|
||||||
{ type: 'soundfont', prebake: true, fonts },
|
{ type: 'soundfont', prebake: true, fonts },
|
||||||
|
|
|
||||||
|
|
@ -111,13 +111,11 @@ export function getEnvelope(audioContext, properties = {}) {
|
||||||
|
|
||||||
export function getLfo(audioContext, properties = {}) {
|
export function getLfo(audioContext, properties = {}) {
|
||||||
// Extract some params we need for deriving other params
|
// Extract some params we need for deriving other params
|
||||||
const { begin, shape = 0, ...props } = properties;
|
const { shape = 0, ...props } = properties;
|
||||||
const lfoprops = {
|
const lfoprops = {
|
||||||
time: begin,
|
|
||||||
shape: getModulationShapeInput(shape),
|
shape: getModulationShapeInput(shape),
|
||||||
...props,
|
...props,
|
||||||
};
|
};
|
||||||
|
|
||||||
return getWorklet(audioContext, 'lfo-processor', lfoprops);
|
return getWorklet(audioContext, 'lfo-processor', lfoprops);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,12 +13,10 @@ import {
|
||||||
createFilter,
|
createFilter,
|
||||||
effectSend,
|
effectSend,
|
||||||
gainNode,
|
gainNode,
|
||||||
getADSRValues,
|
|
||||||
getCompressor,
|
getCompressor,
|
||||||
getDistortion,
|
getDistortion,
|
||||||
getEnvelope,
|
getEnvelope,
|
||||||
getLfo,
|
getLfo,
|
||||||
getParamADSR,
|
|
||||||
getWorklet,
|
getWorklet,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
|
|
@ -365,7 +363,6 @@ export function resetGlobalEffects() {
|
||||||
controller?.reset();
|
controller?.reset();
|
||||||
analysers = {};
|
analysers = {};
|
||||||
analysersData = {};
|
analysersData = {};
|
||||||
idToNodes = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getNodeParam(node, name) {
|
function _getNodeParam(node, name) {
|
||||||
|
|
@ -455,12 +452,15 @@ function _getTargetParams(nodes, target) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectLFO(idx, params, nodeTracker) {
|
function connectLFO(idx, params, nodeTracker) {
|
||||||
const { rate = 1, sync, cps, target, ...filteredParams } = params;
|
// TODO: figure out min/max values and how to handle depth and depthabs here.
|
||||||
|
const { rate = 1, sync, cps, cycle, target, ...filteredParams } = params;
|
||||||
filteredParams['frequency'] = sync !== undefined ? sync / cps : rate;
|
filteredParams['frequency'] = sync !== undefined ? sync / cps : rate;
|
||||||
|
filteredParams['time'] = cycle / cps;
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const lfoNode = getLfo(ac, filteredParams);
|
const lfoNode = getLfo(ac, filteredParams);
|
||||||
nodeTracker[`lfo${idx}`] = [lfoNode];
|
nodeTracker[`lfo${idx}`] = [lfoNode];
|
||||||
_getTargetParams(nodeTracker, target).forEach((t) => lfoNode.connect(t));
|
_getTargetParams(nodeTracker, target).forEach((t) => lfoNode.connect(t));
|
||||||
|
return lfoNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectEnvelope(idx, params, nodeTracker) {
|
function connectEnvelope(idx, params, nodeTracker) {
|
||||||
|
|
@ -474,10 +474,12 @@ function connectEnvelope(idx, params, nodeTracker) {
|
||||||
});
|
});
|
||||||
nodeTracker[`env${idx}`] = [envNode];
|
nodeTracker[`env${idx}`] = [envNode];
|
||||||
_getTargetParams(nodeTracker, target).forEach((t) => envNode.connect(t));
|
_getTargetParams(nodeTracker, target).forEach((t) => envNode.connect(t));
|
||||||
|
return envNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectSendModulator(params, signal, nodeTracker, chainID) {
|
function connectOrbitModulator(params, nodeTracker) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
|
const signal = controller.getOrbit(params.orbit).output;
|
||||||
const dc = new ConstantSourceNode(ac, { offset: params.dc ?? 0 });
|
const dc = new ConstantSourceNode(ac, { offset: params.dc ?? 0 });
|
||||||
dc.start(params.begin);
|
dc.start(params.begin);
|
||||||
const shifted = dc.connect(gainNode(1));
|
const shifted = dc.connect(gainNode(1));
|
||||||
|
|
@ -491,15 +493,6 @@ function connectSendModulator(params, signal, nodeTracker, chainID) {
|
||||||
0,
|
0,
|
||||||
params.begin,
|
params.begin,
|
||||||
);
|
);
|
||||||
webAudioTimeout(
|
|
||||||
ac,
|
|
||||||
() => {
|
|
||||||
modulator.disconnect();
|
|
||||||
delete pendingConnections[params.id][chainID];
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
params.end + 0.05,
|
|
||||||
);
|
|
||||||
return { modulator, nodes: [dc, shifted, modulator] };
|
return { modulator, nodes: [dc, shifted, modulator] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -510,8 +503,6 @@ function mapChannelNumbers(channels) {
|
||||||
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
return (Array.isArray(channels) ? channels : [channels]).map((ch) => ch - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = {};
|
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
|
||||||
|
|
@ -641,7 +632,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
activeSoundSources.delete(chainID);
|
activeSoundSources.delete(chainID);
|
||||||
}
|
}
|
||||||
|
|
||||||
let audioNodes = [];
|
const audioNodes = [];
|
||||||
|
|
||||||
if (['-', '~', '_'].includes(s)) {
|
if (['-', '~', '_'].includes(s)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -939,26 +930,28 @@ 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.push(...chain);
|
||||||
|
|
||||||
// finally, now that `nodes` is populated, set up modulators
|
// finally, now that `nodes` is populated, set up modulators
|
||||||
if (value.lfo) {
|
if (value.lfo) {
|
||||||
for (const [idx, params] of Object.entries(value.lfo)) {
|
for (const [idx, params] of Object.entries(value.lfo)) {
|
||||||
connectLFO(
|
const lfo = connectLFO(
|
||||||
idx,
|
idx,
|
||||||
{
|
{
|
||||||
...params,
|
...params,
|
||||||
cps,
|
cps,
|
||||||
|
cycle,
|
||||||
begin: t,
|
begin: t,
|
||||||
end: endWithRelease,
|
end: endWithRelease,
|
||||||
},
|
},
|
||||||
nodes,
|
nodes,
|
||||||
);
|
);
|
||||||
|
audioNodes.push(lfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (value.env) {
|
if (value.env) {
|
||||||
for (const [idx, params] of Object.entries(value.env)) {
|
for (const [idx, params] of Object.entries(value.env)) {
|
||||||
connectEnvelope(
|
const env = connectEnvelope(
|
||||||
idx,
|
idx,
|
||||||
{
|
{
|
||||||
...params,
|
...params,
|
||||||
|
|
@ -967,57 +960,17 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
},
|
},
|
||||||
nodes,
|
nodes,
|
||||||
);
|
);
|
||||||
|
audioNodes.push(env);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (value.id) {
|
if (value.omod) {
|
||||||
idToNodes[value.id] = new WeakRef(nodes);
|
for (const p of value.omod) {
|
||||||
}
|
const { nodes: nodesToCleanup } = connectOrbitModulator(
|
||||||
if (value.send) {
|
{ ...p, begin: t, end: endWithRelease },
|
||||||
for (const p of value.send) {
|
nodes,
|
||||||
const modNodes = idToNodes[p.id].deref();
|
chainID,
|
||||||
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(', ')}`,
|
|
||||||
);
|
|
||||||
delete idToNodes[p.id];
|
|
||||||
} else {
|
|
||||||
const { modulator, nodes: nodesToCleanup } = connectSendModulator(
|
|
||||||
{ ...p, begin: t, end: endWithRelease },
|
|
||||||
post,
|
|
||||||
modNodes,
|
|
||||||
chainID,
|
|
||||||
);
|
|
||||||
audioNodes = audioNodes.concat(nodesToCleanup);
|
|
||||||
pendingConnections[p.id] ??= {};
|
|
||||||
pendingConnections[p.id][chainID] = new WeakRef([modulator, p.target, endWithRelease]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (value.id in pendingConnections) {
|
|
||||||
for (const data of Object.values(pendingConnections[value.id])) {
|
|
||||||
const derefData = data.deref();
|
|
||||||
if (!derefData) {
|
|
||||||
delete pendingConnections[value.id];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const [modulator, target, end] = derefData;
|
|
||||||
const targets = _getTargetParams(nodes, target);
|
|
||||||
webAudioTimeout(
|
|
||||||
ac,
|
|
||||||
() => {
|
|
||||||
targets.forEach((target) => modulator.connect(target));
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
t,
|
|
||||||
);
|
|
||||||
webAudioTimeout(
|
|
||||||
ac,
|
|
||||||
() => {
|
|
||||||
modulator.disconnect();
|
|
||||||
},
|
|
||||||
0,
|
|
||||||
end,
|
|
||||||
);
|
);
|
||||||
|
audioNodes.push(...nodesToCleanup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue