Merge branch 'main' into scale-docs
This commit is contained in:
commit
7f06f11e96
4 changed files with 274 additions and 103 deletions
|
|
@ -1288,6 +1288,151 @@ export const { fanchor } = registerControl('fanchor');
|
||||||
*/
|
*/
|
||||||
// currently an alias of 'hcutoff' https://codeberg.org/uzu/strudel/issues/496
|
// currently an alias of 'hcutoff' https://codeberg.org/uzu/strudel/issues/496
|
||||||
// ['hpf'],
|
// ['hpf'],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rate of the LFO for the lowpass filter
|
||||||
|
*
|
||||||
|
* @name lprate
|
||||||
|
* @param {number | Pattern} rate rate in hertz
|
||||||
|
*/
|
||||||
|
export const { lprate } = registerControl('lprate');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cycle-synced rate of the LFO for the lowpass filter
|
||||||
|
*
|
||||||
|
* @name lpsync
|
||||||
|
* @param {number | Pattern} rate rate in cycles
|
||||||
|
*/
|
||||||
|
export const { lpsync } = registerControl('lpsync');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depth of the LFO for the lowpass filter
|
||||||
|
*
|
||||||
|
* @name lpdepth
|
||||||
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
*/
|
||||||
|
export const { lpdepth } = registerControl('lpdepth');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape of the LFO for the lowpass filter
|
||||||
|
*
|
||||||
|
* @name lpshape
|
||||||
|
* @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
|
||||||
|
*/
|
||||||
|
export const { lpshape } = registerControl('lpshape');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DC offset of the LFO for the lowpass filter
|
||||||
|
*
|
||||||
|
* @name lpdc
|
||||||
|
* @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
|
||||||
|
*/
|
||||||
|
export const { lpdc } = registerControl('lpdc');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skew of the LFO for the lowpass filter
|
||||||
|
*
|
||||||
|
* @name lpskew
|
||||||
|
* @param {number | Pattern} skew How much to bend the LFO shape
|
||||||
|
*/
|
||||||
|
export const { lpskew } = registerControl('lpskew');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rate of the LFO for the bandpass filter
|
||||||
|
*
|
||||||
|
* @name bprate
|
||||||
|
* @param {number | Pattern} rate rate in hertz
|
||||||
|
*/
|
||||||
|
export const { bprate } = registerControl('bprate');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cycle-synced rate of the LFO for the bandpass filter
|
||||||
|
*
|
||||||
|
* @name bpsync
|
||||||
|
* @param {number | Pattern} rate rate in cycles
|
||||||
|
*/
|
||||||
|
export const { bpsync } = registerControl('bpsync');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depth of the LFO for the bandpass filter
|
||||||
|
*
|
||||||
|
* @name bpdepth
|
||||||
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
*/
|
||||||
|
export const { bpdepth } = registerControl('bpdepth');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape of the LFO for the bandpass filter
|
||||||
|
*
|
||||||
|
* @name bpshape
|
||||||
|
* @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
|
||||||
|
*/
|
||||||
|
export const { bpshape } = registerControl('bpshape');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DC offset of the LFO for the bandpass filter
|
||||||
|
*
|
||||||
|
* @name bpdc
|
||||||
|
* @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
|
||||||
|
*/
|
||||||
|
export const { bpdc } = registerControl('bpdc');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skew of the LFO for the bandpass filter
|
||||||
|
*
|
||||||
|
* @name bpskew
|
||||||
|
* @param {number | Pattern} skew How much to bend the LFO shape
|
||||||
|
*/
|
||||||
|
export const { bpskew } = registerControl('bpskew');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rate of the LFO for the highpass filter
|
||||||
|
*
|
||||||
|
* @name hprate
|
||||||
|
* @param {number | Pattern} rate rate in hertz
|
||||||
|
*/
|
||||||
|
export const { hprate } = registerControl('hprate');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cycle-synced rate of the LFO for the highpass filter
|
||||||
|
*
|
||||||
|
* @name hpsync
|
||||||
|
* @param {number | Pattern} rate rate in cycles
|
||||||
|
*/
|
||||||
|
export const { hpsync } = registerControl('hpsync');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depth of the LFO for the highpass filter
|
||||||
|
*
|
||||||
|
* @name hpdepth
|
||||||
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
*/
|
||||||
|
export const { hpdepth } = registerControl('hpdepth');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shape of the LFO for the highpass filter
|
||||||
|
*
|
||||||
|
* @name hpshape
|
||||||
|
* @param {number | Pattern} shape Shape of the lfo (0, 1, 2, ..)
|
||||||
|
*/
|
||||||
|
export const { hpshape } = registerControl('hpshape');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DC offset of the LFO for the highpass filter
|
||||||
|
*
|
||||||
|
* @name hpdc
|
||||||
|
* @param {number | Pattern} dcoffset dc offset. set to 0 for unipolar
|
||||||
|
*/
|
||||||
|
export const { hpdc } = registerControl('hpdc');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skew of the LFO for the highpass filter
|
||||||
|
*
|
||||||
|
* @name hpskew
|
||||||
|
* @param {number | Pattern} skew How much to bend the LFO shape
|
||||||
|
*/
|
||||||
|
export const { hpskew } = registerControl('hpskew');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies a vibrato to the frequency of the oscillator.
|
* Applies a vibrato to the frequency of the oscillator.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -154,6 +154,24 @@ export const getADSRValues = (params, curve = 'linear', defaultValues) => {
|
||||||
return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)];
|
return [Math.max(a ?? 0, envmin), Math.max(d ?? 0, envmin), Math.min(sustain, envmax), Math.max(r ?? 0, releaseMin)];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getParamLfo(audioContext, param, start, end, lfoValues) {
|
||||||
|
let { defaultDepth = 1, depth, dcoffset, ...getLfoInputs } = lfoValues;
|
||||||
|
if (depth == null) {
|
||||||
|
const hasLFOParams = Object.values(getLfoInputs).some((v) => v != null);
|
||||||
|
depth = hasLFOParams ? defaultDepth : 0;
|
||||||
|
}
|
||||||
|
let lfo;
|
||||||
|
if (depth) {
|
||||||
|
lfo = getLfo(audioContext, start, end, {
|
||||||
|
depth,
|
||||||
|
dcoffset,
|
||||||
|
...getLfoInputs,
|
||||||
|
});
|
||||||
|
lfo.connect(param);
|
||||||
|
}
|
||||||
|
return lfo;
|
||||||
|
}
|
||||||
|
|
||||||
// helper utility for applying standard modulators to a parameter
|
// helper utility for applying standard modulators to a parameter
|
||||||
export function applyParameterModulators(audioContext, param, start, end, envelopeValues, lfoValues) {
|
export function applyParameterModulators(audioContext, param, start, end, envelopeValues, lfoValues) {
|
||||||
let { amount, offset, defaultAmount = 1, curve = 'linear', values, holdEnd, defaultValues } = envelopeValues;
|
let { amount, offset, defaultAmount = 1, curve = 'linear', values, holdEnd, defaultValues } = envelopeValues;
|
||||||
|
|
@ -170,55 +188,58 @@ export function applyParameterModulators(audioContext, param, start, end, envelo
|
||||||
const [attack, decay, sustain, release] = getADSRValues(values, curve, defaultValues);
|
const [attack, decay, sustain, release] = getADSRValues(values, curve, defaultValues);
|
||||||
getParamADSR(param, attack, decay, sustain, release, min, max, start, holdEnd, curve);
|
getParamADSR(param, attack, decay, sustain, release, min, max, start, holdEnd, curve);
|
||||||
}
|
}
|
||||||
let lfo;
|
const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
|
||||||
let { defaultDepth = 1, depth, dcoffset, ...getLfoInputs } = lfoValues;
|
|
||||||
|
|
||||||
if (depth == null) {
|
|
||||||
const hasLFOParams = Object.values(getLfoInputs).some((v) => v != null);
|
|
||||||
depth = hasLFOParams ? defaultDepth : 0;
|
|
||||||
}
|
|
||||||
if (depth) {
|
|
||||||
lfo = getLfo(audioContext, start, end, {
|
|
||||||
depth,
|
|
||||||
dcoffset,
|
|
||||||
...getLfoInputs,
|
|
||||||
});
|
|
||||||
lfo.connect(param);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { lfo, disconnect: () => lfo?.disconnect() };
|
return { lfo, disconnect: () => lfo?.disconnect() };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createFilter(context, type, frequency, Q, att, dec, sus, rel, fenv, start, end, fanchor, model, drive) {
|
export function createFilter(context, start, end, params, cps) {
|
||||||
const curve = 'exponential';
|
let {
|
||||||
const [attack, decay, sustain, release] = getADSRValues([att, dec, sus, rel], curve, [0.005, 0.14, 0, 0.1]);
|
frequency,
|
||||||
let filter;
|
anchor,
|
||||||
let frequencyParam;
|
env,
|
||||||
|
type,
|
||||||
|
model,
|
||||||
|
q = 1,
|
||||||
|
drive = 0.69,
|
||||||
|
depth,
|
||||||
|
dcoffset = -0.5,
|
||||||
|
skew,
|
||||||
|
shape,
|
||||||
|
rate,
|
||||||
|
sync,
|
||||||
|
} = params;
|
||||||
|
let frequencyParam, filter;
|
||||||
if (model === 'ladder') {
|
if (model === 'ladder') {
|
||||||
filter = getWorklet(context, 'ladder-processor', { frequency, q: Q, drive });
|
filter = getWorklet(context, 'ladder-processor', { frequency, q, drive });
|
||||||
frequencyParam = filter.parameters.get('frequency');
|
frequencyParam = filter.parameters.get('frequency');
|
||||||
} else {
|
} else {
|
||||||
filter = context.createBiquadFilter();
|
filter = context.createBiquadFilter();
|
||||||
filter.type = type;
|
filter.type = type;
|
||||||
filter.Q.value = Q;
|
filter.Q.value = q;
|
||||||
filter.frequency.value = frequency;
|
filter.frequency.value = frequency;
|
||||||
frequencyParam = filter.frequency;
|
frequencyParam = filter.frequency;
|
||||||
}
|
}
|
||||||
|
const envelopeValues = [params.attack, params.decay, params.sustain, params.release];
|
||||||
|
const [attack, decay, sustain, release] = getADSRValues(envelopeValues, 'exponential', [0.005, 0.14, 0, 0.1]);
|
||||||
// envelope is active when any of these values is set
|
// envelope is active when any of these values is set
|
||||||
const hasEnvelope = att ?? dec ?? sus ?? rel ?? fenv;
|
const hasEnvelope = [...envelopeValues, env].some((v) => v !== undefined);
|
||||||
// Apply ADSR to filter frequency
|
// Apply ADSR to filter frequency
|
||||||
if (hasEnvelope !== undefined) {
|
if (hasEnvelope) {
|
||||||
fenv = nanFallback(fenv, 1, true);
|
env = nanFallback(env, 1, true);
|
||||||
fanchor = nanFallback(fanchor, 0, true);
|
anchor = nanFallback(anchor, 0, true);
|
||||||
const fenvAbs = Math.abs(fenv);
|
const envAbs = Math.abs(env);
|
||||||
const offset = fenvAbs * fanchor;
|
const offset = envAbs * anchor;
|
||||||
let min = clamp(2 ** -offset * frequency, 0, 20000);
|
let min = clamp(2 ** -offset * frequency, 0, 20000);
|
||||||
let max = clamp(2 ** (fenvAbs - offset) * frequency, 0, 20000);
|
let max = clamp(2 ** (envAbs - offset) * frequency, 0, 20000);
|
||||||
if (fenv < 0) [min, max] = [max, min];
|
if (env < 0) [min, max] = [max, min];
|
||||||
getParamADSR(frequencyParam, attack, decay, sustain, release, min, max, start, end, curve);
|
getParamADSR(frequencyParam, attack, decay, sustain, release, min, max, start, end, 'exponential');
|
||||||
return filter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sync != null) {
|
||||||
|
rate = cps * sync;
|
||||||
|
}
|
||||||
|
const lfoValues = { depth, dcoffset, skew, shape, frequency: rate };
|
||||||
|
getParamLfo(context, frequencyParam, start, end, lfoValues);
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
||||||
import './feedbackdelay.mjs';
|
import './feedbackdelay.mjs';
|
||||||
import './reverb.mjs';
|
import './reverb.mjs';
|
||||||
import './vowel.mjs';
|
import './vowel.mjs';
|
||||||
import { nanFallback, _mod, cycleToSeconds } from './util.mjs';
|
import { nanFallback, _mod, cycleToSeconds, pickAndRename } from './util.mjs';
|
||||||
import workletsUrl from './worklets.mjs?audioworklet';
|
import workletsUrl from './worklets.mjs?audioworklet';
|
||||||
import { createFilter, gainNode, getCompressor, getDistortion, getLfo, getWorklet, effectSend } from './helpers.mjs';
|
import { createFilter, gainNode, getCompressor, getDistortion, getLfo, getWorklet, effectSend } from './helpers.mjs';
|
||||||
import { map } from 'nanostores';
|
import { map } from 'nanostores';
|
||||||
|
|
@ -146,11 +146,6 @@ let defaultDefaultValues = {
|
||||||
gain: 0.8,
|
gain: 0.8,
|
||||||
postgain: 1,
|
postgain: 1,
|
||||||
density: '.03',
|
density: '.03',
|
||||||
ftype: '12db',
|
|
||||||
fanchor: 0,
|
|
||||||
resonance: 1,
|
|
||||||
hresonance: 1,
|
|
||||||
bandq: 1,
|
|
||||||
channels: [1, 2],
|
channels: [1, 2],
|
||||||
phaserdepth: 0.75,
|
phaserdepth: 0.75,
|
||||||
shapevol: 1,
|
shapevol: 1,
|
||||||
|
|
@ -415,32 +410,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
djf,
|
djf,
|
||||||
// filters
|
// filters
|
||||||
fanchor = getDefaultValue('fanchor'),
|
fanchor = getDefaultValue('fanchor'),
|
||||||
drive = 0.69,
|
|
||||||
release = 0,
|
release = 0,
|
||||||
// low pass
|
|
||||||
cutoff,
|
|
||||||
lpenv,
|
|
||||||
lpattack,
|
|
||||||
lpdecay,
|
|
||||||
lpsustain,
|
|
||||||
lprelease,
|
|
||||||
resonance = getDefaultValue('resonance'),
|
|
||||||
// high pass
|
|
||||||
hpenv,
|
|
||||||
hcutoff,
|
|
||||||
hpattack,
|
|
||||||
hpdecay,
|
|
||||||
hpsustain,
|
|
||||||
hprelease,
|
|
||||||
hresonance = getDefaultValue('hresonance'),
|
|
||||||
// band pass
|
|
||||||
bpenv,
|
|
||||||
bandf,
|
|
||||||
bpattack,
|
|
||||||
bpdecay,
|
|
||||||
bpsustain,
|
|
||||||
bprelease,
|
|
||||||
bandq = getDefaultValue('bandq'),
|
|
||||||
|
|
||||||
//phaser
|
//phaser
|
||||||
phaserrate: phaser,
|
phaserrate: phaser,
|
||||||
|
|
@ -565,57 +535,87 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||||
// gain stage
|
// gain stage
|
||||||
chain.push(gainNode(gain));
|
chain.push(gainNode(gain));
|
||||||
|
|
||||||
//filter
|
// filter
|
||||||
const ftype = getFilterType(value.ftype);
|
const ftype = getFilterType(value.ftype);
|
||||||
if (cutoff !== undefined) {
|
|
||||||
let lp = () =>
|
if (value.cutoff !== undefined) {
|
||||||
createFilter(
|
const lpMap = {
|
||||||
ac,
|
frequency: 'cutoff',
|
||||||
'lowpass',
|
q: 'resonance',
|
||||||
cutoff,
|
attack: 'lpattack',
|
||||||
resonance,
|
decay: 'lpdecay',
|
||||||
lpattack,
|
sustain: 'lpsustain',
|
||||||
lpdecay,
|
release: 'lprelease',
|
||||||
lpsustain,
|
env: 'lpenv',
|
||||||
lprelease,
|
anchor: 'fanchor',
|
||||||
lpenv,
|
model: 'ftype',
|
||||||
t,
|
drive: 'drive',
|
||||||
end,
|
rate: 'lprate',
|
||||||
fanchor,
|
sync: 'lpsync',
|
||||||
ftype,
|
depth: 'lpdepth',
|
||||||
drive,
|
shape: 'lpshape',
|
||||||
);
|
dcoffset: 'lpdc',
|
||||||
|
skew: 'lpskew',
|
||||||
|
};
|
||||||
|
const lpParams = pickAndRename(value, lpMap);
|
||||||
|
lpParams.type = 'lowpass';
|
||||||
|
let lp = () => createFilter(ac, t, end, lpParams, cps);
|
||||||
chain.push(lp());
|
chain.push(lp());
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(lp());
|
chain.push(lp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hcutoff !== undefined) {
|
if (value.hcutoff !== undefined) {
|
||||||
let hp = () =>
|
const hpMap = {
|
||||||
createFilter(
|
frequency: 'hcutoff',
|
||||||
ac,
|
q: 'hresonance',
|
||||||
'highpass',
|
attack: 'hpattack',
|
||||||
hcutoff,
|
decay: 'hpdecay',
|
||||||
hresonance,
|
sustain: 'hpsustain',
|
||||||
hpattack,
|
release: 'hprelease',
|
||||||
hpdecay,
|
env: 'hpenv',
|
||||||
hpsustain,
|
anchor: 'fanchor',
|
||||||
hprelease,
|
model: 'ftype',
|
||||||
hpenv,
|
drive: 'drive',
|
||||||
t,
|
rate: 'hprate',
|
||||||
end,
|
sync: 'hpsync',
|
||||||
fanchor,
|
depth: 'hpdepth',
|
||||||
);
|
shape: 'hpshape',
|
||||||
|
dcoffset: 'hpdc',
|
||||||
|
skew: 'hpskew',
|
||||||
|
};
|
||||||
|
const hpParams = pickAndRename(value, hpMap);
|
||||||
|
hpParams.type = 'highpass';
|
||||||
|
let hp = () => createFilter(ac, t, end, hpParams, cps);
|
||||||
chain.push(hp());
|
chain.push(hp());
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(hp());
|
chain.push(hp());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bandf !== undefined) {
|
if (value.bandf !== undefined) {
|
||||||
let bp = () =>
|
const bpMap = {
|
||||||
createFilter(ac, 'bandpass', bandf, bandq, bpattack, bpdecay, bpsustain, bprelease, bpenv, t, end, fanchor);
|
frequency: 'bandf',
|
||||||
|
q: 'bandq',
|
||||||
|
attack: 'bpattack',
|
||||||
|
decay: 'bpdecay',
|
||||||
|
sustain: 'bpsustain',
|
||||||
|
release: 'bprelease',
|
||||||
|
env: 'bpenv',
|
||||||
|
anchor: 'fanchor',
|
||||||
|
model: 'ftype',
|
||||||
|
drive: 'drive',
|
||||||
|
rate: 'bprate',
|
||||||
|
sync: 'bpsync',
|
||||||
|
depth: 'bpdepth',
|
||||||
|
shape: 'bpshape',
|
||||||
|
dcoffset: 'bpdc',
|
||||||
|
skew: 'bpskew',
|
||||||
|
};
|
||||||
|
const bpParams = pickAndRename(value, bpMap);
|
||||||
|
bpParams.type = 'bandpass';
|
||||||
|
let bp = () => createFilter(ac, t, end, bpParams, cps);
|
||||||
chain.push(bp());
|
chain.push(bp());
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(bp());
|
chain.push(bp());
|
||||||
|
|
|
||||||
|
|
@ -109,3 +109,8 @@ export function getCommonSampleInfo(hapValue, bank) {
|
||||||
const label = `${s}:${index}`;
|
const label = `${s}:${index}`;
|
||||||
return { transpose, url, index, midi, label };
|
return { transpose, url, index, midi, label };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Selects entries from `source` and renames them via `map` */
|
||||||
|
export const pickAndRename = (source, map) => {
|
||||||
|
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue