Compare commits

..

23 Commits

Author SHA1 Message Date
alex ba93bd1bc5 Fix onPaint for widgets 2025-10-15 18:54:02 +01:00
Switch Angel AKA Jade Rose ded91fab7d Merge pull request 'Feature: Distortion Modes' (#1561) from glossing/strudel:glossing/distortion-modes into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1561
2025-10-13 07:26:50 +02:00
Switch Angel AKA Jade Rose f40d079543 Merge pull request 'Feature: Wavetable FM' (#1623) from glossing/strudel:glossing/wavetable-fm into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1623
2025-10-13 07:05:56 +02:00
Switch Angel AKA Jade Rose d5833dd475 Merge branch 'main' into glossing/wavetable-fm 2025-10-13 07:05:24 +02:00
Switch Angel AKA Jade Rose e3658ec30e Merge pull request 'fix: repair REPL sample sources and URL concat (#1640)' (#1646) from erikfox/strudel:fix-repl-sample-sources-1640 into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1646
2025-10-12 20:43:41 +02:00
Erik Fox 72eaf96b4f fix: repair REPL sample sources and URL concat (#1640) 2025-10-12 12:30:36 -04:00
Aria d496919fe5 Import 2025-10-02 22:48:41 -05:00
Aria f369de13d7 Hook up FM to wavetables 2025-10-02 22:45:48 -05:00
Aria 72298c5f03 Docstring cleanup 2025-10-01 00:45:26 -05:00
Aria c885d84785 Add comment 2025-10-01 00:39:44 -05:00
Aria a14bf4d97b Add aliases for distort types 2025-10-01 00:36:59 -05:00
Aria c5f1085bd9 WIP 2025-09-30 23:51:59 -05:00
Aria ccbf28f2db Merge branch 'main' into glossing/distortion-modes 2025-09-30 23:25:57 -05:00
Aria 07e3ddbe40 Merge branch 'main' into glossing/distortion-modes 2025-09-27 10:25:51 -07:00
Aria b3537a9acb Some cleanup 2025-09-24 22:17:30 -07:00
Aria f0669ce3ba Move algorithm into constructor 2025-09-24 17:32:03 -07:00
Aria 69b034349a Codeformat 2025-09-13 14:30:05 -05:00
Aria 5f7e10733a Merge branch 'main' into glossing/distortion-modes 2025-09-13 14:23:17 -05:00
Aria 9848740e18 Formatting and tests 2025-09-10 21:39:02 -05:00
Aria 51b150b829 Optimize folding for audio rate 2025-09-10 21:27:56 -05:00
Aria 9b1c23d49f Some tuning experimentally and adding back chebyshev 2025-09-04 20:05:28 -05:00
Aria 42ab65abdb Move things around to allow selection by name and avoid circular imports; fixed chebyshev 2025-09-04 11:47:01 -05:00
Aria bb982e6b9b First pass at modes 2025-09-03 21:55:48 -05:00
17 changed files with 359 additions and 60 deletions
+36 -3
View File
@@ -1887,19 +1887,52 @@ 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
* @synonyms dist
* @param {number | Pattern} distortion
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
* @param {number | string | Pattern} type type of distortion to apply
* @example
* 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*4").bank("tr808").distort("3:0.5:diode")
*
*/
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} volume linear postgain of the distortion
* @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 type of distortion to apply
* @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(rand.range(1, 8))
* .distorttype("<fold chebyshev scurve diode asym sinefold>")
*/
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)
+89 -17
View File
@@ -963,13 +963,26 @@ export const arp = register(
false,
);
/*
* Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are
* distributed equally over the given time duration. They are then combined with the pattern 'weave' is called on, after it has been stretched out (i.e. slowed down by) the time duration.
* @name weave
* @memberof Pattern
* @example pan(saw).weave(4, s("bd(3,8)"), s("~ sd"))
* @example n("0 1 2 3 4 5 6 7").weave(8, s("bd(3,8)"), s("~ sd"))
addToPrototype('weave', function (t, ...pats) {
return this.weaveWith(t, ...pats.map((x) => set.out(x)));
});
*/
/*
* Like 'weave', but accepts functions rather than patterns, which are applied to the pattern.
* @name weaveWith
* @memberof Pattern
*/
export const weaveWith = register('weaveWith', function (t, funcs, pat) {
addToPrototype('weaveWith', function (t, ...funcs) {
const pat = this;
const l = funcs.length;
t = Fraction(t);
if (l == 0) {
@@ -977,21 +990,7 @@ export const weaveWith = register('weaveWith', function (t, funcs, pat) {
}
return stack(...funcs.map((func, i) => pat.inside(t, func).early(Fraction(i).div(l))))._slow(t);
});
/*
* Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are
* distributed equally over the given time duration. They are then combined with the pattern 'weave' is called on, after it has been stretched out (i.e. slowed down by) the time duration.
* @name weave
* @memberof Pattern
* @example pan(saw).weave(4, [s("bd(3,8)"), s("~ sd")])
* @example n("0 1 2 3 4 5 6 7").weave(8, [s("bd(3,8)"), s("~ sd")])
*/
export const weave = register('weave', function (t, pats, pat) {
return pat.weaveWith(
t,
pats.map((x) => (y) => y.set.out(x)),
);
});
*/
//////////////////////////////////////////////////////////////////////
// compose matrix functions
@@ -3534,3 +3533,76 @@ export const morph = (frompat, topat, bypat) => {
bypat = reify(bypat);
return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by))));
};
/**
* Soft-clipping distortion
*
* @name soft
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Hard-clipping distortion
*
* @name hard
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Cubic polynomial distortion
*
* @name cubic
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Diode-emulating distortion
*
* @name diode
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Asymmetrical diode distortion
*
* @name asym
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Wavefolding distortion
*
* @name fold
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Wavefolding distortion composed with sinusoid
*
* @name sinefold
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
/**
* Distortion via Chebyshev polynomials
*
* @name chebyshev
* @param {number | Pattern} distortion amount of distortion to apply
* @param {number | Pattern} volume linear postgain of the distortion
*
*/
const distAlgoNames = ['scurve', 'soft', 'hard', 'cubic', 'diode', 'asym', 'fold', 'sinefold', 'chebyshev'];
for (const name of distAlgoNames) {
// Add aliases for distortion algorithms
Pattern.prototype[name] = function (args) {
const argsPat = reify(args).fmap((v) => (Array.isArray(v) ? [...v, name] : [v, 1, name]));
return this.distort(argsPat);
};
}
+9
View File
@@ -84,9 +84,18 @@ Pattern.prototype.onPaint = function (painter) {
state.controls.painters = [];
}
state.controls.painters.push(painter);
return state;
});
};
// TODO - Why isn't this pure deep copy not working?
// Pattern.prototype.onPaint = function (painter) {
// return this.withState((state) => {
// const painters = state.controls.painters ? [...state.controls.painters, painter] : [painter];
// return new State(state.span, { ...state.controls, painters });
// });
// };
Pattern.prototype.getPainters = function () {
let painters = [];
this.queryArc(0, 0, { painters });
+6 -3
View File
@@ -20,10 +20,13 @@ export async function prebake() {
// import('@strudel/osc'),
);
// load samples
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main/';
const ds = 'https://raw.githubusercontent.com/felixroos/dough-samples/main';
// TODO: move this onto the strudel repo
const ts = 'https://raw.githubusercontent.com/todepond/samples/main/';
const ts = 'https://raw.githubusercontent.com/todepond/samples/main';
const tc = 'https://raw.githubusercontent.com/tidalcycles/uzu-drumkit/main';
await Promise.all([
modulesLoading,
registerSynthSounds(),
@@ -36,9 +39,9 @@ export async function prebake() {
samples(`${ds}/tidal-drum-machines.json`),
samples(`${ds}/piano.json`),
samples(`${ds}/Dirt-Samples.json`),
samples(`${ds}/uzu-drumkit.json`),
samples(`${ds}/vcsl.json`),
samples(`${ds}/mridangam.json`),
samples(`${tc}/strudel.json`),
]);
aliasBank(`${ts}/tidal-drum-machines-alias.json`);
+18
View File
@@ -0,0 +1,18 @@
let audioContext;
export const setDefaultAudioContext = () => {
audioContext = new AudioContext();
return audioContext;
};
export const getAudioContext = () => {
if (!audioContext) {
return setDefaultAudioContext();
}
return audioContext;
};
export function getAudioContextCurrentTime() {
return getAudioContext().currentTime;
}
+1 -1
View File
@@ -1,4 +1,4 @@
import { getAudioContext } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
let worklet;
export async function dspWorklet(ac, code) {
+98 -1
View File
@@ -1,6 +1,7 @@
import { getAudioContext } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
import { clamp, nanFallback, midiToFreq, noteToMidi } from './util.mjs';
import { getNoiseBuffer } from './noise.mjs';
import { logger } from './logger.mjs';
export const noises = ['pink', 'white', 'brown', 'crackle'];
@@ -381,6 +382,102 @@ export function applyFM(param, value, begin) {
return { stop };
}
// Saturation curves
const __squash = (x) => x / (1 + x); // [0, inf) to [0, 1)
const _mod = (n, m) => ((n % m) + m) % m;
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 _fold = (x, k) => {
// Closed form folding for audio rate
let y = (1 + 0.5 * k) * x;
const window = _mod(y + 1, 4);
return 1 - Math.abs(window - 2);
};
const _sineFold = (x, k) => Math.sin((Math.PI / 2) * _fold(x, k));
const _cubic = (x, k) => {
const t = __squash(Math.log1p(k));
const cubic = (x - (t / 3) * x * x * x) / (1 - t / 3); // normalized to go from (-1, 1)
return _soft(cubic, k);
};
const _diode = (x, k, asym = false) => {
const g = 1 + 2 * k; // gain
const t = __squash(Math.log1p(k));
const bias = 0.07 * t;
const pos = _soft(x + bias, 2 * k);
const neg = _soft(asym ? bias : -x + bias, 2 * 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 soft (i.e. 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 _chebyshev = (x, k) => {
const kl = 10 * Math.log1p(k);
let tnm1 = 1;
let tnm2 = x;
let tn;
let y = 0;
for (let i = 1; i < 64; i++) {
if (i < 2) {
// Already set inital conditions
y += i == 0 ? tnm1 : tnm2;
continue;
}
tn = 2 * x * tnm1 - tnm2; // https://en.wikipedia.org/wiki/Chebyshev_polynomials#Recurrence_definition
tnm2 = tnm1;
tnm1 = tn;
if (i % 2 === 0) {
y += Math.min((1.3 * kl) / i, 2) * tn;
}
}
// Soft clip
return _soft(y, kl / 20);
};
export const distortionAlgorithms = {
scurve: _scurve,
soft: _soft,
hard: _hard,
cubic: _cubic,
diode: _diode,
asym: _asym,
fold: _fold,
sinefold: _sineFold,
chebyshev: _chebyshev,
};
const _algoNames = Object.freeze(Object.keys(distortionAlgorithms));
export const getDistortionAlgorithm = (algo) => {
let index = algo;
if (typeof algo === 'string') {
index = _algoNames.indexOf(algo);
if (index === -1) {
logger(`[superdough] Could not find waveshaping algorithm ${algo}.
Available options are ${_algoNames.join(', ')}.
Defaulting to ${_algoNames[0]}.`);
index = 0;
}
}
const name = _algoNames[index % _algoNames.length]; // allow for wrapping if algo was a number
return distortionAlgorithms[name];
};
export const getDistortion = (distort, postgain, algorithm) => {
return getWorklet(getAudioContext(), 'distort-processor', { distort, postgain }, { processorOptions: { algorithm } });
};
export const getFrequencyFromValue = (value, defaultNote = 36) => {
let { note, freq } = value;
note = note || defaultNote;
+1
View File
@@ -11,4 +11,5 @@ export * from './synth.mjs';
export * from './zzfx.mjs';
export * from './logger.mjs';
export * from './dspworklet.mjs';
export * from './audioContext.mjs';
export * from './wavetable.mjs';
+1 -1
View File
@@ -1,5 +1,5 @@
import { drywet } from './helpers.mjs';
import { getAudioContext } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
let noiseCache = {};
+3 -2
View File
@@ -1,5 +1,6 @@
import { noteToMidi, valueToMidi, getSoundIndex, getCommonSampleInfo } from './util.mjs';
import { getAudioContext, registerSound, registerWaveTable } from './index.mjs';
import { getCommonSampleInfo } from './util.mjs';
import { registerSound, registerWaveTable } from './index.mjs';
import { getAudioContext } from './audioContext.mjs';
import { getADSRValues, getParamADSR, getPitchEnvelope, getVibratoOscillator } from './helpers.mjs';
import { logger } from './logger.mjs';
+5 -22
View File
@@ -9,10 +9,11 @@ import './reverb.mjs';
import './vowel.mjs';
import { nanFallback, _mod, cycleToSeconds } from './util.mjs';
import workletsUrl from './worklets.mjs?audioworklet';
import { createFilter, gainNode, getCompressor, getLfo, getWorklet, effectSend } from './helpers.mjs';
import { createFilter, gainNode, getCompressor, getDistortion, getLfo, getWorklet, effectSend } from './helpers.mjs';
import { map } from 'nanostores';
import { logger } from './logger.mjs';
import { loadBuffer } from './sampler.mjs';
import { getAudioContext } from './audioContext.mjs';
import { SuperdoughAudioController } from './superdoughoutput.mjs';
export const DEFAULT_MAX_POLYPHONY = 128;
@@ -154,6 +155,7 @@ let defaultDefaultValues = {
phaserdepth: 0.75,
shapevol: 1,
distortvol: 1,
distorttype: 0,
delay: 0,
byteBeatExpression: '0',
delayfeedback: 0.5,
@@ -200,25 +202,6 @@ export function setVersionDefaults(version) {
export const resetLoadedSounds = () => soundMap.set({});
let audioContext;
export const setDefaultAudioContext = () => {
audioContext = new AudioContext({ latencyHint: 'playback' });
return audioContext;
};
export const getAudioContext = () => {
if (!audioContext) {
return setDefaultAudioContext();
}
return audioContext;
};
export function getAudioContextCurrentTime() {
return getAudioContext().currentTime;
}
let externalWorklets = [];
export function registerWorklet(url) {
externalWorklets.push(url);
@@ -473,6 +456,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
shapevol = getDefaultValue('shapevol'),
distort,
distortvol = getDefaultValue('distortvol'),
distorttype = getDefaultValue('distorttype'),
pan,
vowel,
delay = getDefaultValue('delay'),
@@ -646,8 +630,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;
+2 -1
View File
@@ -1,5 +1,6 @@
import { clamp } from './util.mjs';
import { registerSound, getAudioContext, soundMap } from './superdough.mjs';
import { registerSound, soundMap } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
import {
applyFM,
destroyAudioWorkletNode,
+3
View File
@@ -1,6 +1,7 @@
import { getAudioContext, registerSound } from './index.mjs';
import { getCommonSampleInfo } from './util.mjs';
import {
applyFM,
applyParameterModulators,
destroyAudioWorkletNode,
getADSRValues,
@@ -308,6 +309,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
},
);
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
const fm = applyFM(source.parameters.get('frequency'), value, t);
const envGain = ac.createGain();
const node = source.connect(envGain);
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
@@ -318,6 +320,7 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
() => {
destroyAudioWorkletNode(source);
vibratoOscillator?.stop();
fm?.stop();
node.disconnect();
wtPosModulators?.disconnect();
wtWarpModulators?.disconnect();
+8 -7
View File
@@ -4,6 +4,7 @@
import OLAProcessor from './ola-processor';
import FFT from './fft.js';
import { getDistortionAlgorithm } from './helpers.mjs';
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
const mod = (n, m) => ((n % m) + m) % m;
@@ -430,9 +431,10 @@ class DistortProcessor extends AudioWorkletProcessor {
];
}
constructor() {
constructor({ processorOptions }) {
super();
this.started = false;
this.algorithm = getDistortionAlgorithm(processorOptions.algorithm);
}
process(inputs, outputs, parameters) {
@@ -444,13 +446,12 @@ 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));
for (let ch = 0; ch < input.length; ch++) {
const x = input[ch][n];
output[ch][n] = postgain * this.algorithm(x, shape);
}
}
return true;
+2 -1
View File
@@ -1,6 +1,7 @@
//import { ZZFX } from 'zzfx';
import { midiToFreq, noteToMidi } from './util.mjs';
import { registerSound, getAudioContext } from './superdough.mjs';
import { registerSound } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
import { buildSamples } from './zzfx_fork.mjs';
export const getZZFX = (value, t) => {
+1 -1
View File
@@ -1,4 +1,4 @@
import { getAudioContext } from './superdough.mjs';
import { getAudioContext } from './audioContext.mjs';
// https://github.com/KilledByAPixel/ZzFX/blob/master/ZzFX.js#L85C5-L180C6
// changes: replaced this.volume with 1 + using sampleRate from getAudioContext()
+76
View File
@@ -2829,6 +2829,82 @@ exports[`runs examples > example "distort" example index 1 1`] = `
]
`;
exports[`runs examples > example "distort" example index 2 1`] = `
[
"[ 0/1 → 1/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 1/4 → 1/2 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 1/2 → 3/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 3/4 → 1/1 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 1/1 → 5/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 5/4 → 3/2 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 3/2 → 7/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 7/4 → 2/1 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 2/1 → 9/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 9/4 → 5/2 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 5/2 → 11/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 11/4 → 3/1 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 3/1 → 13/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 13/4 → 7/2 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 7/2 → 15/4 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
"[ 15/4 → 4/1 | s:bd n:4 bank:tr808 distort:3 distortvol:0.5 distorttype:diode ]",
]
`;
exports[`runs examples > example "distorttype" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 1/4 → 1/2 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 1/2 → 3/4 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 3/4 → 1/1 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 1/1 → 5/4 | s:bd bank:tr909 distort:2 distorttype:1 ]",
"[ 5/4 → 3/2 | s:bd bank:tr909 distort:2 distorttype:1 ]",
"[ 3/2 → 7/4 | s:bd bank:tr909 distort:2 distorttype:1 ]",
"[ 7/4 → 2/1 | s:bd bank:tr909 distort:2 distorttype:1 ]",
"[ 2/1 → 9/4 | s:bd bank:tr909 distort:2 distorttype:2 ]",
"[ 9/4 → 5/2 | s:bd bank:tr909 distort:2 distorttype:2 ]",
"[ 5/2 → 11/4 | s:bd bank:tr909 distort:2 distorttype:2 ]",
"[ 11/4 → 3/1 | s:bd bank:tr909 distort:2 distorttype:2 ]",
"[ 3/1 → 13/4 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 13/4 → 7/2 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 7/2 → 15/4 | s:bd bank:tr909 distort:2 distorttype:0 ]",
"[ 15/4 → 4/1 | s:bd bank:tr909 distort:2 distorttype:0 ]",
]
`;
exports[`runs examples > example "distorttype" example index 1 1`] = `
[
"[ (0/1 → 1/2) ⇝ 1/1 | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:1 distorttype:fold ]",
"[ 0/1 ⇜ (1/2 → 1/1) | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:1 distorttype:fold ]",
"[ (1/1 → 3/2) ⇝ 2/1 | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:4.6367951557040215 distorttype:chebyshev ]",
"[ 1/1 ⇜ (3/2 → 2/1) | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:4.6367951557040215 distorttype:chebyshev ]",
"[ (2/1 → 5/2) ⇝ 3/1 | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:7.716689839959145 distorttype:scurve ]",
"[ 2/1 ⇜ (5/2 → 3/1) | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:7.716689839959145 distorttype:scurve ]",
"[ (3/1 → 7/2) ⇝ 4/1 | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:2.5210237745195627 distorttype:diode ]",
"[ 3/1 ⇜ (7/2 → 4/1) | s:sine note:F1 release:1 penv:24 pdecay:0.05 distort:2.5210237745195627 distorttype:diode ]",
]
`;
exports[`runs examples > example "distortvol" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 1/4 → 1/2 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 1/2 → 3/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 3/4 → 1/1 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 1/1 → 5/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 5/4 → 3/2 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 3/2 → 7/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 7/4 → 2/1 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 2/1 → 9/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 9/4 → 5/2 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 5/2 → 11/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 11/4 → 3/1 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 3/1 → 13/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 13/4 → 7/2 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 7/2 → 15/4 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
"[ 15/4 → 4/1 | s:bd bank:tr909 distort:2 distortvol:0.8 ]",
]
`;
exports[`runs examples > example "djf" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:D3 s:supersaw djf:0.5 ]",