Working version
This commit is contained in:
parent
ad43259353
commit
82ca77be9d
4 changed files with 191 additions and 32 deletions
|
|
@ -3758,19 +3758,26 @@ Pattern.prototype.modulate = function (type, config, idx) {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
let output = this;
|
let output = this;
|
||||||
|
let target = config.target;
|
||||||
for (const [rawKey, value] of Object.entries(config)) {
|
for (const [rawKey, value] of Object.entries(config)) {
|
||||||
const key = resolveConfigKey(type, rawKey);
|
const key = resolveConfigKey(type, rawKey);
|
||||||
const pat = reify(value);
|
if (key === 'target') continue; // we will set/default it below
|
||||||
|
const valuePat = reify(value);
|
||||||
output = output
|
output = output
|
||||||
.fmap((v) => (c) => {
|
.fmap((v) => (c) => {
|
||||||
|
if (target == null) {
|
||||||
|
// default target to the control set just before this in the chain
|
||||||
|
// e.g. pat.gain(0.5).lfo({..}) will be a gain-LFO
|
||||||
|
target = Object.keys(v).at(-1);
|
||||||
|
}
|
||||||
v[type] ??= [];
|
v[type] ??= [];
|
||||||
const t = v[type];
|
const t = v[type];
|
||||||
idx ??= t.length;
|
idx ??= t.length;
|
||||||
t[idx] ??= {};
|
t[idx] ??= { target }; // set target
|
||||||
t[idx][key] = c;
|
t[idx][key] = c;
|
||||||
return v;
|
return v;
|
||||||
})
|
})
|
||||||
.appLeft(pat);
|
.appLeft(valuePat);
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import { errorLogger, logger } from './logger.mjs';
|
||||||
import { loadBuffer } from './sampler.mjs';
|
import { loadBuffer } from './sampler.mjs';
|
||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import { SuperdoughAudioController } from './superdoughoutput.mjs';
|
import { SuperdoughAudioController } from './superdoughoutput.mjs';
|
||||||
|
import { getSuperdoughControlData } from './superdoughdata.mjs';
|
||||||
|
|
||||||
export const DEFAULT_MAX_POLYPHONY = 128;
|
export const DEFAULT_MAX_POLYPHONY = 128;
|
||||||
const DEFAULT_AUDIO_DEVICE_NAME = 'System Standard';
|
const DEFAULT_AUDIO_DEVICE_NAME = 'System Standard';
|
||||||
|
|
@ -416,6 +417,28 @@ const targetToParamGuess = {
|
||||||
send: 'depth',
|
send: 'depth',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const controlData = getSuperdoughControlData();
|
||||||
|
|
||||||
|
// TODO: We would like to use the aliases in `controls` here, but cannot as
|
||||||
|
// the modules are independent. We may want to inject that method here in situations
|
||||||
|
// where superdough/core are run in tandem
|
||||||
|
export let getControlName;
|
||||||
|
const _getMainName = (control) => getControlName?.(control) ?? control;
|
||||||
|
|
||||||
|
function _getControlData(control) {
|
||||||
|
return controlData[_getMainName(control)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getControlValue(control, value, data) {
|
||||||
|
const main = _getMainName(control);
|
||||||
|
return Number(value[main] ?? data.default);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getControlClamp(data, currentValue) {
|
||||||
|
const { min, max } = data;
|
||||||
|
return { min: min - currentValue, max: max - currentValue };
|
||||||
|
}
|
||||||
|
|
||||||
function _getTargetParams(nodes, target) {
|
function _getTargetParams(nodes, target) {
|
||||||
let param;
|
let param;
|
||||||
if (target.includes('.')) {
|
if (target.includes('.')) {
|
||||||
|
|
@ -451,49 +474,77 @@ function _getTargetParams(nodes, target) {
|
||||||
return audioParams;
|
return audioParams;
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectLFO(idx, params, nodeTracker) {
|
function _getTargetParamsForControl(control, nodes) {
|
||||||
// TODO: figure out min/max values and how to handle depth and depthabs here.
|
const main = _getMainName(control);
|
||||||
const { rate = 1, sync, cps, cycle, target, ...filteredParams } = params;
|
const data = _getControlData(main);
|
||||||
filteredParams['frequency'] = sync !== undefined ? sync / cps : rate;
|
const targetParams = _getTargetParams(nodes, data.param);
|
||||||
filteredParams['time'] = cycle / cps;
|
return { targetParams, data };
|
||||||
const ac = getAudioContext();
|
}
|
||||||
const lfoNode = getLfo(ac, filteredParams);
|
|
||||||
|
function connectLFO(idx, params, nodeTracker, value) {
|
||||||
|
const { rate = 1, sync, cps, cycle, target, depth = 1, depthabs, ...filteredParams } = params;
|
||||||
|
const { targetParams, data } = _getTargetParamsForControl(target, nodeTracker);
|
||||||
|
const currentValue = _getControlValue(target, value, data);
|
||||||
|
const { min, max } = _getControlClamp(data, currentValue);
|
||||||
|
const depthValue = depthabs != null ? depthabs : depth * currentValue;
|
||||||
|
const modParams = {
|
||||||
|
...filteredParams,
|
||||||
|
frequency: sync !== undefined ? sync / cps : rate,
|
||||||
|
time: cycle / cps,
|
||||||
|
depth: depthValue,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
};
|
||||||
|
const lfoNode = getLfo(getAudioContext(), modParams);
|
||||||
nodeTracker[`lfo${idx}`] = [lfoNode];
|
nodeTracker[`lfo${idx}`] = [lfoNode];
|
||||||
_getTargetParams(nodeTracker, target).forEach((t) => lfoNode.connect(t));
|
targetParams.forEach((t) => lfoNode.connect(t));
|
||||||
return lfoNode;
|
return lfoNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectEnvelope(idx, params, nodeTracker) {
|
function connectEnvelope(idx, params, nodeTracker, value) {
|
||||||
const { target, acurve, dcurve, rcurve, ...filteredParams } = params;
|
const { target, acurve, dcurve, rcurve, depth = 1, depthabs, ...filteredParams } = params;
|
||||||
const ac = getAudioContext();
|
const { targetParams, data } = _getTargetParamsForControl(target, nodeTracker);
|
||||||
const envNode = getEnvelope(ac, {
|
const currentValue = _getControlValue(target, value, data);
|
||||||
|
const { min, max } = _getControlClamp(data, currentValue);
|
||||||
|
const depthValue = depthabs != null ? depthabs : depth * currentValue;
|
||||||
|
const envNode = getEnvelope(getAudioContext(), {
|
||||||
...filteredParams,
|
...filteredParams,
|
||||||
|
depth: depthValue,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
attackCurve: acurve,
|
attackCurve: acurve,
|
||||||
decayCurve: dcurve,
|
decayCurve: dcurve,
|
||||||
releaseCurve: rcurve,
|
releaseCurve: rcurve,
|
||||||
});
|
});
|
||||||
nodeTracker[`env${idx}`] = [envNode];
|
nodeTracker[`env${idx}`] = [envNode];
|
||||||
_getTargetParams(nodeTracker, target).forEach((t) => envNode.connect(t));
|
targetParams.forEach((t) => envNode.connect(t));
|
||||||
return envNode;
|
return envNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectOrbitModulator(params, nodeTracker) {
|
function connectOrbitModulator(params, nodeTracker, value) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
|
const { target, depth = 1, depthabs } = params;
|
||||||
const signal = controller.getOrbit(params.orbit).output;
|
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));
|
||||||
signal.connect(shifted);
|
signal.connect(shifted);
|
||||||
const modulator = shifted.connect(gainNode((params.depth ?? 1) / 0.3));
|
const { targetParams, data } = _getTargetParamsForControl(target, nodeTracker);
|
||||||
|
const currentValue = _getControlValue(target, value, data);
|
||||||
|
const { min, max } = _getControlClamp(data, currentValue);
|
||||||
|
const depthValue = depthabs != null ? depthabs : depth * currentValue;
|
||||||
|
const maxAbsDepth = Math.min(Math.abs(min), Math.abs(max));
|
||||||
|
const boundedDepth = Math.min(Math.abs(depthValue), maxAbsDepth);
|
||||||
|
const modulator = shifted.connect(gainNode((Math.sign(depthValue) * boundedDepth) / 0.3));
|
||||||
webAudioTimeout(
|
webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
_getTargetParams(nodeTracker, params.target).forEach((t) => modulator.connect(t));
|
targetParams.forEach((t) => modulator.connect(t));
|
||||||
},
|
},
|
||||||
0,
|
0,
|
||||||
params.begin,
|
params.begin,
|
||||||
);
|
);
|
||||||
return { modulator, nodes: [dc, shifted, modulator] };
|
return { modulator, toCleanup: [dc, shifted, modulator] };
|
||||||
}
|
}
|
||||||
|
|
||||||
let activeSoundSources = new Map();
|
let activeSoundSources = new Map();
|
||||||
|
|
@ -710,11 +761,13 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
lpParams.type = 'lowpass';
|
lpParams.type = 'lowpass';
|
||||||
const { filter: lpf1, lfo: lfo1 } = filt(lpParams);
|
const { filter: lpf1, lfo: lfo1 } = filt(lpParams);
|
||||||
nodes['lpf'] = [lpf1];
|
nodes['lpf'] = [lpf1];
|
||||||
|
nodes['lpf_lfo'] = [lfo1];
|
||||||
chain.push(lpf1);
|
chain.push(lpf1);
|
||||||
lfo1 && audioNodes.push(lfo1);
|
lfo1 && audioNodes.push(lfo1);
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
const { filter: lpf2, lfo: lfo2 } = filt(lpParams);
|
const { filter: lpf2, lfo: lfo2 } = filt(lpParams);
|
||||||
nodes['lpf'].push(lpf2);
|
nodes['lpf'].push(lpf2);
|
||||||
|
nodes['lpf_lfo'].push(lfo2);
|
||||||
chain.push(lpf2);
|
chain.push(lpf2);
|
||||||
lfo2 && audioNodes.push(lfo2);
|
lfo2 && audioNodes.push(lfo2);
|
||||||
}
|
}
|
||||||
|
|
@ -744,11 +797,13 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
hpParams.type = 'highpass';
|
hpParams.type = 'highpass';
|
||||||
const { filter: hpf1, lfo: lfo1 } = filt(hpParams);
|
const { filter: hpf1, lfo: lfo1 } = filt(hpParams);
|
||||||
nodes['hpf'] = [hpf1];
|
nodes['hpf'] = [hpf1];
|
||||||
|
nodes['hpf_lfo'] = [lfo1];
|
||||||
lfo1 && audioNodes.push(lfo1);
|
lfo1 && audioNodes.push(lfo1);
|
||||||
chain.push(hpf1);
|
chain.push(hpf1);
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
const { filter: hpf2, lfo: lfo2 } = filt(hpParams);
|
const { filter: hpf2, lfo: lfo2 } = filt(hpParams);
|
||||||
nodes['hpf'].push(hpf2);
|
nodes['hpf'].push(hpf2);
|
||||||
|
nodes['hpf_lfo'].push(lfo2);
|
||||||
chain.push(hpf2);
|
chain.push(hpf2);
|
||||||
lfo2 && audioNodes.push(lfo2);
|
lfo2 && audioNodes.push(lfo2);
|
||||||
}
|
}
|
||||||
|
|
@ -777,12 +832,16 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
const bpParams = pickAndRename(value, bpMap);
|
const bpParams = pickAndRename(value, bpMap);
|
||||||
bpParams.type = 'bandpass';
|
bpParams.type = 'bandpass';
|
||||||
const { filter: bpf1, lfo: lfo1 } = filt(bpParams);
|
const { filter: bpf1, lfo: lfo1 } = filt(bpParams);
|
||||||
|
nodes['bpf'] = [bpf1];
|
||||||
|
nodes['bpf_lfo'] = [lfo1];
|
||||||
chain.push(bpf1);
|
chain.push(bpf1);
|
||||||
lfo1 && audioNodes.push(lfo1);
|
lfo1 && audioNodes.push(lfo1);
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
const { filter: bpf2, lfo: lfo2 } = filt(bpParams);
|
const { filter: bpf2, lfo: lfo2 } = filt(bpParams);
|
||||||
nodes['bpf'].push(bpf2);
|
nodes['bpf'].push(bpf2);
|
||||||
|
nodes['bpf_lfo'].push(lfo2);
|
||||||
chain.push(bpf2);
|
chain.push(bpf2);
|
||||||
|
lfo2 && audioNodes.push(lfo2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -847,6 +906,8 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
begin: t,
|
begin: t,
|
||||||
end: endWithRelease,
|
end: endWithRelease,
|
||||||
});
|
});
|
||||||
|
nodes['tremolo'] = [lfo];
|
||||||
|
nodes['tremolo_gain'] = [amGain];
|
||||||
lfo.connect(amGain.gain);
|
lfo.connect(amGain.gain);
|
||||||
audioNodes.push(lfo);
|
audioNodes.push(lfo);
|
||||||
chain.push(amGain);
|
chain.push(amGain);
|
||||||
|
|
@ -904,7 +965,8 @@ 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);
|
||||||
}
|
}
|
||||||
orbitBus.getReverb(roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin);
|
const roomNode = orbitBus.getReverb(roomsize, roomfade, roomlp, roomdim, roomIR, irspeed, irbegin);
|
||||||
|
nodes['room'] = [roomNode];
|
||||||
const reverbSend = orbitBus.sendReverb(post, room);
|
const reverbSend = orbitBus.sendReverb(post, room);
|
||||||
audioNodes.push(reverbSend);
|
audioNodes.push(reverbSend);
|
||||||
}
|
}
|
||||||
|
|
@ -945,6 +1007,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
end: endWithRelease,
|
end: endWithRelease,
|
||||||
},
|
},
|
||||||
nodes,
|
nodes,
|
||||||
|
value,
|
||||||
);
|
);
|
||||||
audioNodes.push(lfo);
|
audioNodes.push(lfo);
|
||||||
}
|
}
|
||||||
|
|
@ -959,18 +1022,15 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
end: endWithRelease,
|
end: endWithRelease,
|
||||||
},
|
},
|
||||||
nodes,
|
nodes,
|
||||||
|
value,
|
||||||
);
|
);
|
||||||
audioNodes.push(env);
|
audioNodes.push(env);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (value.omod) {
|
if (value.omod) {
|
||||||
for (const p of value.omod) {
|
for (const p of value.omod) {
|
||||||
const { nodes: nodesToCleanup } = connectOrbitModulator(
|
const { toCleanup } = connectOrbitModulator({ ...p, begin: t, end: endWithRelease }, nodes, value);
|
||||||
{ ...p, begin: t, end: endWithRelease },
|
audioNodes.push(...toCleanup);
|
||||||
nodes,
|
|
||||||
chainID,
|
|
||||||
);
|
|
||||||
audioNodes.push(...nodesToCleanup);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
85
packages/superdough/superdoughdata.mjs
Normal file
85
packages/superdough/superdoughdata.mjs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
/*
|
||||||
|
superdoughdata.mjs - Data needed for running superdough (defaults, mappings, etc.)
|
||||||
|
Copyright (C) 2025 Strudel contributors - see <https://codeberg.org/uzu/strudel/src/branch/main/packages/superdough/superdoughdata.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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const CONTROL_DATA = {
|
||||||
|
stretch: { param: 'stretch.pitchFactor', default: 1, min: -4, max: 100 },
|
||||||
|
gain: { param: 'gain.gain', default: 0.8, min: 0, max: 10 },
|
||||||
|
postgain: { param: 'post.gain', default: 1, min: 0, max: 10 },
|
||||||
|
pan: { param: 'pan.pan', min: 0, max: 1 },
|
||||||
|
tremolo: { param: 'tremolo.rate', min: 0, max: 40 },
|
||||||
|
tremolosync: { param: 'tremolo.sync', min: 0, max: 8 },
|
||||||
|
tremolodepth: { param: 'tremolo_gain.gain', default: 1, min: 0, max: 10 },
|
||||||
|
tremoloskew: { param: 'tremolo.skew', min: 0, max: 1 },
|
||||||
|
tremolophase: { param: 'tremolo.phase', default: 0, min: 0, max: 1 },
|
||||||
|
tremoloshape: { param: 'tremolo.shape', min: 0, max: 4 },
|
||||||
|
|
||||||
|
// LPF
|
||||||
|
cutoff: { param: 'lpf.frequency', min: 20, max: 24000 },
|
||||||
|
resonance: { param: 'lpf.Q', min: 0.1, max: 30 },
|
||||||
|
lprate: { param: 'lpf_lfo.rate', min: 0, max: 40 },
|
||||||
|
lpsync: { param: 'lpf_lfo.sync', min: 0, max: 8 },
|
||||||
|
lpdepth: { param: 'lpf_lfo.depth', min: 20, max: 24000 },
|
||||||
|
lpdepthfrequency: { param: 'lpf_lfo.depth', min: 20, max: 24000 },
|
||||||
|
lpshape: { param: 'lpf_lfo.shape', min: 0, max: 4 },
|
||||||
|
lpdc: { param: 'lpf_lfo.dcoffset', min: -1, max: 1 },
|
||||||
|
lpskew: { param: 'lpf_lfo.skew', min: 0, max: 1 },
|
||||||
|
|
||||||
|
// HPF
|
||||||
|
hcutoff: { param: 'hpf.frequency', min: 20, max: 24000 },
|
||||||
|
hresonance: { param: 'hpf.Q', min: 0.1, max: 30 },
|
||||||
|
|
||||||
|
// BPF
|
||||||
|
bandf: { param: 'bpf.frequency', min: 20, max: 24000 },
|
||||||
|
bandq: { param: 'bpf.Q', min: 0.1, max: 30 },
|
||||||
|
|
||||||
|
// FILTERS
|
||||||
|
// fanchor: { param: ['lpf.anchor', 'hpf.anchor', 'bpf.anchor'], min: 0, max: 1 },
|
||||||
|
// drive: { param: ['lpf.drive', 'hpf.drive', 'bpf.drive'], min: 0, max: 2 },
|
||||||
|
// vowel: { param: 'vowel.frequency', min: 200, max: 4000 },
|
||||||
|
|
||||||
|
// DISTORTION
|
||||||
|
coarse: { param: 'coarse.coarse', min: 1, max: 64 },
|
||||||
|
crush: { param: 'crush.crush', min: 1, max: 16 },
|
||||||
|
shape: { param: 'shape.shape', min: -1, max: 0.999 },
|
||||||
|
shapevol: { param: 'shape.postgain', default: 1, min: 0, max: 1 },
|
||||||
|
distort: { param: 'distort.distort', min: 0, max: 5 },
|
||||||
|
distortvol: { param: 'distort.postgain', default: 1, min: 0, max: 1 },
|
||||||
|
// distorttype: { param: 'distort.algorithm', default: 0, min: 0, max: 4 },
|
||||||
|
|
||||||
|
// COMPRESSOR
|
||||||
|
compressor: { param: 'compressor.threshold', default: -3, min: -100, max: 0 },
|
||||||
|
compressorRatio: { param: 'compressor.ratio', default: 10, min: 1, max: 20 },
|
||||||
|
compressorKnee: { param: 'compressor.knee', default: 10, min: 0, max: 40 },
|
||||||
|
compressorAttack: { param: 'compressor.attack', default: 0.005, min: 0, max: 1 },
|
||||||
|
compressorRelease: { param: 'compressor.release', default: 0.05, min: 0, max: 2 },
|
||||||
|
|
||||||
|
// PHASER
|
||||||
|
phaserrate: { param: 'phaser.rate', min: 0, max: 40 },
|
||||||
|
phaserdepth: { param: 'phaser.depth', default: 0.75, min: 0, max: 1 },
|
||||||
|
phasersweep: { param: 'phaser.sweep', min: 0, max: 5000 },
|
||||||
|
phasercenter: { param: 'phaser.frequency', min: 20, max: 24000 },
|
||||||
|
|
||||||
|
// ORBIT EFFECTS
|
||||||
|
// delay: { param: 'delay.delayTime', default: 0, min: 0, max: 4 },
|
||||||
|
delaytime: { param: 'delay.delayTime', min: 0, max: 4 },
|
||||||
|
// delayfeedback: { param: 'delay.feedback', default: 0.5, min: 0, max: 0.98 },
|
||||||
|
// delaysync: { param: 'delay.sync', default: 3 / 16, min: 0, max: 2 },
|
||||||
|
dry: { param: 'dry.gain', min: 0, max: 1 },
|
||||||
|
room: { param: 'room.wet', min: 0, max: 1 },
|
||||||
|
// roomfade: { param: 'room.fade', min: 0, max: 1 },
|
||||||
|
roomlp: { param: 'room.lp', min: 20, max: 24000 },
|
||||||
|
djf: { param: 'djf.value', min: 0, max: 1 },
|
||||||
|
|
||||||
|
// SYNTHS
|
||||||
|
detune: { param: 'source.detune', min: 0, max: 1 },
|
||||||
|
wt: { param: 'source.position', min: 0, max: 1 },
|
||||||
|
warp: { param: 'source.warp', min: 0, max: 1 },
|
||||||
|
freq: { param: 'source.frequency', min: 20, max: 24000 },
|
||||||
|
};
|
||||||
|
|
||||||
|
export function getSuperdoughControlData() {
|
||||||
|
return CONTROL_DATA;
|
||||||
|
}
|
||||||
|
|
@ -121,8 +121,8 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||||
{ name: 'shape', defaultValue: 0 },
|
{ name: 'shape', defaultValue: 0 },
|
||||||
{ name: 'curve', defaultValue: 1 },
|
{ name: 'curve', defaultValue: 1 },
|
||||||
{ name: 'dcoffset', defaultValue: 0 },
|
{ name: 'dcoffset', defaultValue: 0 },
|
||||||
{ name: 'min', defaultValue: 0 },
|
{ name: 'min', defaultValue: -1e9 },
|
||||||
{ name: 'max', defaultValue: 1 },
|
{ name: 'max', defaultValue: 1e9 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,8 +160,10 @@ class LFOProcessor extends AudioWorkletProcessor {
|
||||||
|
|
||||||
const dcoffset = parameters['dcoffset'][0];
|
const dcoffset = parameters['dcoffset'][0];
|
||||||
|
|
||||||
const min = dcoffset * depth;
|
const userMin = parameters['min'][0];
|
||||||
const max = dcoffset * depth + depth;
|
const userMax = parameters['max'][0];
|
||||||
|
const min = Math.min(userMin, userMax);
|
||||||
|
const max = Math.max(userMin, userMax);
|
||||||
const shape = waveShapeNames[parameters['shape'][0]];
|
const shape = waveShapeNames[parameters['shape'][0]];
|
||||||
|
|
||||||
const blockSize = output[0].length ?? 0;
|
const blockSize = output[0].length ?? 0;
|
||||||
|
|
@ -967,6 +969,8 @@ class EnvelopeProcessor extends AudioWorkletProcessor {
|
||||||
{ name: 'decayCurve', defaultValue: 0, minValue: -1, maxValue: 1 },
|
{ name: 'decayCurve', defaultValue: 0, minValue: -1, maxValue: 1 },
|
||||||
{ name: 'releaseCurve', defaultValue: 0, minValue: -1, maxValue: 1 },
|
{ name: 'releaseCurve', defaultValue: 0, minValue: -1, maxValue: 1 },
|
||||||
{ name: 'depth', defaultValue: 1 },
|
{ name: 'depth', defaultValue: 1 },
|
||||||
|
{ name: 'min', defaultValue: -1e9 },
|
||||||
|
{ name: 'max', defaultValue: 1e9 },
|
||||||
{ name: 'retrigger', defaultValue: 1, minValue: 0, maxValue: 1 },
|
{ name: 'retrigger', defaultValue: 1, minValue: 0, maxValue: 1 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
@ -1029,6 +1033,8 @@ class EnvelopeProcessor extends AudioWorkletProcessor {
|
||||||
const dCurve = pv(params.decayCurve, i);
|
const dCurve = pv(params.decayCurve, i);
|
||||||
const rCurve = pv(params.releaseCurve, i);
|
const rCurve = pv(params.releaseCurve, i);
|
||||||
const depth = pv(params.depth, i);
|
const depth = pv(params.depth, i);
|
||||||
|
const clampMin = pv(params.min, i);
|
||||||
|
const clampMax = pv(params.max, i);
|
||||||
const states = [
|
const states = [
|
||||||
{ time: Number.POSITIVE_INFINITY, start: 0, target: 0 }, // idle
|
{ time: Number.POSITIVE_INFINITY, start: 0, target: 0 }, // idle
|
||||||
{ time: attack, start: this.attackStart, target: 1, curve: aCurve },
|
{ time: attack, start: this.attackStart, target: 1, curve: aCurve },
|
||||||
|
|
@ -1042,7 +1048,8 @@ class EnvelopeProcessor extends AudioWorkletProcessor {
|
||||||
this.state = (this.state + 1) % states.length;
|
this.state = (this.state + 1) % states.length;
|
||||||
time = states[this.state].time;
|
time = states[this.state].time;
|
||||||
}
|
}
|
||||||
out[i] = this.val * depth;
|
const clamped = clamp(this.val * depth, Math.min(clampMin, clampMax), Math.max(clampMin, clampMax));
|
||||||
|
out[i] = clamped;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue