Compare commits

..

2 Commits

Author SHA1 Message Date
Felix Roos 50f04e8cc8 fix: echoWith / echoForEach in tests 2025-09-15 22:57:18 +02:00
Felix Roos 5e3218a67b breaking: tidal style echoWith + add echoForEach 2025-09-15 22:41:51 +02:00
17 changed files with 282 additions and 675 deletions
+23 -74
View File
@@ -114,7 +114,7 @@ export const { n } = registerControl('n');
* *
* - a letter (a-g or A-G) * - a letter (a-g or A-G)
* - optional accidentals (b or #) * - optional accidentals (b or #)
* - optional (possibly negative) octave number (0-9). Defaults to 3 * - optional octave number (0-9). Defaults to 3
* *
* Examples of valid note names: `c`, `bb`, `Bb`, `f#`, `c3`, `A4`, `Eb2`, `c#5` * Examples of valid note names: `c`, `bb`, `Bb`, `f#`, `c3`, `A4`, `Eb2`, `c#5`
* *
@@ -127,8 +127,6 @@ export const { n } = registerControl('n');
* note("c4 a4 f4 e4") * note("c4 a4 f4 e4")
* @example * @example
* note("60 69 65 64") * note("60 69 65 64")
* @example
* note("fbb1 a#0 cbbb-1 e##-2").sound("saw")
*/ */
export const { note } = registerControl(['note', 'n']); export const { note } = registerControl(['note', 'n']);
@@ -144,8 +142,8 @@ export const { note } = registerControl(['note', 'n']);
*/ */
export const { accelerate } = registerControl('accelerate'); export const { accelerate } = registerControl('accelerate');
/** /**
* Sets the velocity from 0 to 1. Is multiplied together with gain.
* *
* Sets the velocity from 0 to 1. Is multiplied together with gain.
* @name velocity * @name velocity
* @example * @example
* s("hh*8") * s("hh*8")
@@ -257,7 +255,7 @@ export const { fmenv } = registerControl('fmenv');
export const { fmattack } = registerControl('fmattack'); export const { fmattack } = registerControl('fmattack');
/** /**
* Waveform of the fm modulator * waveform of the fm modulator
* *
* @name fmwave * @name fmwave
* @param {number | Pattern} wave waveform * @param {number | Pattern} wave waveform
@@ -391,7 +389,7 @@ export const { bandf, bpf, bp } = registerControl(['bandf', 'bandq', 'bpenv'], '
// ['bpq'], // ['bpq'],
export const { bandq, bpq } = registerControl('bandq', 'bpq'); export const { bandq, bpq } = registerControl('bandq', 'bpq');
/** /**
* A pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample. * a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
* *
* @memberof Pattern * @memberof Pattern
* @name begin * @name begin
@@ -452,7 +450,7 @@ export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb');
*/ */
export const { loopEnd, loope } = registerControl('loopEnd', 'loope'); export const { loopEnd, loope } = registerControl('loopEnd', 'loope');
/** /**
* Bit crusher effect. * bit crusher effect.
* *
* @name crush * @name crush
* @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction). * @param {number | Pattern} depth between 1 (for drastic reduction in bit-depth) to 16 (for barely no reduction).
@@ -463,7 +461,7 @@ export const { loopEnd, loope } = registerControl('loopEnd', 'loope');
// ['clhatdecay'], // ['clhatdecay'],
export const { crush } = registerControl('crush'); export const { crush } = registerControl('crush');
/** /**
* Fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers * fake-resampling for lowering the sample rate. Caution: This effect seems to only work in chromium based browsers
* *
* @name coarse * @name coarse
* @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on. * @param {number | Pattern} factor 1 for original 2 for half, 3 for a third and so on.
@@ -474,7 +472,7 @@ export const { crush } = registerControl('crush');
export const { coarse } = registerControl('coarse'); export const { coarse } = registerControl('coarse');
/** /**
* Modulate the amplitude of a sound with a continuous waveform * modulate the amplitude of a sound with a continuous waveform
* *
* @name tremolo * @name tremolo
* @synonyms trem * @synonyms trem
@@ -486,7 +484,7 @@ export const { coarse } = registerControl('coarse');
export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremoloskew', 'tremolophase'], 'trem'); export const { tremolo } = registerControl(['tremolo', 'tremolodepth', 'tremoloskew', 'tremolophase'], 'trem');
/** /**
* Modulate the amplitude of a sound with a continuous waveform * modulate the amplitude of a sound with a continuous waveform
* *
* @name tremolosync * @name tremolosync
* @synonyms tremsync * @synonyms tremsync
@@ -501,7 +499,7 @@ export const { tremolosync } = registerControl(
); );
/** /**
* Depth of amplitude modulation * depth of amplitude modulation
* *
* @name tremolodepth * @name tremolodepth
* @synonyms tremdepth * @synonyms tremdepth
@@ -512,7 +510,7 @@ export const { tremolosync } = registerControl(
*/ */
export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth'); export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth');
/** /**
* Alter the shape of the modulation waveform * alter the shape of the modulation waveform
* *
* @name tremoloskew * @name tremoloskew
* @synonyms tremskew * @synonyms tremskew
@@ -524,7 +522,7 @@ export const { tremolodepth } = registerControl('tremolodepth', 'tremdepth');
export const { tremoloskew } = registerControl('tremoloskew', 'tremskew'); export const { tremoloskew } = registerControl('tremoloskew', 'tremskew');
/** /**
* Alter the phase of the modulation waveform * alter the phase of the modulation waveform
* *
* @name tremolophase * @name tremolophase
* @synonyms tremphase * @synonyms tremphase
@@ -536,7 +534,7 @@ export const { tremoloskew } = registerControl('tremoloskew', 'tremskew');
export const { tremolophase } = registerControl('tremolophase', 'tremphase'); export const { tremolophase } = registerControl('tremolophase', 'tremphase');
/** /**
* Shape of amplitude modulation * shape of amplitude modulation
* *
* @name tremoloshape * @name tremoloshape
* @synonyms tremshape * @synonyms tremshape
@@ -547,7 +545,7 @@ export const { tremolophase } = registerControl('tremolophase', 'tremphase');
*/ */
export const { tremoloshape } = registerControl('tremoloshape', 'tremshape'); export const { tremoloshape } = registerControl('tremoloshape', 'tremshape');
/** /**
* Filter overdrive for supported filter types * filter overdrive for supported filter types
* *
* @name drive * @name drive
* @param {number | Pattern} amount * @param {number | Pattern} amount
@@ -558,9 +556,7 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape');
export const { drive } = registerControl('drive'); export const { drive } = registerControl('drive');
/** /**
* Modulate the amplitude of an orbit to create a "sidechain" like effect. * modulate the amplitude of an orbit to create a "sidechain" like effect
*
* Can be applied to multiple orbits with the ':' mininotation, e.g. `duckorbit("2:3")`
* *
* @name duckorbit * @name duckorbit
* @synonyms duck * @synonyms duck
@@ -568,77 +564,30 @@ export const { drive } = registerControl('drive');
* @example * @example
* $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2) * $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2)
* $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth(1) * $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth(1)
* @example
* $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2)
* $: s("hh*16").orbit(3)
* $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth(1)
* *
*/ */
export const { duck } = registerControl('duckorbit', 'duck'); export const { duck } = registerControl('duckorbit', 'duck');
/** /**
* The amount of ducking applied to target orbit * the amount of ducking applied to target orbit
*
* Can vary across orbits with the ':' mininotation, e.g. `duckdepth("0.3:0.1")`.
* Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`.
* *
* @name duckdepth * @name duckdepth
* @param {number | Pattern} depth depth of modulation from 0 to 1 * @param {number | Pattern} depth depth of modulation from 0 to 1
* @example * @example
* stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>")) * stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth("<1 .9 .6 0>"))
* @example
* $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2)
* $: s("hh*16").orbit(3)
* $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:3").duckattack(0.2).duckdepth("1:0.5")
* *
*/ */
export const { duckdepth } = registerControl('duckdepth'); export const { duckdepth } = registerControl('duckdepth');
/** /**
* The time required for the ducked signal(s) to reach their lowest volume. * the attack time of the duck effect
* Can be used to prevent clicking or for creative rhythmic effects.
*
* Can vary across orbits with the ':' mininotation, e.g. `duckonset("0:0.003")`.
* Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`.
*
* @name duckonset
* @synonyms duckons
*
* @param {number | Pattern} time The onset time in seconds
* @example
* // Clicks
* sound: freq("63.2388").s("sine").orbit(2).gain(4)
* duckerWithClick: s("bd*4").duckorbit(2).duckattack(0.3).duckonset(0).postgain(0)
* @example
* // No clicks
* sound: freq("63.2388").s("sine").orbit(2).gain(4)
* duckerWithoutClick: s("bd*4").duckorbit(2).duckattack(0.3).duckonset(0.01).postgain(0)
* @example
* // Rhythmic
* noise: s("pink").distort("2:1").orbit(4) // used rhythmically with 0.3 onset below
* hhat: s("hh*16").orbit(7)
* ducker: s("bd*4").bank("tr909").duckorbit("4:7").duckonset("0.3:0.003").duckattack(0.25)
*
*/
export const { duckonset } = registerControl('duckonset', 'duckons');
/**
* The time required for the ducked signal(s) to return to their normal volume.
*
* Can vary across orbits with the ':' mininotation, e.g. `duckonset("0:0.003")`.
* Note: this requires first applying the effect to multiple orbits with e.g. `duckorbit("2:3")`.
* *
* @name duckattack * @name duckattack
* @synonyms duckatt * @synonyms duckatt
* * @param {number | Pattern} time
* @param {number | Pattern} time The attack time in seconds
* @example * @example
* sound: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2) * stack( n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2), s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack("<0.2 0 0.4>").duckdepth(1))
* ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack("<0.2 0 0.4>").duckdepth(1)
* @example
* moreduck: n(run(8)).scale("c:minor").s("sawtooth").delay(.7).orbit(2)
* lessduck: s("hh*16").orbit(5)
* ducker: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit("2:5").duckattack("0.4:0.1")
* *
*/ */
export const { duckattack } = registerControl('duckattack', 'duckatt'); export const { duckattack } = registerControl('duckattack', 'duckatt');
@@ -683,7 +632,7 @@ export const { byteBeatStartTime, bbst } = registerControl('byteBeatStartTime',
export const { channels, ch } = registerControl('channels', 'ch'); export const { channels, ch } = registerControl('channels', 'ch');
/** /**
* Controls the pulsewidth of the pulse oscillator * controls the pulsewidth of the pulse oscillator
* *
* @name pw * @name pw
* @param {number | Pattern} pulsewidth * @param {number | Pattern} pulsewidth
@@ -695,7 +644,7 @@ export const { channels, ch } = registerControl('channels', 'ch');
export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']); export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']);
/** /**
* Controls the lfo rate for the pulsewidth of the pulse oscillator * controls the lfo rate for the pulsewidth of the pulse oscillator
* *
* @name pwrate * @name pwrate
* @param {number | Pattern} rate * @param {number | Pattern} rate
@@ -707,7 +656,7 @@ export const { pw } = registerControl(['pw', 'pwrate', 'pwsweep']);
export const { pwrate } = registerControl('pwrate'); export const { pwrate } = registerControl('pwrate');
/** /**
* Controls the lfo sweep for the pulsewidth of the pulse oscillator * controls the lfo sweep for the pulsewidth of the pulse oscillator
* *
* @name pwsweep * @name pwsweep
* @param {number | Pattern} sweep * @param {number | Pattern} sweep
@@ -776,7 +725,7 @@ export const { phasercenter, phc } = registerControl('phasercenter', 'phc');
export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd', 'phasdp'); export const { phaserdepth, phd, phasdp } = registerControl('phaserdepth', 'phd', 'phasdp');
/** /**
* Choose the channel the pattern is sent to in superdirt * choose the channel the pattern is sent to in superdirt
* *
* @name channel * @name channel
* @param {number | Pattern} channel channel number * @param {number | Pattern} channel channel number
+2 -3
View File
@@ -5,9 +5,8 @@ let debounce = 1000,
lastTime; lastTime;
export function errorLogger(e, origin = 'cyclist') { export function errorLogger(e, origin = 'cyclist') {
if (process.env.NODE_ENV === 'development') { //TODO: add some kind of debug flag that enables this while in dev mode
console.error(e); // console.error(e);
}
logger(`[${origin}] error: ${e.message}`); logger(`[${origin}] error: ${e.message}`);
} }
+28 -6
View File
@@ -2354,20 +2354,42 @@ export const jux = register('jux', function (func, pat) {
/** /**
* Superimpose and offset multiple times, applying the given function each time. * Superimpose and offset multiple times, applying the given function each time.
* @name echoWith * @name echoForEach
* @synonyms echowith, stutWith, stutwith
* @param {number} times how many times to repeat * @param {number} times how many times to repeat
* @param {number} time cycle offset between iterations * @param {number} time cycle offset between iterations
* @param {function} func function to apply, given the pattern and the iteration index * @param {function} func function to apply, given the pattern and the iteration index
* @example * @example
* "<0 [2 4]>" * "<0 [2 4]>"
* .echoWith(4, 1/8, (p,n) => p.add(n*2)) * .echoWith(4, 1/8, (p,n) => p.add(n*2))
* .scale("C:minor").note() * .n().scale("C:minor")
*/
export const { echoForEach } = register(['echoForEach'], function (times, time, func, pat) {
return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i)));
});
/**
* Superimpose and offset multiple times, applying the given function each time.
* @name echoWith
* @synonyms echowith, stutWith, stutwith
* @param {number} times how many times to repeat
* @param {number} time cycle offset between iterations
* @param {function} func function to apply, given the pattern and the iteration index
* @example
* "<0 [2 4]>".echoWith(4, 1/8, add(2))
* .n().scale("C:minor")
*/ */
export const { echoWith, echowith, stutWith, stutwith } = register( export const { echoWith, echowith, stutWith, stutwith } = register(
['echoWith', 'echowith', 'stutWith', 'stutwith'], ['echoWith', 'echowith', 'stutWith', 'stutwith'],
function (times, time, func, pat) { function (times, time, func, pat) {
return stack(...listRange(0, times - 1).map((i) => func(pat.late(Fraction(time).mul(i)), i))); if (times < 1) {
return silence;
}
let pats = pat;
for (let i = 1; i < times; i++) {
pat = func(pat.late(time));
pats = pats.stack(pat);
}
return pats;
}, },
); );
@@ -2383,7 +2405,7 @@ export const { echoWith, echowith, stutWith, stutwith } = register(
* s("bd sd").echo(3, 1/6, .8) * s("bd sd").echo(3, 1/6, .8)
*/ */
export const echo = register('echo', function (times, time, feedback, pat) { export const echo = register('echo', function (times, time, feedback, pat) {
return pat._echoWith(times, time, (pat, i) => pat.gain(Math.pow(feedback, i))); return pat._echoForEach(times, time, (pat, i) => pat.gain(Math.pow(feedback, i)));
}); });
/** /**
@@ -2396,7 +2418,7 @@ export const echo = register('echo', function (times, time, feedback, pat) {
* s("bd sd").stut(3, .8, 1/6) * s("bd sd").stut(3, .8, 1/6)
*/ */
export const stut = register('stut', function (times, feedback, time, pat) { export const stut = register('stut', function (times, feedback, time, pat) {
return pat._echoWith(times, time, (pat, i) => pat.gain(Math.pow(feedback, i))); return pat._echoForEach(times, time, (pat, i) => pat.gain(Math.pow(feedback, i)));
}); });
export const applyN = register('applyN', function (n, func, p) { export const applyN = register('applyN', function (n, func, p) {
+2 -2
View File
@@ -8,12 +8,12 @@ import { logger } from './logger.mjs';
// returns true if the given string is a note // returns true if the given string is a note
export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name); export const isNoteWithOctave = (name) => /^[a-gA-G][#bs]*[0-9]$/.test(name);
export const isNote = (name) => /^[a-gA-G][#bsf]*-?[0-9]?$/.test(name); export const isNote = (name) => /^[a-gA-G][#bsf]*[0-9]?$/.test(name);
export const tokenizeNote = (note) => { export const tokenizeNote = (note) => {
if (typeof note !== 'string') { if (typeof note !== 'string') {
return []; return [];
} }
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)(-?[0-9]*)$/)?.slice(1) || []; const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([0-9]*)$/)?.slice(1) || [];
if (!pc) { if (!pc) {
return []; return [];
} }
+3 -4
View File
@@ -1,9 +1,8 @@
let log = (msg) => console.log(msg); let log = (msg) => console.log(msg);
export function errorLogger(e, origin = 'superdough') { export function errorLogger(e, origin = 'cyclist') {
if (process.env.NODE_ENV === 'development') { //TODO: add some kind of debug flag that enables this while in dev mode
console.error(e); // console.error(e);
}
logger(`[${origin}] error: ${e.message}`); logger(`[${origin}] error: ${e.message}`);
} }
+38 -57
View File
@@ -349,17 +349,18 @@ function getDelay(orbit, delaytime, delayfeedback, t) {
//logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`); //logger(`delayfeedback was clamped to ${maxfeedback} to save your ears`);
} }
delayfeedback = clamp(delayfeedback, 0, 0.98); delayfeedback = clamp(delayfeedback, 0, 0.98);
let delayNode = orbits[orbit].delayNode; if (!orbits[orbit].delayNode) {
if (delayNode === undefined) {
const ac = getAudioContext(); const ac = getAudioContext();
delayNode = ac.createFeedbackDelay(1, delaytime, delayfeedback); const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
delayNode.start?.(t); // for some reason, this throws when audion extension is installed.. dly.start?.(t); // for some reason, this throws when audion extension is installed..
connectToOrbit(delayNode, orbit); connectToOrbit(dly, orbit);
orbits[orbit].delayNode = delayNode; orbits[orbit].delayNode = dly;
} }
delayNode.delayTime.value !== delaytime && delayNode.delayTime.setValueAtTime(delaytime, t); orbits[orbit].delayNode.delayTime.value !== delaytime &&
delayNode.feedback.value !== delayfeedback && delayNode.feedback.setValueAtTime(delayfeedback, t); orbits[orbit].delayNode.delayTime.setValueAtTime(delaytime, t);
return delayNode; orbits[orbit].delayNode.feedback.value !== delayfeedback &&
orbits[orbit].delayNode.feedback.setValueAtTime(delayfeedback, t);
return orbits[orbit].delayNode;
} }
export function getLfo(audioContext, begin, end, properties = {}) { export function getLfo(audioContext, begin, end, properties = {}) {
@@ -415,60 +416,42 @@ function getFilterType(ftype) {
return typeof ftype === 'number' ? filterTypes[Math.floor(_mod(ftype, filterTypes.length))] : ftype; return typeof ftype === 'number' ? filterTypes[Math.floor(_mod(ftype, filterTypes.length))] : ftype;
} }
// type orbit { //type orbit {
// output: GainNode, // gain: number,
// reverbNode: ConvolverNode // reverbNode: reverbNode
// delayNode: FeedbackDelayNode // delayNode: delayNode
// } //}
let orbits = {}; let orbits = {};
function connectToOrbit(node, orbit) { function connectToOrbit(node, orbit) {
if (orbits[orbit] == null) { if (orbits[orbit] == null) {
errorLogger(new Error('target orbit does not exist'), 'superdough'); errorLogger(new Error('target orbit does not exist'), 'superdough');
} }
node.connect(orbits[orbit].output); node.connect(orbits[orbit].gain);
} }
function setOrbit(audioContext, orbit, channels) { function setOrbit(audioContext, orbit, channels) {
if (orbits[orbit] == null) { if (orbits[orbit] == null) {
orbits[orbit] = { orbits[orbit] = {
// Setup output node through which all audio filters prior to hitting gain: new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }),
// the destination (and thus allows for global volume automation)
output: new GainNode(audioContext, { gain: 1, channelCount: 2, channelCountMode: 'explicit' }),
}; };
connectToDestination(orbits[orbit].output, channels); connectToDestination(orbits[orbit].gain, channels);
} }
} }
function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.1, duckdepth = 1) {
function duckOrbit(audioContext, targetOrbit, t, onsettime = 0, attacktime = 0.1, duckdepth = 1) {
const targetArr = [targetOrbit].flat(); const targetArr = [targetOrbit].flat();
const onsetArr = [onsettime].flat();
const attackArr = [attacktime].flat();
const depthArr = [duckdepth].flat();
targetArr.forEach((target, idx) => { targetArr.forEach((target) => {
if (orbits[target] == null) { if (orbits[target] == null) {
errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough'); errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough');
return; return;
} }
const onset = onsetArr[idx] ?? onsetArr[0];
const attack = Math.max(attackArr[idx] ?? attackArr[0], 0.002);
const depth = depthArr[idx] ?? depthArr[0];
const gainParam = orbits[target].output.gain;
webAudioTimeout( webAudioTimeout(
audioContext, audioContext,
() => { () => {
const now = audioContext.currentTime; orbits[target].gain.gain.cancelScheduledValues(t);
const currVal = orbits[target].gain.gain.value;
// cancelScheduledValues and setValueAtTime together emulate cancelAndHoldAtTime orbits[target].gain.gain.linearRampToValueAtTime(clamp(1 - Math.pow(duckdepth, 0.5), 0.01, currVal), t);
// on browsers which lack that method orbits[target].gain.gain.exponentialRampToValueAtTime(1, t + Math.max(0.002, attacktime));
const currVal = gainParam.value;
gainParam.cancelScheduledValues(now);
gainParam.setValueAtTime(currVal, now);
const t0 = Math.max(t, now); // guard against now > t
const duckedVal = clamp(1 - Math.sqrt(depth), 0.01, currVal);
gainParam.exponentialRampToValueAtTime(duckedVal, t0 + onset);
gainParam.exponentialRampToValueAtTime(1, t0 + onset + attack);
}, },
0, 0,
t - 0.01, t - 0.01,
@@ -479,31 +462,30 @@ function duckOrbit(audioContext, targetOrbit, t, onsettime = 0, attacktime = 0.1
let hasChanged = (now, before) => now !== undefined && now !== before; let hasChanged = (now, before) => now !== undefined && now !== before;
function getReverb(orbit, duration, fade, lp, dim, ir, irspeed, irbegin) { function getReverb(orbit, duration, fade, lp, dim, ir, irspeed, irbegin) {
// If no reverb has been created for a given orbit, create one // If no reverb has been created for a given orbit, create one
let reverbNode = orbits[orbit].reverbNode; if (!orbits[orbit].reverbNode) {
if (reverbNode === undefined) {
const ac = getAudioContext(); const ac = getAudioContext();
reverbNode = ac.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin); const reverb = ac.createReverb(duration, fade, lp, dim, ir, irspeed, irbegin);
connectToOrbit(reverbNode, orbit); connectToOrbit(reverb, orbit);
orbits[orbit].reverbNode = reverbNode; orbits[orbit].reverbNode = reverb;
} }
if ( if (
hasChanged(duration, reverbNode.duration) || hasChanged(duration, orbits[orbit].reverbNode.duration) ||
hasChanged(fade, reverbNode.fade) || hasChanged(fade, orbits[orbit].reverbNode.fade) ||
hasChanged(lp, reverbNode.lp) || hasChanged(lp, orbits[orbit].reverbNode.lp) ||
hasChanged(dim, reverbNode.dim) || hasChanged(dim, orbits[orbit].reverbNode.dim) ||
hasChanged(irspeed, reverbNode.irspeed) || hasChanged(irspeed, orbits[orbit].reverbNode.irspeed) ||
hasChanged(irbegin, reverbNode.irbegin) || hasChanged(irbegin, orbits[orbit].reverbNode.irbegin) ||
reverbNode.ir !== ir orbits[orbit].reverbNode.ir !== ir
) { ) {
// only regenerate when something has changed // only regenerate when something has changed
// avoids endless regeneration on things like // avoids endless regeneration on things like
// stack(s("a"), s("b").rsize(8)).room(.5) // stack(s("a"), s("b").rsize(8)).room(.5)
// this only works when args may stay undefined until here // this only works when args may stay undefined until here
// setting default values breaks this // setting default values breaks this
reverbNode.generate(duration, fade, lp, dim, ir, irspeed, irbegin); orbits[orbit].reverbNode.generate(duration, fade, lp, dim, ir, irspeed, irbegin);
} }
return reverbNode; return orbits[orbit].reverbNode;
} }
export let analysers = {}, export let analysers = {},
@@ -600,7 +582,6 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
postgain = getDefaultValue('postgain'), postgain = getDefaultValue('postgain'),
density = getDefaultValue('density'), density = getDefaultValue('density'),
duckorbit, duckorbit,
duckonset,
duckattack, duckattack,
duckdepth, duckdepth,
// filters // filters
@@ -682,7 +663,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
setOrbit(ac, orbit, channels, t, cycle, cps); setOrbit(ac, orbit, channels, t, cycle, cps);
if (duckorbit != null) { if (duckorbit != null) {
duckOrbit(ac, duckorbit, t, duckonset, duckattack, duckdepth); duckOrbit(ac, duckorbit, t, duckattack, duckdepth);
} }
gain = applyGainCurve(nanFallback(gain, 1)); gain = applyGainCurve(nanFallback(gain, 1));
+1 -1
View File
@@ -7,7 +7,7 @@ export const tokenizeNote = (note) => {
if (typeof note !== 'string') { if (typeof note !== 'string') {
return []; return [];
} }
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)(-?[0-9]*)$/)?.slice(1) || []; const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bsf]*)([0-9]*)$/)?.slice(1) || [];
if (!pc) { if (!pc) {
return []; return [];
} }
+7 -57
View File
@@ -7,18 +7,15 @@ This program is free software: you can redistribute it and/or modify it under th
// import { strict as assert } from 'assert'; // import { strict as assert } from 'assert';
import '../tonal.mjs'; // need to import this to add prototypes import '../tonal.mjs'; // need to import this to add prototypes
import { pure, n, seq, note, noteToMidi } from '@strudel/core'; import { pure, n, seq, note } from '@strudel/core';
import { describe, it, expect } from 'vitest'; import { describe, it, expect } from 'vitest';
import { mini } from '../../mini/mini.mjs'; import { mini } from '../../mini/mini.mjs';
describe('tonal', () => { describe('tonal', () => {
describe('scaleTranspose', () => { it('Should run tonal functions ', () => {
it('transposes notes by scale degrees', () => {
expect(pure('c3').scale('C major').scaleTranspose(1).firstCycleValues).toEqual(['D3']); expect(pure('c3').scale('C major').scaleTranspose(1).firstCycleValues).toEqual(['D3']);
}); });
}); it('scale with plain values', () => {
describe('scale', () => {
it('converts plain values', () => {
expect( expect(
seq(0, 1, 2) seq(0, 1, 2)
.scale('C major') .scale('C major')
@@ -26,80 +23,34 @@ describe('tonal', () => {
.firstCycleValues.map((h) => h.note), .firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']); ).toEqual(['C3', 'D3', 'E3']);
}); });
it('converts n values', () => { it('scale with n values', () => {
expect( expect(
n(seq(0, 1, 2)) n(seq(0, 1, 2))
.scale('C major') .scale('C major')
.firstCycleValues.map((h) => h.note), .firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']); ).toEqual(['C3', 'D3', 'E3']);
}); });
it('converts n values (mini notation)', () => { it('scale with colon', () => {
expect( expect(
n(seq(0, 1, 2)) n(seq(0, 1, 2))
.scale('C:major') .scale('C:major')
.firstCycleValues.map((h) => h.note), .firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']); ).toEqual(['C3', 'D3', 'E3']);
}); });
it('converts n values (no tonic)', () => { it('scale without tonic', () => {
expect( expect(
n(seq(0, 1, 2)) n(seq(0, 1, 2))
.scale('major') .scale('major')
.firstCycleValues.map((h) => h.note), .firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']); ).toEqual(['C3', 'D3', 'E3']);
}); });
it('converts n values (explicit mini notation)', () => { it('scale with mininotation colon', () => {
expect( expect(
n(seq(0, 1, 2)) n(seq(0, 1, 2))
.scale(mini('C:major')) .scale(mini('C:major'))
.firstCycleValues.map((h) => h.note), .firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']); ).toEqual(['C3', 'D3', 'E3']);
}); });
it('converts decorated n values', () => {
expect(
n(seq('0b', '1#', '-2', '3##', '4bb'))
.scale('C major')
.firstCycleValues.map((h) => h.note),
).toEqual(['B2', 'Eb3', 'A2', 'G3', 'F3']);
});
it('produces silence for mixed sharps and flats', () => {
expect(
n(seq('0b#', '1#b', '2#b#'))
.scale('C major')
.firstCycleValues.map((h) => h.note),
).toEqual([]);
});
it('snaps notes (upwards) to scale', () => {
const inputNotes = ['Cb', 'Eb', 'G', 'A#', 'Bb'];
const expectedNotes = ['B2', 'E3', 'G3', 'B3', 'B3'];
expect(
note(seq(inputNotes))
.scale('C major')
.firstCycleValues.map((h) => h.note),
).toEqual(expectedNotes);
});
it('snaps notes to the correct octave', () => {
const inputNotes = ['Cb0', 'Eb4', 'G1', 'A#19', 'Bb8'];
const expectedNotes = ['B#-1', 'D#4', 'G#1', 'A#19', 'A#8'];
expect(
note(seq(inputNotes))
.scale('A# minor') // A#, B#, C#, D#, E#, F#, G#
.firstCycleValues.map((h) => h.note),
).toEqual(expectedNotes);
});
it('handles scale names provided with colons', () => {
const inputNotes = ['Cb', 'E', 'G', 'A#', 'Bb'];
const expectedNotes = ['A#2', 'D#3', 'G#3', 'A#3', 'A#3'];
expect(
note(seq(inputNotes))
.scale('F#:pentatonic') // F#, G#, A#, C#, and D#
.firstCycleValues.map((h) => h.note),
).toEqual(expectedNotes);
});
});
describe('transpose', () => {
it('transposes note numbers with interval numbers', () => { it('transposes note numbers with interval numbers', () => {
expect( expect(
note(seq(40, 40, 40)) note(seq(40, 40, 40))
@@ -132,5 +83,4 @@ describe('tonal', () => {
).toEqual(['C', 'D', 'Eb']); ).toEqual(['C', 'D', 'Eb']);
expect(seq('c', 'c', 'c').transpose('1P', '2M', '3m').firstCycleValues).toEqual(['C', 'D', 'Eb']); expect(seq('c', 'c', 'c').transpose('1P', '2M', '3m').firstCycleValues).toEqual(['C', 'D', 'Eb']);
}); });
});
}); });
+43 -91
View File
@@ -6,28 +6,19 @@ 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, silence, logger, pure, isNote } from '@strudel/core'; import { register, _mod, silence, logger, pure, isNote } from '@strudel/core';
import { stepInNamedScale, nearestNumberIndex } from './tonleiter.mjs'; import { stepInNamedScale } from './tonleiter.mjs';
import { noteToMidi } from '../core/util.mjs';
const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P'; const octavesInterval = (octaves) => (octaves <= 0 ? -1 : 1) + octaves * 7 + 'P';
function getScale(scaleName) {
scaleName = scaleName.replaceAll(':', ' ');
const scale = Scale.get(scaleName);
const { tonic, empty } = scale;
if ((empty && isNote(scaleName)) || (empty && !tonic)) {
throw new Error(
`Scale name ${scaleName} is incomplete. Make sure to use ":" instead of spaces, example: .scale("C:major")`,
);
} else if (empty) {
throw new Error(`Invalid scale name "${scaleName}"`);
}
return scale;
}
function scaleStep(step, scale) { function scaleStep(step, scale) {
scale = scale.replaceAll(':', ' ');
step = Math.ceil(step); step = Math.ceil(step);
let { intervals, tonic } = getScale(scale); let { intervals, tonic, empty } = Scale.get(scale);
if ((empty && isNote(scale)) || (empty && !tonic)) {
throw new Error(`incomplete scale. Make sure to use ":" instead of spaces, example: .scale("C:major")`);
} else if (empty) {
throw new Error(`invalid scale "${scale}"`);
}
tonic = tonic || 'C'; tonic = tonic || 'C';
const { pc, oct = 3 } = Note.get(tonic); const { pc, oct = 3 } = Note.get(tonic);
const octaveOffset = Math.floor(step / intervals.length); const octaveOffset = Math.floor(step / intervals.length);
@@ -39,7 +30,8 @@ function scaleStep(step, scale) {
// transpose note inside scale by offset steps // transpose note inside scale by offset steps
// function scaleOffset(scale: string, offset: number, note: string) { // function scaleOffset(scale: string, offset: number, note: string) {
function scaleOffset(scale, offset, note) { function scaleOffset(scale, offset, note) {
let { notes } = getScale(scale); let [tonic, scaleName] = Scale.tokenize(scale);
let { notes } = Scale.get(`${tonic} ${scaleName}`);
notes = notes.map((note) => Note.get(note).pc); // use only pc! notes = notes.map((note) => Note.get(note).pc); // use only pc!
offset = Number(offset); offset = Number(offset);
if (isNaN(offset)) { if (isNaN(offset)) {
@@ -128,7 +120,10 @@ export const { transpose, trans } = register(['transpose', 'trans'], function tr
const interval = !isNaN(Number(intervalOrSemitones)) const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones) ? Interval.fromSemitones(intervalOrSemitones)
: String(intervalOrSemitones); : String(intervalOrSemitones);
const targetNote = Note.transpose(note, interval); // TODO: move simplify to player to preserve enharmonics
// tone.js doesn't understand multiple sharps flats e.g. F##3 has to be turned into G3
// TODO: check if this is still relevant..
const targetNote = Note.simplify(Note.transpose(note, interval));
if (typeof hap.value === 'object') { if (typeof hap.value === 'object') {
return hap.withValue(() => ({ ...hap.value, note: targetNote })); return hap.withValue(() => ({ ...hap.value, note: targetNote }));
} }
@@ -176,59 +171,8 @@ export const { scaleTranspose, scaleTrans, strans } = register(
}, },
); );
// Converts a step value, which is a number optionally decorated with sharps and flats,
// to a number and an `offset` number of semitones
function _convertStepToNumberAndOffset(step) {
let asNumber = Number(step);
let offset = 0;
if (isNaN(asNumber)) {
step = String(step);
// Check to see if the step matches the expected format:
// - A number (possibly negative)
// - Some number of sharps or flats (but not both)
const match = /^(-?\d+)(#+|b+)?$/.exec(step);
if (!match) {
throw new Error(`invalid scale step "${step}", expected number or integer with optional # b suffixes`);
}
asNumber = Number(match[1]);
// These decorations will determine the semitone offset based on the number of
// sharps or flats
const decorations = match[2] || '';
offset = decorations[0] === '#' ? decorations.length : -decorations.length;
}
return [asNumber, offset];
}
let scaleToMidisAndNotes = {};
// Finds the nearest scale note to `note`
function _getNearestScaleNote(scaleName, note, preferHigher = true) {
let noteMidi = typeof note === 'string' ? noteToMidi(note) : note;
if (scaleToMidisAndNotes[scaleName] === undefined) {
const { intervals, tonic } = getScale(scaleName);
const { pc } = Note.get(tonic);
const expandedIntervals = intervals.concat('8P'); // add the octave for wrapping
const sNotes = expandedIntervals.map((interval) => Note.transpose(pc + '0', interval));
const sMidi = sNotes.map(noteToMidi);
// Cache
scaleToMidisAndNotes[scaleName] = [sMidi, sNotes];
}
const [scaleMidis, scaleNotes] = scaleToMidisAndNotes[scaleName];
const rootMidi = scaleMidis[0];
const octaveDiff = Math.floor((noteMidi - rootMidi) / 12);
const alignedMidis = scaleMidis.map((m) => m + 12 * octaveDiff);
const noteIdx = nearestNumberIndex(noteMidi, alignedMidis, preferHigher);
const noteMatch = scaleNotes[noteIdx];
return Note.transpose(noteMatch, Interval.fromSemitones(12 * octaveDiff));
}
/** /**
* Turns numbers into notes in the scale (zero indexed) or quantizes notes to a scale. * Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
*
* When describing notes via numbers, note that negative numbers can be used to wrap backwards
* in the scale as well as sharps or flats (but not both) to produce notes outside of the scale.
*
* Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
* *
* A scale consists of a root note (e.g. `c4`, `c`, `f#`, `bb4`) followed by semicolon (':') and then a [scale type](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts). * A scale consists of a root note (e.g. `c4`, `c`, `f#`, `bb4`) followed by semicolon (':') and then a [scale type](https://github.com/tonaljs/tonal/blob/main/packages/scale-type/data.ts).
* *
@@ -247,12 +191,6 @@ function _getNearestScaleNote(scaleName, note, preferHigher = true) {
* n(rand.range(0,12).segment(8)) * n(rand.range(0,12).segment(8))
* .scale("C:ritusen") * .scale("C:ritusen")
* .s("piano") * .s("piano")
* @example
* n("<[0,7b] [-4# -4] [-2,7##] 4 [0,7] [-4# -4b] [-2,7###] 4b>*4")
* .scale("C:<major minor>/2")
* .s("piano")
* @example
* note("C1*16").transpose(irand(36)).scale('Cb2 major').scaleTranspose(3)
*/ */
export const scale = register( export const scale = register(
@@ -266,35 +204,49 @@ export const scale = register(
pat pat
.fmap((value) => { .fmap((value) => {
const isObject = typeof value === 'object'; const isObject = typeof value === 'object';
// The case where the note has been defined via `n` or `pure` let step = isObject ? value.n : value;
if (!isObject || (isObject && ('n' in value || 'value' in value))) { if (isObject) {
const step = isObject ? (value.n ?? value.value) : value;
delete value.n; // remove n so it won't cause trouble delete value.n; // remove n so it won't cause trouble
}
if (isNote(step)) { if (isNote(step)) {
// legacy.. // legacy..
return pure(step); return pure(step);
} }
let asNumber = Number(step);
let semitones = 0;
if (isNaN(asNumber)) {
step = String(step);
if (!/^[-+]?\d+(#*|b*){1}$/.test(step)) {
logger(
`[tonal] invalid scale step "${step}", expected number or integer with optional # b suffixes`,
'error',
);
return silence;
}
const isharp = step.indexOf('#');
if (isharp >= 0) {
asNumber = Number(step.substring(0, isharp));
semitones = step.length - isharp;
} else {
const iflat = step.indexOf('b');
asNumber = Number(step.substring(0, iflat));
semitones = iflat - step.length;
}
}
try { try {
const [number, offset] = _convertStepToNumberAndOffset(step);
let note; let note;
if (isObject && value.anchor) { if (isObject && value.anchor) {
note = stepInNamedScale(number, scale, value.anchor); note = stepInNamedScale(asNumber, scale, value.anchor);
} else { } else {
note = scaleStep(number, scale); note = scaleStep(asNumber, scale);
} }
if (offset != 0) note = Note.transpose(note, Interval.fromSemitones(offset)); if (semitones != 0) note = Note.transpose(note, Interval.fromSemitones(semitones));
value = pure(isObject ? { ...value, note } : note); value = pure(isObject ? { ...value, note } : note);
} catch (err) { } catch (err) {
logger(`[tonal] ${err.message}`, 'error'); logger(`[tonal] ${err.message}`, 'error');
return silence; value = silence;
} }
return value; return value;
}
// The case where the note has been defined via `note`
else {
const note = _getNearestScaleNote(scale, value.note);
return pure(isObject ? { ...value, note } : note);
}
}) })
.outerJoin() .outerJoin()
// legacy: // legacy:
+4 -3
View File
@@ -101,11 +101,11 @@ export function nearestNumberIndex(target, numbers, preferHigher) {
let scaleSteps = {}; // [scaleName]: semitones[] let scaleSteps = {}; // [scaleName]: semitones[]
export function stepInNamedScale(step, scale, anchor, preferHigher) { export function stepInNamedScale(step, scale, anchor, preferHigher) {
const [root, scaleName] = Scale.tokenize(scale); let [root, scaleName] = Scale.tokenize(scale);
const rootMidi = x2midi(root); const rootMidi = x2midi(root);
const rootChroma = midi2chroma(rootMidi); const rootChroma = midi2chroma(rootMidi);
if (!scaleSteps[scaleName]) { if (!scaleSteps[scaleName]) {
const { intervals } = Scale.get(`C ${scaleName}`); let { intervals } = Scale.get(`C ${scaleName}`);
// cache result // cache result
scaleSteps[scaleName] = intervals.map(step2semitones); scaleSteps[scaleName] = intervals.map(step2semitones);
} }
@@ -222,7 +222,6 @@ export const Note = {
}; };
// TODO: support octave numbers // TODO: support octave numbers
// Example: Note("Bb3").transpose("c3")
export function transpose(note, step) { export function transpose(note, step) {
// example: E, 3 // example: E, 3
const stepNumber = Step.tokenize(step)[1]; // 3 const stepNumber = Step.tokenize(step)[1]; // 3
@@ -236,3 +235,5 @@ export function transpose(note, step) {
const offsetAccidentals = accidentalString(Step.accidentals(step) + Note.accidentals(note) + stepIndex - indexOffset); // "we need to add a # to to the G to make it a major third from E" const offsetAccidentals = accidentalString(Step.accidentals(step) + Note.accidentals(note) + stepIndex - indexOffset); // "we need to add a # to to the G to make it a major third from E"
return [targetNote, offsetAccidentals].join(''); return [targetNote, offsetAccidentals].join('');
} }
//Note("Bb3").transpose("c3")
+54 -273
View File
@@ -3093,50 +3093,57 @@ exports[`runs examples > example "dry" example index 0 1`] = `
exports[`runs examples > example "duckattack" example index 0 1`] = ` exports[`runs examples > example "duckattack" example index 0 1`] = `
[ [
"[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
"[ 0/1 → 1/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 1/8 → 1/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 1/4 → 5/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 1/4 → 5/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
"[ 1/4 → 3/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 3/8 → 1/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 1/2 → 9/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 1/2 → 9/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
"[ 1/2 → 5/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 5/8 → 3/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 11/16 → 3/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 11/16 → 3/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
"[ 3/4 → 7/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
"[ 7/8 → 1/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
"[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", "[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
"[ 1/1 → 9/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 9/8 → 5/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", "[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
"[ 5/4 → 11/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 11/8 → 3/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", "[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
"[ 3/2 → 13/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 13/8 → 7/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", "[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
"[ 7/4 → 15/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]", "[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckattack:0 duckdepth:1 ]",
"[ 15/8 → 2/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
"[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", "[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
"[ 2/1 → 17/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 17/8 → 9/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", "[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
"[ 9/4 → 19/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 19/8 → 5/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", "[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
"[ 5/2 → 21/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 21/8 → 11/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", "[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
"[ 11/4 → 23/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 23/8 → 47/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]", "[ 23/8 → 47/16 | s:bd n:4 duckorbit:2 duckattack:0.4 duckdepth:1 ]",
"[ 23/8 → 3/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
"[ 3/1 → 49/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 3/1 → 49/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
"[ 3/1 → 25/8 | note:C3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 25/8 → 13/4 | note:D3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 13/4 → 53/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 13/4 → 53/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
"[ 13/4 → 27/8 | note:Eb3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 27/8 → 7/2 | note:F3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 7/2 → 57/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 7/2 → 57/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
"[ 7/2 → 29/8 | note:G3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 29/8 → 15/4 | note:Ab3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 59/16 → 15/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 59/16 → 15/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
"[ 15/4 → 31/8 | note:Bb3 s:sawtooth delay:0.7 orbit:2 ]",
"[ 31/8 → 63/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 31/8 → 63/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
] "[ 31/8 → 4/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
`;
exports[`runs examples > example "duckattack" example index 1 1`] = `
[
"[ 0/1 → 1/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 1/4 → 5/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 1/2 → 9/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 11/16 → 3/4 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 7/8 → 15/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 1/1 → 17/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 5/4 → 21/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 3/2 → 25/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 27/16 → 7/4 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 15/8 → 31/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 2/1 → 33/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 9/4 → 37/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 5/2 → 41/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 43/16 → 11/4 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 23/8 → 47/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 3/1 → 49/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 13/4 → 53/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 7/2 → 57/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 59/16 → 15/4 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
"[ 31/8 → 63/16 | s:bd n:4 duckorbit:[2 5] duckattack:[0.4 0.1] ]",
] ]
`; `;
@@ -3197,94 +3204,6 @@ exports[`runs examples > example "duckdepth" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "duckdepth" example index 1 1`] = `
[
"[ 0/1 → 1/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 1/4 → 5/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 1/2 → 9/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 11/16 → 3/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 7/8 → 15/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 1/1 → 17/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 5/4 → 21/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 3/2 → 25/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 27/16 → 7/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 15/8 → 31/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 2/1 → 33/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 9/4 → 37/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 5/2 → 41/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 43/16 → 11/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 23/8 → 47/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 3/1 → 49/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 13/4 → 53/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 7/2 → 57/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 59/16 → 15/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
"[ 31/8 → 63/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:[1 0.5] ]",
]
`;
exports[`runs examples > example "duckonset" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 1/4 → 1/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 1/2 → 3/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 3/4 → 1/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 1/1 → 5/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 5/4 → 3/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 3/2 → 7/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 7/4 → 2/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 2/1 → 9/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 9/4 → 5/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 5/2 → 11/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 11/4 → 3/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 3/1 → 13/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 13/4 → 7/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 7/2 → 15/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
"[ 15/4 → 4/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0 postgain:0 ]",
]
`;
exports[`runs examples > example "duckonset" example index 1 1`] = `
[
"[ 0/1 → 1/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 1/4 → 1/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 1/2 → 3/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 3/4 → 1/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 1/1 → 5/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 5/4 → 3/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 3/2 → 7/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 7/4 → 2/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 2/1 → 9/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 9/4 → 5/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 5/2 → 11/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 11/4 → 3/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 3/1 → 13/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 13/4 → 7/2 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 7/2 → 15/4 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
"[ 15/4 → 4/1 | s:bd duckorbit:2 duckattack:0.3 duckonset:0.01 postgain:0 ]",
]
`;
exports[`runs examples > example "duckonset" example index 2 1`] = `
[
"[ 0/1 → 1/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 1/4 → 1/2 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 1/2 → 3/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 3/4 → 1/1 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 1/1 → 5/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 5/4 → 3/2 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 3/2 → 7/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 7/4 → 2/1 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 2/1 → 9/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 9/4 → 5/2 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 5/2 → 11/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 11/4 → 3/1 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 3/1 → 13/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 13/4 → 7/2 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 7/2 → 15/4 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
"[ 15/4 → 4/1 | s:bd bank:tr909 duckorbit:[4 7] duckonset:[0.3 0.003] duckattack:0.25 ]",
]
`;
exports[`runs examples > example "duckorbit" example index 0 1`] = ` exports[`runs examples > example "duckorbit" example index 0 1`] = `
[ [
"[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]", "[ 0/1 → 1/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
@@ -3310,31 +3229,6 @@ exports[`runs examples > example "duckorbit" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "duckorbit" example index 1 1`] = `
[
"[ 0/1 → 1/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 1/4 → 5/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 1/2 → 9/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 11/16 → 3/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 7/8 → 15/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 1/1 → 17/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 5/4 → 21/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 3/2 → 25/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 27/16 → 7/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 15/8 → 31/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 2/1 → 33/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 9/4 → 37/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 5/2 → 41/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 43/16 → 11/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 23/8 → 47/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 3/1 → 49/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 13/4 → 53/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 7/2 → 57/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 59/16 → 15/4 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
"[ 31/8 → 63/16 | s:bd n:4 duckorbit:[2 3] duckattack:0.2 duckdepth:1 ]",
]
`;
exports[`runs examples > example "duration" example index 0 1`] = ` exports[`runs examples > example "duration" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | note:c s:piano duration:0.5 ]", "[ 0/1 → 1/4 | note:c s:piano duration:0.5 ]",
@@ -3407,20 +3301,28 @@ exports[`runs examples > example "echo" example index 0 1`] = `
] ]
`; `;
exports[`runs examples > example "echoForEach" example index 0 1`] = `[]`;
exports[`runs examples > example "echoWith" example index 0 1`] = ` exports[`runs examples > example "echoWith" example index 0 1`] = `
[ [
"[ -3/8 ⇜ (0/1 → 1/8) | note:Bb3 ]", "[ -3/8 ⇜ (0/1 → 1/8) | note:Bb3 ]",
"[ -1/4 ⇜ (0/1 → 1/4) | note:D4 ]", "[ -1/4 ⇜ (0/1 → 1/8) ⇝ 1/4 | note:D4 ]",
"[ -1/8 ⇜ (0/1 → 3/8) | note:F4 ]", "[ -1/8 ⇜ (0/1 → 1/8) ⇝ 3/8 | note:F4 ]",
"[ 0/1 → 1/1 | note:C3 ]", "[ 0/1 → 1/1 | note:C3 ]",
"[ -1/4 ⇜ (1/8 → 1/4) | note:D4 ]",
"[ -1/8 ⇜ (1/8 → 1/4) ⇝ 3/8 | note:F4 ]",
"[ (1/8 → 1/1) ⇝ 9/8 | note:Eb3 ]", "[ (1/8 → 1/1) ⇝ 9/8 | note:Eb3 ]",
"[ -1/8 ⇜ (1/4 → 3/8) | note:F4 ]",
"[ (1/4 → 1/1) ⇝ 5/4 | note:G3 ]", "[ (1/4 → 1/1) ⇝ 5/4 | note:G3 ]",
"[ (3/8 → 1/1) ⇝ 11/8 | note:Bb3 ]", "[ (3/8 → 1/1) ⇝ 11/8 | note:Bb3 ]",
"[ 1/8 ⇜ (1/1 → 9/8) | note:Eb3 ]", "[ 1/8 ⇜ (1/1 → 9/8) | note:Eb3 ]",
"[ 1/4 ⇜ (1/1 → 5/4) | note:G3 ]", "[ 1/4 ⇜ (1/1 → 9/8) ⇝ 5/4 | note:G3 ]",
"[ 3/8 ⇜ (1/1 → 11/8) | note:Bb3 ]", "[ 3/8 ⇜ (1/1 → 9/8) ⇝ 11/8 | note:Bb3 ]",
"[ 1/1 → 3/2 | note:Eb3 ]", "[ 1/1 → 3/2 | note:Eb3 ]",
"[ 1/4 ⇜ (9/8 → 5/4) | note:G3 ]",
"[ 3/8 ⇜ (9/8 → 5/4) ⇝ 11/8 | note:Bb3 ]",
"[ 9/8 → 13/8 | note:G3 ]", "[ 9/8 → 13/8 | note:G3 ]",
"[ 3/8 ⇜ (5/4 → 11/8) | note:Bb3 ]",
"[ 5/4 → 7/4 | note:Bb3 ]", "[ 5/4 → 7/4 | note:Bb3 ]",
"[ 11/8 → 15/8 | note:D4 ]", "[ 11/8 → 15/8 | note:D4 ]",
"[ 3/2 → 2/1 | note:G3 ]", "[ 3/2 → 2/1 | note:G3 ]",
@@ -3428,17 +3330,23 @@ exports[`runs examples > example "echoWith" example index 0 1`] = `
"[ (7/4 → 2/1) ⇝ 9/4 | note:D4 ]", "[ (7/4 → 2/1) ⇝ 9/4 | note:D4 ]",
"[ (15/8 → 2/1) ⇝ 19/8 | note:F4 ]", "[ (15/8 → 2/1) ⇝ 19/8 | note:F4 ]",
"[ 13/8 ⇜ (2/1 → 17/8) | note:Bb3 ]", "[ 13/8 ⇜ (2/1 → 17/8) | note:Bb3 ]",
"[ 7/4 ⇜ (2/1 → 9/4) | note:D4 ]", "[ 7/4 ⇜ (2/1 → 17/8) ⇝ 9/4 | note:D4 ]",
"[ 15/8 ⇜ (2/1 → 19/8) | note:F4 ]", "[ 15/8 ⇜ (2/1 → 17/8) ⇝ 19/8 | note:F4 ]",
"[ 2/1 → 3/1 | note:C3 ]", "[ 2/1 → 3/1 | note:C3 ]",
"[ 7/4 ⇜ (17/8 → 9/4) | note:D4 ]",
"[ 15/8 ⇜ (17/8 → 9/4) ⇝ 19/8 | note:F4 ]",
"[ (17/8 → 3/1) ⇝ 25/8 | note:Eb3 ]", "[ (17/8 → 3/1) ⇝ 25/8 | note:Eb3 ]",
"[ 15/8 ⇜ (9/4 → 19/8) | note:F4 ]",
"[ (9/4 → 3/1) ⇝ 13/4 | note:G3 ]", "[ (9/4 → 3/1) ⇝ 13/4 | note:G3 ]",
"[ (19/8 → 3/1) ⇝ 27/8 | note:Bb3 ]", "[ (19/8 → 3/1) ⇝ 27/8 | note:Bb3 ]",
"[ 17/8 ⇜ (3/1 → 25/8) | note:Eb3 ]", "[ 17/8 ⇜ (3/1 → 25/8) | note:Eb3 ]",
"[ 9/4 ⇜ (3/1 → 13/4) | note:G3 ]", "[ 9/4 ⇜ (3/1 → 25/8) ⇝ 13/4 | note:G3 ]",
"[ 19/8 ⇜ (3/1 → 27/8) | note:Bb3 ]", "[ 19/8 ⇜ (3/1 → 25/8) ⇝ 27/8 | note:Bb3 ]",
"[ 3/1 → 7/2 | note:Eb3 ]", "[ 3/1 → 7/2 | note:Eb3 ]",
"[ 9/4 ⇜ (25/8 → 13/4) | note:G3 ]",
"[ 19/8 ⇜ (25/8 → 13/4) ⇝ 27/8 | note:Bb3 ]",
"[ 25/8 → 29/8 | note:G3 ]", "[ 25/8 → 29/8 | note:G3 ]",
"[ 19/8 ⇜ (13/4 → 27/8) | note:Bb3 ]",
"[ 13/4 → 15/4 | note:Bb3 ]", "[ 13/4 → 15/4 | note:Bb3 ]",
"[ 27/8 → 31/8 | note:D4 ]", "[ 27/8 → 31/8 | note:D4 ]",
"[ 7/2 → 4/1 | note:G3 ]", "[ 7/2 → 4/1 | note:G3 ]",
@@ -3750,8 +3658,6 @@ exports[`runs examples > example "fast" example index 0 1`] = `
exports[`runs examples > example "fastChunk" example index 0 1`] = ` exports[`runs examples > example "fastChunk" example index 0 1`] = `
[ [
"[ 0/1 → 1/4 | color:red note:0 ]",
"[ 1/4 → 1/2 | color:red note:1 ]",
"[ 1/2 → 3/4 | note:E2 ]", "[ 1/2 → 3/4 | note:E2 ]",
"[ 3/4 → 1/1 | note:F2 ]", "[ 3/4 → 1/1 | note:F2 ]",
"[ 1/1 → 5/4 | note:G2 ]", "[ 1/1 → 5/4 | note:G2 ]",
@@ -3760,8 +3666,6 @@ exports[`runs examples > example "fastChunk" example index 0 1`] = `
"[ 7/4 → 2/1 | note:C3 ]", "[ 7/4 → 2/1 | note:C3 ]",
"[ 2/1 → 9/4 | note:D3 ]", "[ 2/1 → 9/4 | note:D3 ]",
"[ 9/4 → 5/2 | note:D2 ]", "[ 9/4 → 5/2 | note:D2 ]",
"[ 5/2 → 11/4 | color:red note:2 ]",
"[ 11/4 → 3/1 | color:red note:3 ]",
"[ 3/1 → 13/4 | note:G2 ]", "[ 3/1 → 13/4 | note:G2 ]",
"[ 13/4 → 7/2 | note:A2 ]", "[ 13/4 → 7/2 | note:A2 ]",
"[ 7/2 → 15/4 | note:B2 ]", "[ 7/2 → 15/4 | note:B2 ]",
@@ -6670,27 +6574,6 @@ exports[`runs examples > example "note" example index 2 1`] = `
] ]
`; `;
exports[`runs examples > example "note" example index 3 1`] = `
[
"[ 0/1 → 1/4 | note:fbb1 s:saw ]",
"[ 1/4 → 1/2 | note:a#0 s:saw ]",
"[ 1/2 → 3/4 | note:cbbb-1 s:saw ]",
"[ 3/4 → 1/1 | note:e##-2 s:saw ]",
"[ 1/1 → 5/4 | note:fbb1 s:saw ]",
"[ 5/4 → 3/2 | note:a#0 s:saw ]",
"[ 3/2 → 7/4 | note:cbbb-1 s:saw ]",
"[ 7/4 → 2/1 | note:e##-2 s:saw ]",
"[ 2/1 → 9/4 | note:fbb1 s:saw ]",
"[ 9/4 → 5/2 | note:a#0 s:saw ]",
"[ 5/2 → 11/4 | note:cbbb-1 s:saw ]",
"[ 11/4 → 3/1 | note:e##-2 s:saw ]",
"[ 3/1 → 13/4 | note:fbb1 s:saw ]",
"[ 13/4 → 7/2 | note:a#0 s:saw ]",
"[ 7/2 → 15/4 | note:cbbb-1 s:saw ]",
"[ 15/4 → 4/1 | note:e##-2 s:saw ]",
]
`;
exports[`runs examples > example "nrpnn" example index 0 1`] = ` exports[`runs examples > example "nrpnn" example index 0 1`] = `
[ [
"[ 0/1 → 1/1 | note:c4 nrpnn:[1 8] nrpv:123 midichan:1 ]", "[ 0/1 → 1/1 | note:c4 nrpnn:[1 8] nrpv:123 midichan:1 ]",
@@ -8979,108 +8862,6 @@ exports[`runs examples > example "scale" example index 2 1`] = `
] ]
`; `;
exports[`runs examples > example "scale" example index 3 1`] = `
[
"[ 0/1 → 1/4 | note:C3 s:piano ]",
"[ 0/1 → 1/4 | note:B3 s:piano ]",
"[ 1/4 → 3/8 | note:Gb2 s:piano ]",
"[ 3/8 → 1/2 | note:F2 s:piano ]",
"[ 1/2 → 3/4 | note:A2 s:piano ]",
"[ 1/2 → 3/4 | note:D4 s:piano ]",
"[ 3/4 → 1/1 | note:G3 s:piano ]",
"[ 1/1 → 5/4 | note:C3 s:piano ]",
"[ 1/1 → 5/4 | note:C4 s:piano ]",
"[ 5/4 → 11/8 | note:Gb2 s:piano ]",
"[ 11/8 → 3/2 | note:E2 s:piano ]",
"[ 3/2 → 7/4 | note:A2 s:piano ]",
"[ 3/2 → 7/4 | note:Eb4 s:piano ]",
"[ 7/4 → 2/1 | note:F#3 s:piano ]",
"[ 2/1 → 9/4 | note:C3 s:piano ]",
"[ 2/1 → 9/4 | note:B3 s:piano ]",
"[ 9/4 → 19/8 | note:Gb2 s:piano ]",
"[ 19/8 → 5/2 | note:F2 s:piano ]",
"[ 5/2 → 11/4 | note:Ab2 s:piano ]",
"[ 5/2 → 11/4 | note:D4 s:piano ]",
"[ 11/4 → 3/1 | note:G3 s:piano ]",
"[ 3/1 → 13/4 | note:C3 s:piano ]",
"[ 3/1 → 13/4 | note:C4 s:piano ]",
"[ 13/4 → 27/8 | note:Gb2 s:piano ]",
"[ 27/8 → 7/2 | note:E2 s:piano ]",
"[ 7/2 → 15/4 | note:Ab2 s:piano ]",
"[ 7/2 → 15/4 | note:Eb4 s:piano ]",
"[ 15/4 → 4/1 | note:F#3 s:piano ]",
]
`;
exports[`runs examples > example "scale" example index 4 1`] = `
[
"[ 0/1 → 1/16 | note:Gb1 ]",
"[ 1/16 → 1/8 | note:Gb1 ]",
"[ 1/8 → 3/16 | note:Gb1 ]",
"[ 3/16 → 1/4 | note:Gb1 ]",
"[ 1/4 → 5/16 | note:Gb1 ]",
"[ 5/16 → 3/8 | note:Gb1 ]",
"[ 3/8 → 7/16 | note:Gb1 ]",
"[ 7/16 → 1/2 | note:Gb1 ]",
"[ 1/2 → 9/16 | note:Gb1 ]",
"[ 9/16 → 5/8 | note:Gb1 ]",
"[ 5/8 → 11/16 | note:Gb1 ]",
"[ 11/16 → 3/4 | note:Gb1 ]",
"[ 3/4 → 13/16 | note:Gb1 ]",
"[ 13/16 → 7/8 | note:Gb1 ]",
"[ 7/8 → 15/16 | note:Gb1 ]",
"[ 15/16 → 1/1 | note:Gb1 ]",
"[ 1/1 → 17/16 | note:Cb3 ]",
"[ 17/16 → 9/8 | note:Cb3 ]",
"[ 9/8 → 19/16 | note:Cb3 ]",
"[ 19/16 → 5/4 | note:Cb3 ]",
"[ 5/4 → 21/16 | note:Cb3 ]",
"[ 21/16 → 11/8 | note:Cb3 ]",
"[ 11/8 → 23/16 | note:Cb3 ]",
"[ 23/16 → 3/2 | note:Cb3 ]",
"[ 3/2 → 25/16 | note:Cb3 ]",
"[ 25/16 → 13/8 | note:Cb3 ]",
"[ 13/8 → 27/16 | note:Cb3 ]",
"[ 27/16 → 7/4 | note:Cb3 ]",
"[ 7/4 → 29/16 | note:Cb3 ]",
"[ 29/16 → 15/8 | note:Cb3 ]",
"[ 15/8 → 31/16 | note:Cb3 ]",
"[ 31/16 → 2/1 | note:Cb3 ]",
"[ 2/1 → 33/16 | note:Eb4 ]",
"[ 33/16 → 17/8 | note:Eb4 ]",
"[ 17/8 → 35/16 | note:Eb4 ]",
"[ 35/16 → 9/4 | note:Eb4 ]",
"[ 9/4 → 37/16 | note:Eb4 ]",
"[ 37/16 → 19/8 | note:Eb4 ]",
"[ 19/8 → 39/16 | note:Eb4 ]",
"[ 39/16 → 5/2 | note:Eb4 ]",
"[ 5/2 → 41/16 | note:Eb4 ]",
"[ 41/16 → 21/8 | note:Eb4 ]",
"[ 21/8 → 43/16 | note:Eb4 ]",
"[ 43/16 → 11/4 | note:Eb4 ]",
"[ 11/4 → 45/16 | note:Eb4 ]",
"[ 45/16 → 23/8 | note:Eb4 ]",
"[ 23/8 → 47/16 | note:Eb4 ]",
"[ 47/16 → 3/1 | note:Eb4 ]",
"[ 3/1 → 49/16 | note:Db2 ]",
"[ 49/16 → 25/8 | note:Db2 ]",
"[ 25/8 → 51/16 | note:Db2 ]",
"[ 51/16 → 13/4 | note:Db2 ]",
"[ 13/4 → 53/16 | note:Db2 ]",
"[ 53/16 → 27/8 | note:Db2 ]",
"[ 27/8 → 55/16 | note:Db2 ]",
"[ 55/16 → 7/2 | note:Db2 ]",
"[ 7/2 → 57/16 | note:Db2 ]",
"[ 57/16 → 29/8 | note:Db2 ]",
"[ 29/8 → 59/16 | note:Db2 ]",
"[ 59/16 → 15/4 | note:Db2 ]",
"[ 15/4 → 61/16 | note:Db2 ]",
"[ 61/16 → 31/8 | note:Db2 ]",
"[ 31/8 → 63/16 | note:Db2 ]",
"[ 63/16 → 4/1 | note:Db2 ]",
]
`;
exports[`runs examples > example "scaleTranspose" example index 0 1`] = ` exports[`runs examples > example "scaleTranspose" example index 0 1`] = `
[ [
"[ 0/1 → 1/2 | note:C3 ]", "[ 0/1 → 1/2 | note:C3 ]",
-5
View File
@@ -1,5 +0,0 @@
The FontStruction “TIC-80 wide font”
(https://fontstruct.com/fontstructions/show/1388526) by “nesbox” is licensed
under a Creative Commons CC0 Public Domain Dedication license
(http://creativecommons.org/publicdomain/zero/1.0/).
[ancestry]
-16
View File
@@ -1,16 +0,0 @@
The font file in this archive was created using Fontstruct the free, online
font-building tool.
This font was created by “nesbox”.
This font has a homepage where this archive and other versions may be found:
https://fontstruct.com/fontstructions/show/1388526
[ancestry]
Try Fontstruct at https://fontstruct.com
Its easy and its fun.
Fontstruct is copyright ©2017-2025 Rob Meek
LEGAL NOTICE:
In using this font you must comply with the licensing terms described in the
file “license.txt” included with this archive.
If you redistribute the font file in this archive, it must be accompanied by all
the other files from this archive, including this one.
Binary file not shown.
@@ -74,7 +74,6 @@ const fontFamilyOptions = {
FiraCode: 'FiraCode', FiraCode: 'FiraCode',
'FiraCode-SemiBold': 'FiraCode SemiBold', 'FiraCode-SemiBold': 'FiraCode SemiBold',
teletext: 'teletext', teletext: 'teletext',
tic80: 'tic80',
mode7: 'mode7', mode7: 'mode7',
BigBlueTerminal: 'BigBlueTerminal', BigBlueTerminal: 'BigBlueTerminal',
x3270: 'x3270', x3270: 'x3270',
+4 -4
View File
@@ -285,7 +285,7 @@ stack(
.mul(gain(sine.struct("x*8").add(3/5).mul(2/5).fast(8))) .mul(gain(sine.struct("x*8").add(3/5).mul(2/5).fast(8)))
.piano()`; .piano()`;
// iter, echo, echoWith // iter, echo, echoForEach
export const undergroundPlumber = `// "Underground plumber" export const undergroundPlumber = `// "Underground plumber"
// @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/ // @license CC BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
// @by Felix Roos // @by Felix Roos
@@ -307,7 +307,7 @@ stack(
.cutoff(400).decay(.12).sustain(0) .cutoff(400).decay(.12).sustain(0)
, ,
"[g2,[c3 eb3]]".iter(4) "[g2,[c3 eb3]]".iter(4)
.echoWith(4, 1/8, (x,n)=>x.transpose(n*12).gain(Math.pow(.4,n))) .echoForEach(4, 1/8, (x,n)=>x.transpose(n*12).gain(Math.pow(.4,n)))
.note().layer(h) .note().layer(h)
.clip(.1) .clip(.1)
) )
@@ -432,7 +432,7 @@ export const festivalOfFingers3 = `// "Festival of fingers 3"
setcps(1) setcps(1)
n("[-7*3],0,2,6,[8 7]") n("[-7*3],0,2,6,[8 7]")
.echoWith( .echoForEach(
4, // echo 4 times 4, // echo 4 times
1/4, // 1/4s between echos 1/4, // 1/4s between echos
(x,i)=>x (x,i)=>x
@@ -485,7 +485,7 @@ stack(
.decay(.1).sustain(0) // make notes short .decay(.1).sustain(0) // make notes short
.s('triangle') // waveform .s('triangle') // waveform
.degradeBy(perlin.range(0,.5)) // randomly controlled random removal :) .degradeBy(perlin.range(0,.5)) // randomly controlled random removal :)
.echoWith(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes .echoForEach(4,.125,(x,n)=>x.gain(.15*1/(n+1))) // echo notes
//.hush() //.hush()
) )
.slow(3/2)`; .slow(3/2)`;
-5
View File
@@ -50,11 +50,6 @@
src: url('/fonts/teletext/EuropeanTeletext.ttf'); src: url('/fonts/teletext/EuropeanTeletext.ttf');
size-adjust: 90%; size-adjust: 90%;
} }
@font-face {
font-family: 'tic80';
src: url('/fonts/tic80/tic-80-wide-font.otf');
size-adjust: 60%;
}
@font-face { @font-face {
font-family: 'mode7'; font-family: 'mode7';
src: url('/fonts/mode7/MODE7GX3.TTF'); src: url('/fonts/mode7/MODE7GX3.TTF');