mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 06:19:33 -04:00
First pass at modes
This commit is contained in:
@@ -1575,7 +1575,7 @@ export const { roomsize, size, sz, rsize } = registerControl('roomsize', 'size',
|
||||
export const { shape } = registerControl(['shape', 'shapevol']);
|
||||
/**
|
||||
* Wave shaping distortion. CAUTION: it can get loud.
|
||||
* Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output.
|
||||
* Second option in optional array syntax (ex: ".9:.5") applies a postgain to the output. Third option sets the waveshaping type.
|
||||
* Most useful values are usually between 0 and 10 (depending on source gain). If you are feeling adventurous, you can turn it up to 11 and beyond ;)
|
||||
*
|
||||
* @name distort
|
||||
@@ -1585,9 +1585,40 @@ export const { shape } = registerControl(['shape', 'shapevol']);
|
||||
* s("bd sd [~ bd] sd,hh*8").distort("<0 2 3 10:.5>")
|
||||
* @example
|
||||
* note("d1!8").s("sine").penv(36).pdecay(.12).decay(.23).distort("8:.4")
|
||||
* @example
|
||||
* s("bd*4").bank("tr909").distort("4:0.5:fold")
|
||||
*
|
||||
*/
|
||||
export const { distort, dist } = registerControl(['distort', 'distortvol'], 'dist');
|
||||
export const { distort, dist } = registerControl(['distort', 'distortvol', 'distorttype'], 'dist');
|
||||
|
||||
/**
|
||||
* Postgain for waveshaping distortion.
|
||||
*
|
||||
* @name distortvol
|
||||
* @synonyms distvol
|
||||
* @param {number | Pattern} type
|
||||
* @example
|
||||
* s("bd*4").bank("tr909").distort(2).distortvol(0.8)
|
||||
*/
|
||||
export const { distortvol } = registerControl('distortvol', 'distvol');
|
||||
|
||||
/**
|
||||
* Type of waveshaping distortion to apply.
|
||||
*
|
||||
* @name distorttype
|
||||
* @synonyms disttype
|
||||
* @param {number | string | Pattern} type
|
||||
* @example
|
||||
* s("bd*4").bank("tr909").distort(2).distorttype("<0 1 2>")
|
||||
*
|
||||
* @example
|
||||
* s("sine").note("F1*2").release(1)
|
||||
* .penv(24).pdecay(0.05)
|
||||
* .distort(70)
|
||||
* .distorttype("<fold decimate scurve hard soft diode asym>")
|
||||
*/
|
||||
export const { distorttype } = registerControl('distorttype', 'disttype');
|
||||
|
||||
/**
|
||||
* Dynamics Compressor. The params are `compressor("threshold:ratio:knee:attack:release")`
|
||||
* More info [here](https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode?retiredLocale=de#instance_properties)
|
||||
|
||||
@@ -307,3 +307,7 @@ export function applyFM(param, value, begin) {
|
||||
}
|
||||
return { stop };
|
||||
}
|
||||
|
||||
export const getDistortion = (distort, postgain, algorithm) => {
|
||||
return getWorklet(getAudioContext(), 'distort-processor', { distort, postgain, algorithm });
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ import './reverb.mjs';
|
||||
import './vowel.mjs';
|
||||
import { clamp, nanFallback, _mod, cycleToSeconds, secondsToCycle } from './util.mjs';
|
||||
import workletsUrl from './worklets.mjs?audioworklet';
|
||||
import { createFilter, gainNode, getCompressor, getWorklet, webAudioTimeout } from './helpers.mjs';
|
||||
import { createFilter, gainNode, getCompressor, getDistortion, getWorklet, webAudioTimeout } from './helpers.mjs';
|
||||
import { map } from 'nanostores';
|
||||
import { logger, errorLogger } from './logger.mjs';
|
||||
import { loadBuffer } from './sampler.mjs';
|
||||
@@ -608,6 +608,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||
shapevol = getDefaultValue('shapevol'),
|
||||
distort,
|
||||
distortvol = getDefaultValue('distortvol'),
|
||||
distorttype,
|
||||
pan,
|
||||
vowel,
|
||||
delay = getDefaultValue('delay'),
|
||||
@@ -782,8 +783,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
||||
// effects
|
||||
coarse !== undefined && chain.push(getWorklet(ac, 'coarse-processor', { coarse }));
|
||||
crush !== undefined && chain.push(getWorklet(ac, 'crush-processor', { crush }));
|
||||
shape !== undefined && chain.push(getWorklet(ac, 'shape-processor', { shape, postgain: shapevol }));
|
||||
distort !== undefined && chain.push(getWorklet(ac, 'distort-processor', { distort, postgain: distortvol }));
|
||||
distort !== undefined && chain.push(getDistortion(distort, distortvol, distorttype));
|
||||
|
||||
if (tremolosync != null) {
|
||||
tremolo = cps * tremolosync;
|
||||
|
||||
@@ -7,6 +7,9 @@ import FFT from './fft.js';
|
||||
|
||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||
const _mod = (n, m) => ((n % m) + m) % m;
|
||||
const ffloor = (x) => x | 0;
|
||||
const pv = (arr, n) => arr[n] ?? arr[0];
|
||||
const mix = (a, b, t) => (1 - t) * a + t * b;
|
||||
|
||||
// Restrict phase to the range [0, maxPhase) via wrapping
|
||||
function wrapPhase(phase, maxPhase = 1) {
|
||||
@@ -341,11 +344,85 @@ class LadderProcessor extends AudioWorkletProcessor {
|
||||
}
|
||||
registerProcessor('ladder-processor', LadderProcessor);
|
||||
|
||||
// Saturation curves
|
||||
|
||||
const __squash = (x) => x / (1 + x); // [0, inf) to [0, 1)
|
||||
|
||||
const _scurve = (x, k) => ((1 + k) * x) / (1 + k * Math.abs(x));
|
||||
const _soft = (x, k) => Math.tanh(x * (1 + k));
|
||||
const _hard = (x, k) => clamp((1 + k) * x, -1, 1);
|
||||
const _sine = (x, k) => Math.sin(x * (1 + k));
|
||||
|
||||
const _fold = (x, k) => {
|
||||
let y = (1 + k) * x;
|
||||
while (y > 1 || y < -1) {
|
||||
y = y > 1 ? 2 - y : -2 - y;
|
||||
}
|
||||
return y;
|
||||
};
|
||||
|
||||
const _sineFold = (x, k) => Math.sin((Math.PI / 2) * _fold(x, k));
|
||||
|
||||
const _pow = (x, k) => {
|
||||
const t = __squash(k);
|
||||
const p = 1 / (1 + 0.5 * t); // tame k
|
||||
return _soft(Math.sign(x) * Math.pow(Math.abs(x), p), 0.5 * k);
|
||||
};
|
||||
|
||||
const _diode = (x, k, asym = false) => {
|
||||
const g = 1 + k; // gain
|
||||
const t = __squash(k);
|
||||
const bias = 0.15 * t;
|
||||
const pos = _soft(x + bias, k);
|
||||
const neg = _soft(asym ? bias : -x + bias, k);
|
||||
const y = pos - neg;
|
||||
// We divide by the derivative at 0 so that the distortion is roughly
|
||||
// the identity map near 0 => small values are preserved and undistorted
|
||||
const sech = 1 / Math.cosh(g * bias);
|
||||
const sech2 = sech * sech; // derivative of tanh is sech^2
|
||||
const denom = Math.max(1e-8, (asym ? 1 : 2) * g * sech2); // g from chain rule; 2 if both pos/neg have x
|
||||
return _soft(y / denom, k);
|
||||
};
|
||||
|
||||
const _asym = (x, k) => _diode(x, k, true);
|
||||
|
||||
const _cubic = (x, k) => {
|
||||
const t = __squash(k);
|
||||
const cubic = (x - (t / 3) * x * x * x) / (1 - t / 3); // normalized to go from (-1, 1)
|
||||
return _soft(cubic, k);
|
||||
};
|
||||
|
||||
export const saturationAlgos = {
|
||||
scurve: _scurve,
|
||||
soft: _soft,
|
||||
hard: _hard,
|
||||
sine: _sine,
|
||||
fold: _fold,
|
||||
sinefold: _sineFold,
|
||||
pow: _pow,
|
||||
cubic: _cubic,
|
||||
diode: _diode,
|
||||
asym: _asym,
|
||||
};
|
||||
const _algoNames = Object.freeze(Object.keys(saturationAlgos));
|
||||
|
||||
const _getAlgorithm = (algo) => {
|
||||
let algoName = typeof algo === 'string' ? algo : _algoNames[algo % _algoNames.length];
|
||||
if (!_algoNames.includes(algoName)) {
|
||||
algoName = _algoNames[0];
|
||||
logger(`[superdough] Could not find waveshaping algorithm ${algo}.
|
||||
Available options are ${_algoNames.join(', ')}.
|
||||
Defaulting to ${algoName}.`);
|
||||
}
|
||||
return saturationAlgos[algoName];
|
||||
};
|
||||
|
||||
class DistortProcessor extends AudioWorkletProcessor {
|
||||
static get parameterDescriptors() {
|
||||
return [
|
||||
{ name: 'distort', defaultValue: 0 },
|
||||
{ name: 'postgain', defaultValue: 1 },
|
||||
{ name: 'algorithm', defaultValue: 0, min: 0, max: _algoNames.length - 1 },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -363,13 +440,13 @@ class DistortProcessor extends AudioWorkletProcessor {
|
||||
return false;
|
||||
}
|
||||
this.started = hasInput;
|
||||
|
||||
const shape = Math.expm1(parameters.distort[0]);
|
||||
const postgain = Math.max(0.001, Math.min(1, parameters.postgain[0]));
|
||||
|
||||
for (let n = 0; n < blockSize; n++) {
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
output[i][n] = (((1 + shape) * input[i][n]) / (1 + shape * Math.abs(input[i][n]))) * postgain;
|
||||
const postgain = clamp(pv(parameters.postgain, n), 0.001, 1);
|
||||
const shape = Math.expm1(pv(parameters.distort, n));
|
||||
const algorithm = _getAlgorithm(pv(parameters.algorithm, n));
|
||||
for (let ch = 0; ch < input.length; ch++) {
|
||||
const x = input[ch][n];
|
||||
output[ch][n] = postgain * algorithm(x, shape);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -312,7 +312,7 @@ export function SettingsTab({ started }) {
|
||||
confirmDialog('Sure?').then((r) => {
|
||||
if (r) {
|
||||
const { userPatterns } = settingsMap.get(); // keep current patterns
|
||||
settingsMap.set({...defaultSettings, userPatterns});
|
||||
settingsMap.set({ ...defaultSettings, userPatterns });
|
||||
}
|
||||
});
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user