mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-15 02:06:57 -04:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bc208d8f5 | |||
| cfeff26d42 | |||
| 7ee6b933cc | |||
| e86850b6d8 | |||
| 31c694b596 | |||
| 132a124644 | |||
| 8325b4cfc4 | |||
| 3b2df0b725 | |||
| 22f1512697 | |||
| 9a05a679aa | |||
| 321e76166a | |||
| bde17594ce | |||
| 7bbb653963 | |||
| 30e672a027 | |||
| f7f3aa7668 | |||
| adc3a9ac6c | |||
| 2efd56e331 | |||
| d453c861d3 | |||
| f637b8b0dd | |||
| 907f03f3bf | |||
| 47c85f8540 |
@@ -162,7 +162,6 @@ export class StrudelMirror {
|
|||||||
this.onDraw = onDraw || this.draw;
|
this.onDraw = onDraw || this.draw;
|
||||||
this.id = id || s4();
|
this.id = id || s4();
|
||||||
this.solo = solo;
|
this.solo = solo;
|
||||||
this.hasPainters = false;
|
|
||||||
|
|
||||||
this.drawer = new Drawer((haps, time, _, painters) => {
|
this.drawer = new Drawer((haps, time, _, painters) => {
|
||||||
const currentFrame = haps.filter((hap) => hap.isActive(time));
|
const currentFrame = haps.filter((hap) => hap.isActive(time));
|
||||||
@@ -178,7 +177,7 @@ export class StrudelMirror {
|
|||||||
onToggle: (started) => {
|
onToggle: (started) => {
|
||||||
replOptions?.onToggle?.(started);
|
replOptions?.onToggle?.(started);
|
||||||
if (started) {
|
if (started) {
|
||||||
this.shouldAnimate() && this.drawer.start(this.repl.scheduler);
|
this.drawer.start(this.repl.scheduler);
|
||||||
if (this.solo) {
|
if (this.solo) {
|
||||||
// stop other repls when this one is started
|
// stop other repls when this one is started
|
||||||
document.dispatchEvent(
|
document.dispatchEvent(
|
||||||
@@ -208,10 +207,9 @@ export class StrudelMirror {
|
|||||||
updateWidgets(this.editor, widgets);
|
updateWidgets(this.editor, widgets);
|
||||||
updateMiniLocations(this.editor, this.miniLocations);
|
updateMiniLocations(this.editor, this.miniLocations);
|
||||||
replOptions?.afterEval?.(options);
|
replOptions?.afterEval?.(options);
|
||||||
this.hasPainters = options.pattern.getPainters().length > 0;
|
|
||||||
// if no painters are set (.onPaint was not called), then we only need
|
// if no painters are set (.onPaint was not called), then we only need
|
||||||
// the present moment (for highlighting)
|
// the present moment (for highlighting)
|
||||||
const drawTime = this.hasPainters ? this.drawTime : [0, 0];
|
const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0];
|
||||||
this.drawer.setDrawTime(drawTime);
|
this.drawer.setDrawTime(drawTime);
|
||||||
// invalidate drawer after we've set the appropriate drawTime
|
// invalidate drawer after we've set the appropriate drawTime
|
||||||
this.drawer.invalidate(this.repl.scheduler);
|
this.drawer.invalidate(this.repl.scheduler);
|
||||||
@@ -239,7 +237,6 @@ export class StrudelMirror {
|
|||||||
cmEditor.style.backgroundColor = 'transparent';
|
cmEditor.style.backgroundColor = 'transparent';
|
||||||
}
|
}
|
||||||
const settings = codemirrorSettings.get();
|
const settings = codemirrorSettings.get();
|
||||||
this.isPatternHighlightingEnabled = parseBooleans(settings.isPatternHighlightingEnabled);
|
|
||||||
this.setFontSize(settings.fontSize);
|
this.setFontSize(settings.fontSize);
|
||||||
this.setFontFamily(settings.fontFamily);
|
this.setFontFamily(settings.fontFamily);
|
||||||
|
|
||||||
@@ -324,17 +321,8 @@ export class StrudelMirror {
|
|||||||
flash(this.editor, ms);
|
flash(this.editor, ms);
|
||||||
}
|
}
|
||||||
highlight(haps, time) {
|
highlight(haps, time) {
|
||||||
if (!this.isPatternHighlightingEnabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
highlightMiniLocations(this.editor, time, haps);
|
highlightMiniLocations(this.editor, time, haps);
|
||||||
}
|
}
|
||||||
shouldAnimate() {
|
|
||||||
return (
|
|
||||||
this.repl.scheduler.started &&
|
|
||||||
(this.isPatternHighlightingEnabled || this.hasPainters || this.onDraw !== this.draw)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
setFontSize(size) {
|
setFontSize(size) {
|
||||||
this.root.style.fontSize = size + 'px';
|
this.root.style.fontSize = size + 'px';
|
||||||
}
|
}
|
||||||
@@ -355,11 +343,6 @@ export class StrudelMirror {
|
|||||||
this.editor.dispatch({
|
this.editor.dispatch({
|
||||||
effects: compartments[key].reconfigure(newValue),
|
effects: compartments[key].reconfigure(newValue),
|
||||||
});
|
});
|
||||||
if (key === 'isPatternHighlightingEnabled') {
|
|
||||||
this.isPatternHighlightingEnabled = value;
|
|
||||||
this.drawer.stop();
|
|
||||||
this.shouldAnimate() && this.drawer.start(this.repl.scheduler);
|
|
||||||
}
|
|
||||||
if (key === 'theme') {
|
if (key === 'theme') {
|
||||||
activateTheme(value);
|
activateTheme(value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1294,6 +1294,8 @@ export const { fanchor } = registerControl('fanchor');
|
|||||||
*
|
*
|
||||||
* @name lprate
|
* @name lprate
|
||||||
* @param {number | Pattern} rate rate in hertz
|
* @param {number | Pattern} rate rate in hertz
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lprate("<4 8 2 1>")
|
||||||
*/
|
*/
|
||||||
export const { lprate } = registerControl('lprate');
|
export const { lprate } = registerControl('lprate');
|
||||||
|
|
||||||
@@ -1302,6 +1304,8 @@ export const { lprate } = registerControl('lprate');
|
|||||||
*
|
*
|
||||||
* @name lpsync
|
* @name lpsync
|
||||||
* @param {number | Pattern} rate rate in cycles
|
* @param {number | Pattern} rate rate in cycles
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpsync("<4 8 2 1>")
|
||||||
*/
|
*/
|
||||||
export const { lpsync } = registerControl('lpsync');
|
export const { lpsync } = registerControl('lpsync');
|
||||||
|
|
||||||
@@ -1310,8 +1314,23 @@ export const { lpsync } = registerControl('lpsync');
|
|||||||
*
|
*
|
||||||
* @name lpdepth
|
* @name lpdepth
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepth("<1 .5 1.8 0>")
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const { lpdepth } = registerControl('lpdepth');
|
export const { lpdepth } = registerControl('lpdepth');
|
||||||
|
/**
|
||||||
|
* Depth of the LFO for the lowpass filter, in HZ
|
||||||
|
*
|
||||||
|
* @name lpdepthfrequency
|
||||||
|
* @synonyms
|
||||||
|
* lpdethfreq
|
||||||
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).lpdepthfrequency("<200 500 100 0>")
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const { lpdepthfrequency } = registerControl('lpdepthfrequency', 'lpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the lowpass filter
|
* Shape of the LFO for the lowpass filter
|
||||||
@@ -1361,6 +1380,19 @@ export const { bpsync } = registerControl('bpsync');
|
|||||||
*/
|
*/
|
||||||
export const { bpdepth } = registerControl('bpdepth');
|
export const { bpdepth } = registerControl('bpdepth');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depth of the LFO for the bandpass filter, in HZ
|
||||||
|
*
|
||||||
|
* @name bpdepthfrequency
|
||||||
|
* @synonyms
|
||||||
|
* bpdethfreq
|
||||||
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).bpdepthfrequency("<200 500 100 0>")
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const { bpdepthfrequency } = registerControl('bpdepthfrequency', 'bpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the bandpass filter
|
* Shape of the LFO for the bandpass filter
|
||||||
*
|
*
|
||||||
@@ -1407,7 +1439,20 @@ export const { hpsync } = registerControl('hpsync');
|
|||||||
* @name hpdepth
|
* @name hpdepth
|
||||||
* @param {number | Pattern} depth depth of modulation
|
* @param {number | Pattern} depth depth of modulation
|
||||||
*/
|
*/
|
||||||
export const { hpdepth } = registerControl('hpdepth');
|
export const { hpdepth, hpdepthfreq } = registerControl('hpdepth');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Depth of the LFO for the hipass filter, in hz
|
||||||
|
*
|
||||||
|
* @name hpdepthfrequency
|
||||||
|
* @synonyms
|
||||||
|
* hpdethfreq
|
||||||
|
* @param {number | Pattern} depth depth of modulation
|
||||||
|
* @example
|
||||||
|
* note("<c c c# c c c4>*16").s("sawtooth").lpf(600).hpdepthfrequency("<200 500 100 0>")
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const { hpdepthfrequency } = registerControl('hpdepthfrequency', 'hpdepthfreq');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shape of the LFO for the highpass filter
|
* Shape of the LFO for the highpass filter
|
||||||
@@ -1833,6 +1878,7 @@ export const { octave } = registerControl('octave');
|
|||||||
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
* An `orbit` is a global parameter context for patterns. Patterns with the same orbit will share the same global effects.
|
||||||
*
|
*
|
||||||
* @name orbit
|
* @name orbit
|
||||||
|
* @synonyms o
|
||||||
* @param {number | Pattern} number
|
* @param {number | Pattern} number
|
||||||
* @example
|
* @example
|
||||||
* stack(
|
* stack(
|
||||||
@@ -1840,7 +1886,7 @@ export const { octave } = registerControl('octave');
|
|||||||
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
|
* s("~ sd ~ sd").delay(.5).delaytime(.125).orbit(2)
|
||||||
* )
|
* )
|
||||||
*/
|
*/
|
||||||
export const { orbit } = registerControl('orbit');
|
export const { orbit } = registerControl('orbit', 'o');
|
||||||
// TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that
|
// TODO: what is this? not found in tidal doc Answer: gain is limited to maximum of 2. This allows you to go over that
|
||||||
export const { overgain } = registerControl('overgain');
|
export const { overgain } = registerControl('overgain');
|
||||||
// TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
|
// TODO: what is this? not found in tidal doc. Similar to above, but limited to 1
|
||||||
|
|||||||
@@ -91,9 +91,6 @@ Fraction.prototype.or = function (other) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fraction = (n) => {
|
const fraction = (n) => {
|
||||||
if (n instanceof Fraction) {
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
if (typeof n === 'number') {
|
if (typeof n === 'number') {
|
||||||
/*
|
/*
|
||||||
https://github.com/infusion/Fraction.js/#doubles
|
https://github.com/infusion/Fraction.js/#doubles
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
stringifyValues,
|
stringifyValues,
|
||||||
} from './util.mjs';
|
} from './util.mjs';
|
||||||
import drawLine from './drawLine.mjs';
|
import drawLine from './drawLine.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { errorLogger, logger } from './logger.mjs';
|
||||||
|
|
||||||
let stringParser;
|
let stringParser;
|
||||||
|
|
||||||
@@ -414,7 +414,7 @@ export class Pattern {
|
|||||||
try {
|
try {
|
||||||
return this.query(new State(new TimeSpan(begin, end), controls));
|
return this.query(new State(new TimeSpan(begin, end), controls));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger(`[query]: ${err.message}`, 'error');
|
errorLogger(err, 'query');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,8 +238,8 @@ export function repl({
|
|||||||
pattern = eachTransform(pattern);
|
pattern = eachTransform(pattern);
|
||||||
}
|
}
|
||||||
if (allTransforms.length) {
|
if (allTransforms.length) {
|
||||||
for (let i in allTransforms) {
|
for (const transform of allTransforms) {
|
||||||
pattern = allTransforms[i](pattern);
|
pattern = transform(pattern);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -253,12 +253,7 @@ export const pairs = function (xs) {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optimized clamp (~1.7x faster than Math.max/min approach)
|
export const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
export const clamp = (num, min, max) => {
|
|
||||||
if (num < min) return min;
|
|
||||||
if (num > max) return max;
|
|
||||||
return num;
|
|
||||||
};
|
|
||||||
|
|
||||||
/* solmization, not used yet */
|
/* solmization, not used yet */
|
||||||
const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/
|
const solfeggio = ['Do', 'Reb', 'Re', 'Mib', 'Mi', 'Fa', 'Solb', 'Sol', 'Lab', 'La', 'Sib', 'Si']; /*solffegio notes*/
|
||||||
|
|||||||
@@ -36,27 +36,6 @@ export function getWorklet(ac, processor, params, config) {
|
|||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pool of inactive (weakly referenced) voices to be reused if possible
|
|
||||||
const voicePools = {};
|
|
||||||
|
|
||||||
// Cycles through the pool attempting to find an inactive voice that hasn't been GC'd
|
|
||||||
export const claimVoice = (key) => {
|
|
||||||
voicePools[key] ??= [];
|
|
||||||
const pool = voicePools[key];
|
|
||||||
let node;
|
|
||||||
while (pool.length && !node) {
|
|
||||||
const ref = pool.pop();
|
|
||||||
node = ref?.deref?.();
|
|
||||||
}
|
|
||||||
return node;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Releases a voice from use and adds to the inactive pool
|
|
||||||
export const releaseVoice = (key, node) => {
|
|
||||||
voicePools[key] ??= [];
|
|
||||||
voicePools[key].push(new WeakRef(node));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getParamADSR = (
|
export const getParamADSR = (
|
||||||
param,
|
param,
|
||||||
attack,
|
attack,
|
||||||
@@ -212,8 +191,7 @@ export function applyParameterModulators(audioContext, param, start, end, envelo
|
|||||||
const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
|
const lfo = getParamLfo(audioContext, param, start, end, lfoValues);
|
||||||
return { lfo, disconnect: () => lfo?.disconnect() };
|
return { lfo, disconnect: () => lfo?.disconnect() };
|
||||||
}
|
}
|
||||||
|
export function createFilter(context, start, end, params, cps, cycle) {
|
||||||
export function createFilter(context, start, end, params, cps) {
|
|
||||||
let {
|
let {
|
||||||
frequency,
|
frequency,
|
||||||
anchor,
|
anchor,
|
||||||
@@ -223,12 +201,14 @@ export function createFilter(context, start, end, params, cps) {
|
|||||||
q = 1,
|
q = 1,
|
||||||
drive = 0.69,
|
drive = 0.69,
|
||||||
depth,
|
depth,
|
||||||
|
depthfrequency,
|
||||||
dcoffset = -0.5,
|
dcoffset = -0.5,
|
||||||
skew,
|
skew,
|
||||||
shape,
|
shape,
|
||||||
rate,
|
rate,
|
||||||
sync,
|
sync,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
let frequencyParam, filter;
|
let frequencyParam, filter;
|
||||||
if (model === 'ladder') {
|
if (model === 'ladder') {
|
||||||
filter = getWorklet(context, 'ladder-processor', { frequency, q, drive });
|
filter = getWorklet(context, 'ladder-processor', { frequency, q, drive });
|
||||||
@@ -259,8 +239,25 @@ export function createFilter(context, start, end, params, cps) {
|
|||||||
if (sync != null) {
|
if (sync != null) {
|
||||||
rate = cps * sync;
|
rate = cps * sync;
|
||||||
}
|
}
|
||||||
const lfoValues = { depth, dcoffset, skew, shape, frequency: rate, min: 10, max: 20000 };
|
const hasLFO = [depth, depthfrequency, skew, shape, rate].some((v) => v !== undefined);
|
||||||
getParamLfo(context, frequencyParam, start, end, lfoValues);
|
if (hasLFO) {
|
||||||
|
depth = depth ?? 1;
|
||||||
|
const time = cycle / cps;
|
||||||
|
const modDepth = depthfrequency ?? (depth ?? 1) * frequency;
|
||||||
|
const lfoValues = {
|
||||||
|
depth: modDepth,
|
||||||
|
dcoffset,
|
||||||
|
skew,
|
||||||
|
shape,
|
||||||
|
frequency: rate ?? cps,
|
||||||
|
min: -frequency + 30,
|
||||||
|
max: 20000 - frequency,
|
||||||
|
time,
|
||||||
|
curve: 1,
|
||||||
|
};
|
||||||
|
getParamLfo(context, frequencyParam, start, end, lfoValues);
|
||||||
|
}
|
||||||
|
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,17 +336,6 @@ export function scheduleAtTime(callback, targetTime, audioContext = getAudioCont
|
|||||||
const currentTime = audioContext.currentTime;
|
const currentTime = audioContext.currentTime;
|
||||||
webAudioTimeout(audioContext, callback, currentTime, targetTime);
|
webAudioTimeout(audioContext, callback, currentTime, targetTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
let silencer;
|
|
||||||
function getSilencer() {
|
|
||||||
const ac = getAudioContext();
|
|
||||||
if (!silencer) {
|
|
||||||
silencer = gainNode(0);
|
|
||||||
silencer.connect(ac.destination);
|
|
||||||
}
|
|
||||||
return silencer;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConstantSource inherits AudioScheduledSourceNode, which has scheduling abilities
|
// ConstantSource inherits AudioScheduledSourceNode, which has scheduling abilities
|
||||||
// a bit of a hack, but it works very well :)
|
// a bit of a hack, but it works very well :)
|
||||||
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
||||||
@@ -357,11 +343,18 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
|||||||
|
|
||||||
// Certain browsers requires audio nodes to be connected in order for their onended events
|
// Certain browsers requires audio nodes to be connected in order for their onended events
|
||||||
// to fire, so we _mute it_ and then connect it to the destination
|
// to fire, so we _mute it_ and then connect it to the destination
|
||||||
const zeroGain = getSilencer();
|
const zeroGain = gainNode(0);
|
||||||
|
zeroGain.connect(audioContext.destination);
|
||||||
constantNode.connect(zeroGain);
|
constantNode.connect(zeroGain);
|
||||||
|
|
||||||
// Schedule the `onComplete` callback to occur at `stopTime`
|
// Schedule the `onComplete` callback to occur at `stopTime`
|
||||||
constantNode.onended = () => {
|
constantNode.onended = () => {
|
||||||
|
// Ensure garbage collection
|
||||||
|
try {
|
||||||
|
zeroGain.disconnect();
|
||||||
|
} catch {
|
||||||
|
// pass
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
constantNode.disconnect();
|
constantNode.disconnect();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -387,9 +380,9 @@ const mod = (freq, range = 1, type = 'sine') => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
osc.start();
|
osc.start();
|
||||||
const g = new GainNode(ctx, { gain: range });
|
const g = gainNode(range);
|
||||||
osc.connect(g); // -range, range
|
osc.connect(g); // -range, range
|
||||||
return { node: g, stop: (t) => osc.stop(t) };
|
return { node: g, stop: (t) => osc.stop(t), osc: osc };
|
||||||
};
|
};
|
||||||
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
const fm = (frequencyparam, harmonicityRatio, modulationIndex, wave = 'sine') => {
|
||||||
const carrfreq = frequencyparam.value;
|
const carrfreq = frequencyparam.value;
|
||||||
@@ -441,22 +434,22 @@ export function applyFM(param, value, begin) {
|
|||||||
modulator.connect(envGain);
|
modulator.connect(envGain);
|
||||||
envGain.connect(param);
|
envGain.connect(param);
|
||||||
}
|
}
|
||||||
|
fmmod.osc.onended = () => {
|
||||||
|
envGain.disconnect();
|
||||||
|
modulator.disconnect();
|
||||||
|
fmmod.osc.disconnect();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return { stop };
|
return { stop };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saturation curves
|
// Saturation curves
|
||||||
|
|
||||||
const fastTanh = (x) => {
|
|
||||||
const x2 = x * x;
|
|
||||||
const y = (x * (27 + x2)) / (27 + 9 * x2);
|
|
||||||
return y < -1 ? -1 : y > 1 ? 1 : y;
|
|
||||||
};
|
|
||||||
const __squash = (x) => x / (1 + x); // [0, inf) to [0, 1)
|
const __squash = (x) => x / (1 + x); // [0, inf) to [0, 1)
|
||||||
const _mod = (n, m) => ((n % m) + m) % m;
|
const _mod = (n, m) => ((n % m) + m) % m;
|
||||||
|
|
||||||
const _scurve = (x, k) => ((1 + k) * x) / (1 + k * Math.abs(x));
|
const _scurve = (x, k) => ((1 + k) * x) / (1 + k * Math.abs(x));
|
||||||
const _soft = (x, k) => fastTanh(x * (1 + k));
|
const _soft = (x, k) => Math.tanh(x * (1 + k));
|
||||||
const _hard = (x, k) => clamp((1 + k) * x, -1, 1);
|
const _hard = (x, k) => clamp((1 + k) * x, -1, 1);
|
||||||
|
|
||||||
const _fold = (x, k) => {
|
const _fold = (x, k) => {
|
||||||
@@ -496,8 +489,13 @@ const _chebyshev = (x, k) => {
|
|||||||
let tnm1 = 1;
|
let tnm1 = 1;
|
||||||
let tnm2 = x;
|
let tnm2 = x;
|
||||||
let tn;
|
let tn;
|
||||||
let y = x;
|
let y = 0;
|
||||||
for (let i = 2; i < 64; i++) {
|
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
|
tn = 2 * x * tnm1 - tnm2; // https://en.wikipedia.org/wiki/Chebyshev_polynomials#Recurrence_definition
|
||||||
tnm2 = tnm1;
|
tnm2 = tnm1;
|
||||||
tnm1 = tn;
|
tnm1 = tn;
|
||||||
@@ -562,25 +560,3 @@ export const destroyAudioWorkletNode = (node) => {
|
|||||||
node.disconnect();
|
node.disconnect();
|
||||||
node.parameters.get('end')?.setValueAtTime(0, 0);
|
node.parameters.get('end')?.setValueAtTime(0, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setParams = (node, params, t) => {
|
|
||||||
const ac = getAudioContext();
|
|
||||||
t ??= ac.currentTime;
|
|
||||||
const workletParams = node && node.parameters && typeof node.parameters.get === 'function';
|
|
||||||
for (const [key, value] of Object.entries(params)) {
|
|
||||||
if (value == null) continue;
|
|
||||||
let param;
|
|
||||||
if (workletParams) {
|
|
||||||
param = node.parameters.get(key);
|
|
||||||
}
|
|
||||||
if (param == null && node[key] instanceof AudioParam) {
|
|
||||||
param = node[key];
|
|
||||||
}
|
|
||||||
if (param instanceof AudioParam) {
|
|
||||||
param.cancelScheduledValues(t);
|
|
||||||
param.setValueAtTime(value, t);
|
|
||||||
} else {
|
|
||||||
node[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -478,16 +478,15 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
const endWithRelease = end + release;
|
const endWithRelease = end + release;
|
||||||
const chainID = Math.round(Math.random() * 1000000);
|
const chainID = Math.round(Math.random() * 1000000);
|
||||||
|
|
||||||
// oldest audio nodes will be stopped if maximum polyphony is exceeded
|
// oldest audio nodes will be destroyed if maximum polyphony is exceeded
|
||||||
while (activeSoundSources.size >= maxPolyphony) {
|
for (let i = 0; i <= activeSoundSources.size - maxPolyphony; i++) {
|
||||||
const { value: entry } = activeSoundSources.entries().next();
|
const ch = activeSoundSources.entries().next();
|
||||||
if (!entry) break;
|
const source = ch.value[1].deref();
|
||||||
const [oldChainID, ref] = entry;
|
const chainID = ch.value[0];
|
||||||
const handle = ref?.deref();
|
|
||||||
const endTime = t + 0.25;
|
const endTime = t + 0.25;
|
||||||
handle?.node?.gain?.linearRampToValueAtTime?.(0, endTime);
|
source?.node?.gain?.linearRampToValueAtTime(0, endTime);
|
||||||
handle?.stop?.(endTime);
|
source?.stop?.(endTime);
|
||||||
activeSoundSources.delete(oldChainID);
|
activeSoundSources.delete(chainID);
|
||||||
}
|
}
|
||||||
|
|
||||||
let audioNodes = [];
|
let audioNodes = [];
|
||||||
@@ -554,13 +553,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
rate: 'lprate',
|
rate: 'lprate',
|
||||||
sync: 'lpsync',
|
sync: 'lpsync',
|
||||||
depth: 'lpdepth',
|
depth: 'lpdepth',
|
||||||
|
depthfrequency: 'lpdepthfrequency',
|
||||||
shape: 'lpshape',
|
shape: 'lpshape',
|
||||||
dcoffset: 'lpdc',
|
dcoffset: 'lpdc',
|
||||||
skew: 'lpskew',
|
skew: 'lpskew',
|
||||||
};
|
};
|
||||||
const lpParams = pickAndRename(value, lpMap);
|
const lpParams = pickAndRename(value, lpMap);
|
||||||
lpParams.type = 'lowpass';
|
lpParams.type = 'lowpass';
|
||||||
let lp = () => createFilter(ac, t, end, lpParams, cps);
|
let lp = () => createFilter(ac, t, end, lpParams, cps, cycle);
|
||||||
chain.push(lp());
|
chain.push(lp());
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(lp());
|
chain.push(lp());
|
||||||
@@ -582,13 +582,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
rate: 'hprate',
|
rate: 'hprate',
|
||||||
sync: 'hpsync',
|
sync: 'hpsync',
|
||||||
depth: 'hpdepth',
|
depth: 'hpdepth',
|
||||||
|
depthfrequency: 'hpdepthfrequency',
|
||||||
shape: 'hpshape',
|
shape: 'hpshape',
|
||||||
dcoffset: 'hpdc',
|
dcoffset: 'hpdc',
|
||||||
skew: 'hpskew',
|
skew: 'hpskew',
|
||||||
};
|
};
|
||||||
const hpParams = pickAndRename(value, hpMap);
|
const hpParams = pickAndRename(value, hpMap);
|
||||||
hpParams.type = 'highpass';
|
hpParams.type = 'highpass';
|
||||||
let hp = () => createFilter(ac, t, end, hpParams, cps);
|
let hp = () => createFilter(ac, t, end, hpParams, cps, cycle);
|
||||||
chain.push(hp());
|
chain.push(hp());
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(hp());
|
chain.push(hp());
|
||||||
@@ -610,13 +611,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
rate: 'bprate',
|
rate: 'bprate',
|
||||||
sync: 'bpsync',
|
sync: 'bpsync',
|
||||||
depth: 'bpdepth',
|
depth: 'bpdepth',
|
||||||
|
depthfrequency: 'bpdepthfrequency',
|
||||||
shape: 'bpshape',
|
shape: 'bpshape',
|
||||||
dcoffset: 'bpdc',
|
dcoffset: 'bpdc',
|
||||||
skew: 'bpskew',
|
skew: 'bpskew',
|
||||||
};
|
};
|
||||||
const bpParams = pickAndRename(value, bpMap);
|
const bpParams = pickAndRename(value, bpMap);
|
||||||
bpParams.type = 'bandpass';
|
bpParams.type = 'bandpass';
|
||||||
let bp = () => createFilter(ac, t, end, bpParams, cps);
|
let bp = () => createFilter(ac, t, end, bpParams, cps, cycle);
|
||||||
chain.push(bp());
|
chain.push(bp());
|
||||||
if (ftype === '24db') {
|
if (ftype === '24db') {
|
||||||
chain.push(bp());
|
chain.push(bp());
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { registerSound, soundMap } from './superdough.mjs';
|
|||||||
import { getAudioContext } from './audioContext.mjs';
|
import { getAudioContext } from './audioContext.mjs';
|
||||||
import {
|
import {
|
||||||
applyFM,
|
applyFM,
|
||||||
claimVoice,
|
|
||||||
destroyAudioWorkletNode,
|
destroyAudioWorkletNode,
|
||||||
gainNode,
|
gainNode,
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
@@ -13,8 +12,6 @@ import {
|
|||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
setParams,
|
|
||||||
releaseVoice,
|
|
||||||
noises,
|
noises,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
@@ -167,38 +164,36 @@ export function registerSynthSounds() {
|
|||||||
const end = holdend + release + 0.01;
|
const end = holdend + release + 0.01;
|
||||||
const voices = clamp(unison, 1, 100);
|
const voices = clamp(unison, 1, 100);
|
||||||
let panspread = voices > 1 ? clamp(spread, 0, 1) : 0;
|
let panspread = voices > 1 ? clamp(spread, 0, 1) : 0;
|
||||||
let o = claimVoice('supersaw');
|
let o = getWorklet(
|
||||||
const params = {
|
ac,
|
||||||
frequency,
|
'supersaw-oscillator',
|
||||||
begin,
|
{
|
||||||
end,
|
frequency,
|
||||||
freqspread: detune,
|
begin,
|
||||||
panspread,
|
end,
|
||||||
};
|
freqspread: detune,
|
||||||
const processorOptions = {
|
voices,
|
||||||
voices,
|
panspread,
|
||||||
};
|
},
|
||||||
if (o == null) {
|
{
|
||||||
o = getWorklet(ac, 'supersaw-oscillator', params, {
|
|
||||||
outputChannelCount: [2],
|
outputChannelCount: [2],
|
||||||
processorOptions,
|
},
|
||||||
});
|
);
|
||||||
} else {
|
|
||||||
setParams(o, params);
|
|
||||||
o.port.postMessage({ type: 'reset', payload: { processorOptions } });
|
|
||||||
}
|
|
||||||
const gainAdjustment = 1 / Math.sqrt(voices);
|
const gainAdjustment = 1 / Math.sqrt(voices);
|
||||||
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
getPitchEnvelope(o.parameters.get('detune'), value, begin, holdend);
|
||||||
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
const vibratoOscillator = getVibratoOscillator(o.parameters.get('detune'), value, begin);
|
||||||
const fm = applyFM(o.parameters.get('frequency'), value, begin);
|
const fm = applyFM(o.parameters.get('frequency'), value, begin);
|
||||||
const envGain = o.connect(gainNode(1));
|
let envGain = gainNode(1);
|
||||||
|
envGain = o.connect(envGain);
|
||||||
|
|
||||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear');
|
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3 * gainAdjustment, begin, holdend, 'linear');
|
||||||
const timeoutNode = webAudioTimeout(
|
|
||||||
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
destroyAudioWorkletNode(o);
|
||||||
envGain.disconnect();
|
envGain.disconnect();
|
||||||
releaseVoice('supersaw', o);
|
|
||||||
onended();
|
onended();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
@@ -272,7 +267,7 @@ export function registerSynthSounds() {
|
|||||||
|
|
||||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
|
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 1, begin, holdend, 'linear');
|
||||||
|
|
||||||
const timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
destroyAudioWorkletNode(o);
|
||||||
@@ -346,7 +341,7 @@ export function registerSynthSounds() {
|
|||||||
lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep });
|
lfo = getLfo(ac, begin, end, { frequency: pwrate, depth: pwsweep });
|
||||||
lfo.connect(o.parameters.get('pulsewidth'));
|
lfo.connect(o.parameters.get('pulsewidth'));
|
||||||
}
|
}
|
||||||
const timeoutNode = webAudioTimeout(
|
let timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
destroyAudioWorkletNode(o);
|
destroyAudioWorkletNode(o);
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { logger } from './logger.mjs';
|
|||||||
// currently duplicate with core util.mjs to skip dependency
|
// currently duplicate with core util.mjs to skip dependency
|
||||||
// TODO: add separate util module?
|
// TODO: add separate util module?
|
||||||
|
|
||||||
export const LN2 = Math.LN2;
|
|
||||||
|
|
||||||
export const tokenizeNote = (note) => {
|
export const tokenizeNote = (note) => {
|
||||||
if (typeof note !== 'string') {
|
if (typeof note !== 'string') {
|
||||||
return [];
|
return [];
|
||||||
@@ -34,9 +32,10 @@ export const noteToMidi = (note, defaultOctave = 3) => {
|
|||||||
export const midiToFreq = (n) => {
|
export const midiToFreq = (n) => {
|
||||||
return Math.pow(2, (n - 69) / 12) * 440;
|
return Math.pow(2, (n - 69) / 12) * 440;
|
||||||
};
|
};
|
||||||
|
export const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
|
|
||||||
export const freqToMidi = (freq) => {
|
export const freqToMidi = (freq) => {
|
||||||
return (12 * Math.log(freq / 440)) / LN2 + 69;
|
return (12 * Math.log(freq / 440)) / Math.LN2 + 69;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const valueToMidi = (value, fallbackValue) => {
|
export const valueToMidi = (value, fallbackValue) => {
|
||||||
@@ -115,38 +114,3 @@ export function getCommonSampleInfo(hapValue, bank) {
|
|||||||
export const pickAndRename = (source, map) => {
|
export const pickAndRename = (source, map) => {
|
||||||
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
|
return Object.fromEntries(Object.entries(map).map(([newKey, oldKey]) => [newKey, source[oldKey]]));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optimized / numerical functions (ideal for hot dsp loops)
|
|
||||||
|
|
||||||
// Optimized clamp (~1.7x faster than Math.max/min approach)
|
|
||||||
export const clamp = (num, min, max) => {
|
|
||||||
if (num < min) return min;
|
|
||||||
if (num > max) return max;
|
|
||||||
return num;
|
|
||||||
};
|
|
||||||
export const mod = (n, m) => ((n % m) + m) % m;
|
|
||||||
export const lerp = (a, b, n) => n * (b - a) + a;
|
|
||||||
export const frac = (x) => x - Math.floor(x);
|
|
||||||
|
|
||||||
// Fast integer ops for non-negative values
|
|
||||||
export const ffloor = (x) => x | 0;
|
|
||||||
export const fround = (x) => ffloor(x + 0.5);
|
|
||||||
export const fceil = (x) => ffloor(x + 1);
|
|
||||||
export const ffrac = (x) => x - ffloor(x);
|
|
||||||
|
|
||||||
export const fastexpm1 = (x) => x * (1 + x + 0.5 * x * x); // Taylor approximation
|
|
||||||
|
|
||||||
export const fastpow2 = (x) => {
|
|
||||||
// Taylor approximation of 2 ^ x
|
|
||||||
const a = x * LN2;
|
|
||||||
return 1 + a * (1 + a * (0.5 + a / 6));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const applySemitoneDetuneToFrequency = (frequency, detune) => {
|
|
||||||
return frequency * Math.pow(2, detune / 12);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const applyFastDetune = (frequency, detune) => {
|
|
||||||
if (detune < 4) return frequency * fastpow2(detune / 12);
|
|
||||||
return applySemitoneDetuneToFrequency(frequency, detune);
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -3,18 +3,14 @@ import { getCommonSampleInfo } from './util.mjs';
|
|||||||
import {
|
import {
|
||||||
applyFM,
|
applyFM,
|
||||||
applyParameterModulators,
|
applyParameterModulators,
|
||||||
claimVoice,
|
destroyAudioWorkletNode,
|
||||||
gainNode,
|
|
||||||
getADSRValues,
|
getADSRValues,
|
||||||
getFrequencyFromValue,
|
getFrequencyFromValue,
|
||||||
getParamADSR,
|
getParamADSR,
|
||||||
getPitchEnvelope,
|
getPitchEnvelope,
|
||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
setParams,
|
|
||||||
releaseVoice,
|
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
destroyAudioWorkletNode,
|
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
|
|
||||||
@@ -222,39 +218,32 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
const frequency = getFrequencyFromValue(value);
|
const frequency = getFrequencyFromValue(value);
|
||||||
const { url, label } = getCommonSampleInfo(value, tables);
|
const { url, label } = getCommonSampleInfo(value, tables);
|
||||||
const payload = await getPayload(url, label, frameLen);
|
const payload = await getPayload(url, label, frameLen);
|
||||||
const voiceKey = `wavetable:${payload.key}`;
|
|
||||||
let holdEnd = t + duration;
|
let holdEnd = t + duration;
|
||||||
if (clip !== undefined) {
|
if (clip !== undefined) {
|
||||||
holdEnd = Math.min(t + clip * duration, holdEnd);
|
holdEnd = Math.min(t + clip * duration, holdEnd);
|
||||||
}
|
}
|
||||||
const endWithRelease = holdEnd + release;
|
const endWithRelease = holdEnd + release;
|
||||||
const envEnd = endWithRelease + 0.01;
|
const envEnd = endWithRelease + 0.01;
|
||||||
let source = claimVoice(voiceKey);
|
const source = getWorklet(
|
||||||
const params = {
|
ac,
|
||||||
begin: t,
|
'wavetable-oscillator-processor',
|
||||||
end: envEnd,
|
{
|
||||||
frequency,
|
begin: t,
|
||||||
freqspread: value.detune,
|
end: envEnd,
|
||||||
position: value.wt,
|
frequency,
|
||||||
warp: value.warp,
|
freqspread: value.detune,
|
||||||
warpMode: warpmode,
|
position: value.wt,
|
||||||
panspread: value.spread,
|
warp: value.warp,
|
||||||
};
|
warpMode: warpmode,
|
||||||
const processorOptions = {
|
voices: Math.max(value.unison ?? 1, 1),
|
||||||
phaseRand: value.wtphaserand ?? (value.unison > 1 ? 1 : 0),
|
panspread: value.spread,
|
||||||
voices: Math.max(value.unison ?? 1, 1),
|
phaserand: (value.wtphaserand ?? value.unison > 1) ? 1 : 0,
|
||||||
};
|
},
|
||||||
if (source == null) {
|
{ outputChannelCount: [2] },
|
||||||
source = getWorklet(ac, 'wavetable-oscillator-processor', params, { outputChannelCount: [2], processorOptions });
|
);
|
||||||
} else {
|
|
||||||
setParams(source, params);
|
|
||||||
source.port.postMessage({ type: 'reset', payload: { processorOptions } });
|
|
||||||
}
|
|
||||||
source.port.postMessage({ type: 'table', payload });
|
source.port.postMessage({ type: 'table', payload });
|
||||||
if (ac.currentTime > t) {
|
if (ac.currentTime > t) {
|
||||||
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
logger(`[wavetable] still loading sound "${s}:${n}"`, 'highlight');
|
||||||
destroyAudioWorkletNode(source);
|
|
||||||
releaseVoice(voiceKey, source);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const posADSRParams = [value.wtattack, value.wtdecay, value.wtsustain, value.wtrelease];
|
const posADSRParams = [value.wtattack, value.wtdecay, value.wtsustain, value.wtrelease];
|
||||||
@@ -321,20 +310,20 @@ export async function onTriggerSynth(t, value, onended, tables, cps, frameLen) {
|
|||||||
);
|
);
|
||||||
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
|
const vibratoOscillator = getVibratoOscillator(source.parameters.get('detune'), value, t);
|
||||||
const fm = applyFM(source.parameters.get('frequency'), value, t);
|
const fm = applyFM(source.parameters.get('frequency'), value, t);
|
||||||
const envGain = source.connect(gainNode(1));
|
const envGain = ac.createGain();
|
||||||
getParamADSR(envGain.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
|
const node = source.connect(envGain);
|
||||||
|
getParamADSR(node.gain, attack, decay, sustain, release, 0, 0.3, t, holdEnd, 'linear');
|
||||||
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
|
getPitchEnvelope(source.parameters.get('detune'), value, t, holdEnd);
|
||||||
const handle = { node: envGain };
|
const handle = { node, source };
|
||||||
const timeoutNode = webAudioTimeout(
|
const timeoutNode = webAudioTimeout(
|
||||||
ac,
|
ac,
|
||||||
() => {
|
() => {
|
||||||
|
destroyAudioWorkletNode(source);
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
fm?.stop();
|
fm?.stop();
|
||||||
|
node.disconnect();
|
||||||
wtPosModulators?.disconnect();
|
wtPosModulators?.disconnect();
|
||||||
wtWarpModulators?.disconnect();
|
wtWarpModulators?.disconnect();
|
||||||
envGain.disconnect();
|
|
||||||
destroyAudioWorkletNode(source);
|
|
||||||
releaseVoice(voiceKey, source);
|
|
||||||
onended();
|
onended();
|
||||||
},
|
},
|
||||||
t,
|
t,
|
||||||
|
|||||||
+132
-124
@@ -5,29 +5,27 @@
|
|||||||
import OLAProcessor from './ola-processor';
|
import OLAProcessor from './ola-processor';
|
||||||
import FFT from './fft.js';
|
import FFT from './fft.js';
|
||||||
import { getDistortionAlgorithm } from './helpers.mjs';
|
import { getDistortionAlgorithm } from './helpers.mjs';
|
||||||
import {
|
|
||||||
applyFastDetune,
|
|
||||||
applySemitoneDetuneToFrequency,
|
|
||||||
clamp,
|
|
||||||
fastexpm1,
|
|
||||||
fceil,
|
|
||||||
ffloor,
|
|
||||||
ffrac,
|
|
||||||
frac,
|
|
||||||
fround,
|
|
||||||
lerp,
|
|
||||||
} from './util.mjs';
|
|
||||||
|
|
||||||
const blockSize = 128;
|
const blockSize = 128;
|
||||||
const PI = Math.PI;
|
const PI = Math.PI;
|
||||||
const TWO_PI = 2 * PI;
|
const TWO_PI = 2 * PI;
|
||||||
const INVSR = 1 / sampleRate;
|
const INVSR = 1 / sampleRate;
|
||||||
const MAX_VOICES = 64;
|
|
||||||
|
|
||||||
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
|
const mod = (n, m) => ((n % m) + m) % m;
|
||||||
|
const lerp = (a, b, n) => n * (b - a) + a;
|
||||||
const pv = (arr, n) => arr[n] ?? arr[0];
|
const pv = (arr, n) => arr[n] ?? arr[0];
|
||||||
|
const frac = (x) => x - Math.floor(x);
|
||||||
|
|
||||||
|
// Fast integer ops for non-negative values
|
||||||
|
const ffloor = (x) => x | 0;
|
||||||
|
const fround = (x) => ffloor(x + 0.5);
|
||||||
|
const fceil = (x) => ffloor(x + 1);
|
||||||
|
const ffrac = (x) => x - ffloor(x);
|
||||||
|
|
||||||
const fast_tanh = (x) => {
|
const fast_tanh = (x) => {
|
||||||
const x2 = x * x;
|
const x2 = x ** 2;
|
||||||
return (x * (27 + x2)) / (27 + 9 * x2);
|
return (x * (27.0 + x2)) / (27.0 + 9.0 * x2);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Optimized per-voice detuner which precomputes constants
|
// Optimized per-voice detuner which precomputes constants
|
||||||
@@ -40,6 +38,10 @@ const getDetuner = (unison, detune) => {
|
|||||||
return (voiceIdx) => voiceIdx * scale - center;
|
return (voiceIdx) => voiceIdx * scale - center;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const applySemitoneDetuneToFrequency = (frequency, detune) => {
|
||||||
|
return frequency * Math.pow(2, detune / 12);
|
||||||
|
};
|
||||||
|
|
||||||
// Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing
|
// Smooth waveshape near discontinuities to remove frequencies above Nyquist and prevent aliasing
|
||||||
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
// referenced from https://www.kvraudio.com/forum/viewtopic.php?t=375517
|
||||||
function polyBlep(phase, dt) {
|
function polyBlep(phase, dt) {
|
||||||
@@ -167,11 +169,10 @@ class LFOProcessor extends AudioWorkletProcessor {
|
|||||||
}
|
}
|
||||||
const dt = frequency * INVSR;
|
const dt = frequency * INVSR;
|
||||||
for (let n = 0; n < blockSize; n++) {
|
for (let n = 0; n < blockSize; n++) {
|
||||||
let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
|
|
||||||
modval = curve !== 1 ? Math.pow(modval, curve) : modval;
|
|
||||||
const out = clamp(modval, min, max);
|
|
||||||
for (let i = 0; i < output.length; i++) {
|
for (let i = 0; i < output.length; i++) {
|
||||||
output[i][n] = out;
|
let modval = (waveshapes[shape](this.phase, skew) + dcoffset) * depth;
|
||||||
|
modval = Math.pow(modval, curve);
|
||||||
|
output[i][n] = clamp(modval, min, max);
|
||||||
}
|
}
|
||||||
this.incrementPhase(dt);
|
this.incrementPhase(dt);
|
||||||
}
|
}
|
||||||
@@ -442,7 +443,7 @@ class DistortProcessor extends AudioWorkletProcessor {
|
|||||||
this.started = hasInput;
|
this.started = hasInput;
|
||||||
for (let n = 0; n < blockSize; n++) {
|
for (let n = 0; n < blockSize; n++) {
|
||||||
const postgain = clamp(pv(parameters.postgain, n), 0.001, 1);
|
const postgain = clamp(pv(parameters.postgain, n), 0.001, 1);
|
||||||
const shape = fastexpm1(pv(parameters.distort, n));
|
const shape = Math.expm1(pv(parameters.distort, n));
|
||||||
for (let ch = 0; ch < input.length; ch++) {
|
for (let ch = 0; ch < input.length; ch++) {
|
||||||
const x = input[ch][n];
|
const x = input[ch][n];
|
||||||
output[ch][n] = postgain * this.algorithm(x, shape);
|
output[ch][n] = postgain * this.algorithm(x, shape);
|
||||||
@@ -455,17 +456,9 @@ registerProcessor('distort-processor', DistortProcessor);
|
|||||||
|
|
||||||
// SUPERSAW
|
// SUPERSAW
|
||||||
class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
||||||
constructor(options) {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.phases = new Float32Array(MAX_VOICES);
|
this.phase = [];
|
||||||
this.reset(options.processorOptions);
|
|
||||||
}
|
|
||||||
reset(options) {
|
|
||||||
const phases = this.phases;
|
|
||||||
for (let i = 0; i < phases.length; i++) {
|
|
||||||
phases[i] = Math.random();
|
|
||||||
}
|
|
||||||
this.voices = options.voices;
|
|
||||||
}
|
}
|
||||||
static get parameterDescriptors() {
|
static get parameterDescriptors() {
|
||||||
return [
|
return [
|
||||||
@@ -475,17 +468,20 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
max: Number.POSITIVE_INFINITY,
|
max: Number.POSITIVE_INFINITY,
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'end',
|
name: 'end',
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
max: Number.POSITIVE_INFINITY,
|
max: Number.POSITIVE_INFINITY,
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'frequency',
|
name: 'frequency',
|
||||||
defaultValue: 440,
|
defaultValue: 440,
|
||||||
min: Number.EPSILON,
|
min: Number.EPSILON,
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
name: 'panspread',
|
name: 'panspread',
|
||||||
defaultValue: 0.4,
|
defaultValue: 0.4,
|
||||||
@@ -502,53 +498,50 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
min: 0,
|
min: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'voices',
|
||||||
|
defaultValue: 5,
|
||||||
|
min: 1,
|
||||||
|
automationRate: 'k-rate',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
process(_input, outputs, params) {
|
process(_input, outputs, params) {
|
||||||
const started = currentTime >= params.begin[0];
|
if (currentTime <= params.begin[0]) {
|
||||||
const ended = currentTime >= params.end[0];
|
|
||||||
if (!started || ended) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (currentTime >= params.end[0]) {
|
||||||
|
// this.port.postMessage({ type: 'onended' });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
// fast k-rate paths
|
const voices = params.voices[0]; // k-rate
|
||||||
let freq, detune, freqspread, detuner;
|
|
||||||
if (params.freqspread.length === 1) {
|
|
||||||
freqspread = params.freqspread[0];
|
|
||||||
detuner = getDetuner(this.voices, freqspread);
|
|
||||||
}
|
|
||||||
if (params.frequency.length === 1 && params.detune.length === 1) {
|
|
||||||
freq = params.frequency[0];
|
|
||||||
detune = params.detune[0];
|
|
||||||
freq = applySemitoneDetuneToFrequency(freq, detune / 100);
|
|
||||||
}
|
|
||||||
let panspread, gainL, gainR;
|
|
||||||
if (params.panspread.length === 1) {
|
|
||||||
panspread = this.voices > 1 ? clamp(params.panspread[0], 0, 1) : 0;
|
|
||||||
gainL = Math.sqrt(0.5 - 0.5 * panspread);
|
|
||||||
gainR = Math.sqrt(0.5 + 0.5 * panspread);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < output[0].length; i++) {
|
for (let i = 0; i < output[0].length; i++) {
|
||||||
|
const detune = pv(params.detune, i);
|
||||||
|
const freqspread = pv(params.freqspread, i);
|
||||||
const panspread = pv(params.panspread, i) * 0.5 + 0.5;
|
const panspread = pv(params.panspread, i) * 0.5 + 0.5;
|
||||||
gainL = Math.sqrt(1 - panspread);
|
let gainL = Math.sqrt(1 - panspread);
|
||||||
gainR = Math.sqrt(panspread);
|
let gainR = Math.sqrt(panspread);
|
||||||
// Main detuning (a-rate path)
|
let freq = pv(params.frequency, i);
|
||||||
detune ??= pv(params.detune, i);
|
// Main detuning
|
||||||
freqspread ??= pv(params.freqspread, i);
|
freq = applySemitoneDetuneToFrequency(freq, detune / 100);
|
||||||
freq ??= applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100);
|
const detuner = getDetuner(voices, freqspread);
|
||||||
detuner ??= getDetuner(this.voices, freqspread);
|
for (let n = 0; n < voices; n++) {
|
||||||
for (let n = 0; n < this.voices; n++) {
|
|
||||||
// Individual voice detuning
|
// Individual voice detuning
|
||||||
const freqVoice = applyFastDetune(freq, detuner(n));
|
const freqVoice = applySemitoneDetuneToFrequency(freq, detuner(n));
|
||||||
// We must wrap this here because it is passed into sawblep below which
|
// We must wrap this here because it is passed into sawblep below which
|
||||||
// has domain [0, 1]
|
// has domain [0, 1]
|
||||||
const dt = frac(freqVoice * INVSR);
|
const dt = frac(freqVoice * INVSR);
|
||||||
const v = waveshapes.sawblep(this.phases[n], dt);
|
this.phase[n] = this.phase[n] ?? Math.random();
|
||||||
|
const v = waveshapes.sawblep(this.phase[n], dt);
|
||||||
|
|
||||||
output[0][i] += v * gainL;
|
output[0][i] += v * gainL;
|
||||||
output[1][i] += v * gainR;
|
output[1][i] += v * gainR;
|
||||||
let pn = this.phases[n] + dt;
|
|
||||||
|
let pn = this.phase[n] + dt;
|
||||||
if (pn >= 1.0) pn -= 1.0;
|
if (pn >= 1.0) pn -= 1.0;
|
||||||
this.phases[n] = pn;
|
this.phase[n] = pn;
|
||||||
// invert right and left gain
|
// invert right and left gain
|
||||||
const tmp = gainL;
|
const tmp = gainL;
|
||||||
gainL = gainR;
|
gainL = gainR;
|
||||||
@@ -1132,8 +1125,10 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
{ name: 'freqspread', defaultValue: 0.18, min: 0 },
|
{ name: 'freqspread', defaultValue: 0.18, min: 0 },
|
||||||
{ name: 'position', defaultValue: 0, min: 0, max: 1 },
|
{ name: 'position', defaultValue: 0, min: 0, max: 1 },
|
||||||
{ name: 'warp', defaultValue: 0, min: 0, max: 1 },
|
{ name: 'warp', defaultValue: 0, min: 0, max: 1 },
|
||||||
{ name: 'warpMode', defaultValue: 0, automationRate: 'k-rate' },
|
{ name: 'warpMode', defaultValue: 0 },
|
||||||
|
{ name: 'voices', defaultValue: 1, min: 1, automationRate: 'k-rate' },
|
||||||
{ name: 'panspread', defaultValue: 0.7, min: 0, max: 1 },
|
{ name: 'panspread', defaultValue: 0.7, min: 0, max: 1 },
|
||||||
|
{ name: 'phaserand', defaultValue: 0, min: 0, max: 1 },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1141,32 +1136,35 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
super(options);
|
super(options);
|
||||||
this.frameLen = 0;
|
this.frameLen = 0;
|
||||||
this.numFrames = 0;
|
this.numFrames = 0;
|
||||||
this.phases = new Float32Array(MAX_VOICES);
|
this.phase = [];
|
||||||
|
|
||||||
this.port.onmessage = (e) => {
|
this.port.onmessage = (e) => {
|
||||||
const { type, payload } = e.data || {};
|
const { type, payload } = e.data || {};
|
||||||
if (type === 'table') {
|
if (type === 'table') {
|
||||||
const key = payload.key;
|
const key = payload.key;
|
||||||
this.frameLen = payload.frameLen;
|
this.frameLen = payload.frameLen;
|
||||||
if (!tablesCache[key]) {
|
if (!tablesCache[key]) {
|
||||||
tablesCache[key] = payload.frames;
|
const tables = [payload.frames];
|
||||||
|
let table = tables[0];
|
||||||
|
for (let level = 1; level < 1; level++) {
|
||||||
|
const nextLen = table.length >> 1;
|
||||||
|
const nextTable = table.map((frame) => {
|
||||||
|
const avg = new Float32Array(nextLen);
|
||||||
|
for (let i = 0; i < nextLen; i++) {
|
||||||
|
avg[i] = (frame[2 * i] + frame[2 * i + 1]) / 2;
|
||||||
|
}
|
||||||
|
return avg;
|
||||||
|
});
|
||||||
|
tables.push(nextTable);
|
||||||
|
table = nextTable;
|
||||||
|
if (nextLen <= 32) break;
|
||||||
|
}
|
||||||
|
tablesCache[key] = tables;
|
||||||
}
|
}
|
||||||
this.table = tablesCache[key];
|
this.tables = tablesCache[key];
|
||||||
this.numFrames = this.table.length;
|
this.numFrames = this.tables[0].length;
|
||||||
} else if (type === 'reset') {
|
|
||||||
this.reset(payload.processorOptions);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.reset(options.processorOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(options) {
|
|
||||||
const phaseRand = options.phaseRand ?? 1;
|
|
||||||
const phases = this.phases;
|
|
||||||
for (let i = 0; i < phases.length; i++) {
|
|
||||||
phases[i] = Math.random() * phaseRand;
|
|
||||||
}
|
|
||||||
this.voices = options.voices;
|
|
||||||
this.normalizer = 1 / Math.sqrt(this.voices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_mirror(x) {
|
_mirror(x) {
|
||||||
@@ -1311,63 +1309,73 @@ class WavetableOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
return a + (b - a) * frac;
|
return a + (b - a) * frac;
|
||||||
}
|
}
|
||||||
|
|
||||||
process(_inputs, outputs, params) {
|
_chooseMip(dphi) {
|
||||||
const started = currentTime >= params.begin[0];
|
const approxHarm = clamp(dphi, 1e-6, 64);
|
||||||
const ended = currentTime >= params.end[0];
|
let level = 0;
|
||||||
if (!started || ended) {
|
while (level + 1 < (this.tables?.length || 1) && approxHarm < this.tables[level][0].length / 8) {
|
||||||
|
level++;
|
||||||
|
}
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
|
||||||
|
process(_inputs, outputs, parameters) {
|
||||||
|
if (currentTime >= parameters.end[0]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (currentTime <= parameters.begin[0]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
const outL = outputs[0][0];
|
const outL = outputs[0][0];
|
||||||
const outR = outputs[0][1] || outputs[0][0];
|
const outR = outputs[0][1] || outputs[0][0];
|
||||||
// fast k-rate paths
|
if (!this.tables) {
|
||||||
let freq, detune, freqspread, detuner;
|
outL.fill(0);
|
||||||
if (params.freqspread.length === 1) {
|
if (outR !== outL) outR.set(outL);
|
||||||
freqspread = params.freqspread[0];
|
return true;
|
||||||
detuner = getDetuner(this.voices, freqspread);
|
|
||||||
}
|
}
|
||||||
if (params.frequency.length === 1 && params.detune.length === 1) {
|
const voices = parameters.voices[0]; // k-rate
|
||||||
freq = params.frequency[0];
|
|
||||||
detune = params.detune[0];
|
|
||||||
freq = applySemitoneDetuneToFrequency(freq, detune / 100);
|
|
||||||
}
|
|
||||||
let panspread, gainL, gainR;
|
|
||||||
if (params.panspread.length === 1) {
|
|
||||||
panspread = this.voices > 1 ? clamp(params.panspread[0], 0, 1) : 0;
|
|
||||||
gainL = Math.sqrt(0.5 - 0.5 * panspread);
|
|
||||||
gainR = Math.sqrt(0.5 + 0.5 * panspread);
|
|
||||||
}
|
|
||||||
const warpMode = params.warpMode[0];
|
|
||||||
for (let i = 0; i < outL.length; i++) {
|
for (let i = 0; i < outL.length; i++) {
|
||||||
const tablePos = clamp(pv(params.position, i), 0, 1);
|
const detune = pv(parameters.detune, i);
|
||||||
|
const freqspread = pv(parameters.freqspread, i);
|
||||||
|
const tablePos = clamp(pv(parameters.position, i), 0, 1);
|
||||||
const idx = tablePos * (this.numFrames - 1);
|
const idx = tablePos * (this.numFrames - 1);
|
||||||
const fIdx = idx | 0;
|
const fIdx = idx | 0;
|
||||||
const interpT = idx - fIdx;
|
const interpT = idx - fIdx;
|
||||||
const warpAmount = clamp(pv(params.warp, i), 0, 1);
|
const warpAmount = clamp(pv(parameters.warp, i), 0, 1);
|
||||||
panspread ??= this.voices > 1 ? clamp(pv(params.panspread, i), 0, 1) : 0;
|
const warpMode = pv(parameters.warpMode, i);
|
||||||
gainL ??= Math.sqrt(0.5 - 0.5 * panspread);
|
const phaseRand = clamp(pv(parameters.phaserand, i), 0, 1);
|
||||||
gainR ??= Math.sqrt(0.5 + 0.5 * panspread);
|
const panspread = voices > 1 ? clamp(pv(parameters.panspread, i), 0, 1) : 0;
|
||||||
detune ??= pv(params.detune, i);
|
const gain1 = Math.sqrt(0.5 - 0.5 * panspread);
|
||||||
freqspread ??= pv(params.freqspread, i);
|
const gain2 = Math.sqrt(0.5 + 0.5 * panspread);
|
||||||
freq ??= applySemitoneDetuneToFrequency(pv(params.frequency, i), detune / 100); // overall detune
|
let f = pv(parameters.frequency, i);
|
||||||
detuner ??= getDetuner(this.voices, freqspread);
|
f = applySemitoneDetuneToFrequency(f, detune / 100); // overall detune
|
||||||
for (let n = 0; n < this.voices; n++) {
|
const normalizer = 1 / Math.sqrt(voices);
|
||||||
const freqVoice = applyFastDetune(freq, detuner(n)); // voice detune
|
const detuner = getDetuner(voices, freqspread);
|
||||||
const dPhase = freqVoice * INVSR;
|
for (let n = 0; n < voices; n++) {
|
||||||
|
const isOdd = (n & 1) == 1;
|
||||||
|
let gainL = gain1;
|
||||||
|
let gainR = gain2;
|
||||||
|
// invert right and left gain
|
||||||
|
if (isOdd) {
|
||||||
|
gainL = gain2;
|
||||||
|
gainR = gain1;
|
||||||
|
}
|
||||||
|
const fVoice = applySemitoneDetuneToFrequency(f, detuner(n)); // voice detune
|
||||||
|
const dPhase = fVoice * INVSR;
|
||||||
|
const level = this._chooseMip(dPhase);
|
||||||
|
const table = this.tables[level];
|
||||||
|
|
||||||
// warp phase then sample
|
// warp phase then sample
|
||||||
const ph = this._warpPhase(this.phases[n], warpAmount, warpMode);
|
this.phase[n] = this.phase[n] ?? Math.random() * phaseRand;
|
||||||
const s0 = this._sampleFrame(this.table[fIdx], ph);
|
const ph = this._warpPhase(this.phase[n], warpAmount, warpMode);
|
||||||
const s1 = this._sampleFrame(this.table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
const s0 = this._sampleFrame(table[fIdx], ph);
|
||||||
|
const s1 = this._sampleFrame(table[Math.min(this.numFrames - 1, fIdx + 1)], ph);
|
||||||
let s = lerp(s0, s1, interpT);
|
let s = lerp(s0, s1, interpT);
|
||||||
if (warpMode === WarpMode.FLIP && this.phases[n] < warpAmount) {
|
if (warpMode === WarpMode.FLIP && this.phase[n] < warpAmount) {
|
||||||
s = -s;
|
s = -s;
|
||||||
}
|
}
|
||||||
outL[i] += s * gainL * this.normalizer;
|
outL[i] += s * gainL * normalizer;
|
||||||
outR[i] += s * gainR * this.normalizer;
|
outR[i] += s * gainR * normalizer;
|
||||||
this.phases[n] = frac(this.phases[n] + dPhase);
|
this.phase[n] = frac(this.phase[n] + dPhase);
|
||||||
// invert right and left gain
|
|
||||||
const tmp = gainL;
|
|
||||||
gainR = gainL;
|
|
||||||
gainL = tmp;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { Dough } from './dough.mjs';
|
import { Dough } from './dough.mjs';
|
||||||
|
|
||||||
const clamp = (num, min, max) => {
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
if (num < min) return min;
|
|
||||||
if (num > max) return max;
|
|
||||||
return num;
|
|
||||||
};
|
|
||||||
|
|
||||||
class DoughProcessor extends AudioWorkletProcessor {
|
class DoughProcessor extends AudioWorkletProcessor {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ const PI_DIV_SR = Math.PI / SAMPLE_RATE;
|
|||||||
const ISR = 1 / SAMPLE_RATE;
|
const ISR = 1 / SAMPLE_RATE;
|
||||||
|
|
||||||
let gainCurveFunc = (val) => Math.pow(val, 2);
|
let gainCurveFunc = (val) => Math.pow(val, 2);
|
||||||
const clamp = (num, min, max) => {
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
if (num < min) return min;
|
|
||||||
if (num > max) return max;
|
|
||||||
return num;
|
|
||||||
};
|
|
||||||
|
|
||||||
function applyGainCurve(val) {
|
function applyGainCurve(val) {
|
||||||
return gainCurveFunc(val);
|
return gainCurveFunc(val);
|
||||||
|
|||||||
@@ -5,7 +5,16 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
import { Note, Interval, Scale } from '@tonaljs/tonal';
|
||||||
import { register, _mod, logger, isNote, noteToMidi, removeUndefineds, getAccidentalsOffset } from '@strudel/core';
|
import {
|
||||||
|
_mod,
|
||||||
|
errorLogger,
|
||||||
|
getAccidentalsOffset,
|
||||||
|
isNote,
|
||||||
|
logger,
|
||||||
|
noteToMidi,
|
||||||
|
register,
|
||||||
|
removeUndefineds,
|
||||||
|
} from '@strudel/core';
|
||||||
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs';
|
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs';
|
||||||
|
|
||||||
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
|
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
|
||||||
@@ -294,7 +303,7 @@ export const scale = register(
|
|||||||
}
|
}
|
||||||
if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset));
|
if (offset != 0) scaleNote = Note.transpose(scaleNote, Interval.fromSemitones(offset));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger(`[tonal] ${err.message}`, 'error');
|
errorLogger(err, 'tonal');
|
||||||
return; // will be removed
|
return; // will be removed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1391,6 +1391,75 @@ exports[`runs examples > example "bpdecay" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "bpdepthfrequency" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:200 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:500 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:100 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 bpdepthfrequency:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "bpenv" example index 0 1`] = `
|
exports[`runs examples > example "bpenv" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c2 s:sawtooth bandf:500 bpattack:0.5 bpenv:4 ]",
|
"[ 0/1 → 1/4 | note:c2 s:sawtooth bandf:500 bpattack:0.5 bpenv:4 ]",
|
||||||
@@ -4786,6 +4855,75 @@ exports[`runs examples > example "hpdecay" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "hpdepthfrequency" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:200 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:500 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:100 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 hpdepthfrequency:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "hpenv" example index 0 1`] = `
|
exports[`runs examples > example "hpenv" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c2 s:sawtooth hcutoff:500 hpattack:0.5 hpenv:4 ]",
|
"[ 0/1 → 1/4 | note:c2 s:sawtooth hcutoff:500 hpattack:0.5 hpenv:4 ]",
|
||||||
@@ -5969,6 +6107,144 @@ exports[`runs examples > example "lpdecay" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "lpdepth" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpdepth:1 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpdepth:0.5 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpdepth:1.8 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpdepth:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "lpdepthfrequency" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:200 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:500 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:100 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpdepthfrequency:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "lpenv" example index 0 1`] = `
|
exports[`runs examples > example "lpenv" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c2 s:sawtooth cutoff:300 lpattack:0.5 lpenv:4 ]",
|
"[ 0/1 → 1/4 | note:c2 s:sawtooth cutoff:300 lpattack:0.5 lpenv:4 ]",
|
||||||
@@ -6157,6 +6433,75 @@ exports[`runs examples > example "lpq" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "lprate" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lprate:4 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lprate:8 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lprate:2 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lprate:1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "lprelease" example index 0 1`] = `
|
exports[`runs examples > example "lprelease" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:c2 s:sawtooth clip:0.5 cutoff:300 lpenv:4 lprelease:0.5 release:0.5 ]",
|
"[ 0/1 → 1/4 | note:c2 s:sawtooth clip:0.5 cutoff:300 lpenv:4 lprelease:0.5 release:0.5 ]",
|
||||||
@@ -6199,6 +6544,75 @@ exports[`runs examples > example "lpsustain" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "lpsync" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:c4 s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:c# s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:c s:sawtooth cutoff:600 lpsync:4 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:c# s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:c4 s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:c s:sawtooth cutoff:600 lpsync:8 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:c# s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:c s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:c4 s:sawtooth cutoff:600 lpsync:2 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:c4 s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:c# s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:c s:sawtooth cutoff:600 lpsync:1 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "lrate" example index 0 1`] = `
|
exports[`runs examples > example "lrate" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:1 lrate:1 ]",
|
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:1 lrate:1 ]",
|
||||||
|
|||||||
Reference in New Issue
Block a user