mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-18 08:36:06 -04:00
Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f17a4d045f | |||
| 8e1e83f3cf | |||
| 2d64b538e7 | |||
| b717615558 | |||
| 8c4f10464b | |||
| 018649c4f3 | |||
| 5ff59a878d | |||
| a20468b85f | |||
| 9648e35de2 | |||
| cd344cefe0 | |||
| 1f6a313f02 | |||
| 5cbeb227d8 | |||
| 2ab2cc5fc8 | |||
| 3dd667e825 | |||
| 482ebb695a | |||
| 218952b8b6 | |||
| 2e8a8b50df | |||
| f22f89347e | |||
| de15d79edf | |||
| 23e5358ae4 | |||
| 3b6777e259 | |||
| ed5bf3b3fc | |||
| b089328da3 | |||
| 784c711870 | |||
| 34b10b6c84 | |||
| 450f9685f3 | |||
| 03090e399c | |||
| ed58d26da6 | |||
| e3605590e5 | |||
| d55ef8e843 | |||
| efc2333360 | |||
| 92626e7c20 | |||
| ad113b3888 | |||
| 39c45bc2b2 | |||
| 899af88bd9 | |||
| c2b8cc4b82 | |||
| 40ed7b5382 | |||
| 5d829419ba | |||
| e26da854c0 | |||
| a28b7a2d5a | |||
| 83c776d70f | |||
| 051efdc13b | |||
| ef3cd56b0d | |||
| 9017085fa3 | |||
| cdbbe079db | |||
| 6ee9dea2ff | |||
| 03cbe4b6d4 | |||
| 784ee576da | |||
| ee3e1217b4 | |||
| 973ae4c7da | |||
| 4d13b981f1 | |||
| 7691403fd0 | |||
| 3b6e3624be | |||
| 51f0bab1f8 | |||
| dae1560c40 | |||
| 82f0dbb750 | |||
| fb7d76a2ab | |||
| 26225e4ea7 | |||
| 2415210be6 | |||
| fe18aa770d |
@@ -83,4 +83,14 @@ export default [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// Properties provided by AudioWorkletGlobalScope
|
||||||
|
files: ['packages/superdough/worklets.mjs'],
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
currentTime: 'readonly',
|
||||||
|
sampleRate: 'readonly',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/codemirror",
|
"name": "@strudel/codemirror",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Codemirror Extensions for Strudel",
|
"description": "Codemirror Extensions for Strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
+48
-21
@@ -252,6 +252,20 @@ export const { fmenv } = registerControl('fmenv');
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const { fmattack } = registerControl('fmattack');
|
export const { fmattack } = registerControl('fmattack');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* waveform of the fm modulator
|
||||||
|
*
|
||||||
|
* @name fmwave
|
||||||
|
* @param {number | Pattern} wave waveform
|
||||||
|
* @example
|
||||||
|
* n("0 1 2 3".fast(4)).scale("d:minor").s("sine").fmwave("<sine square sawtooth crackle>").fm(4).fmh(2.01)
|
||||||
|
* @example
|
||||||
|
* n("0 1 2 3".fast(4)).chord("<Dm Am F G>").voicing().s("sawtooth").fmwave("brown").fm(.6)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const { fmwave } = registerControl('fmwave');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
|
* Decay time for the FM envelope: seconds until the sustain level is reached after the attack phase.
|
||||||
*
|
*
|
||||||
@@ -306,6 +320,7 @@ export const { fft } = registerControl('fft');
|
|||||||
*
|
*
|
||||||
* @name decay
|
* @name decay
|
||||||
* @param {number | Pattern} time decay time in seconds
|
* @param {number | Pattern} time decay time in seconds
|
||||||
|
* @synonyms dec
|
||||||
* @example
|
* @example
|
||||||
* note("c3 e3 f3 g3").decay("<.1 .2 .3 .4>").sustain(0)
|
* note("c3 e3 f3 g3").decay("<.1 .2 .3 .4>").sustain(0)
|
||||||
*
|
*
|
||||||
@@ -526,28 +541,40 @@ export const { tremoloshape } = registerControl('tremoloshape', 'tremshape');
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// TODO: SUPRADOUGH implement post orbit "pump" sidechain effect
|
/**
|
||||||
// /**
|
* modulate the amplitude of an orbit to create a "sidechain" like effect
|
||||||
// * modulate the amplitude of an orbit to create a "sidechain" like effect
|
*
|
||||||
// *
|
* @name duckorbit
|
||||||
// * @name pump
|
* @param {number | Pattern} orbit target orbit
|
||||||
// * @param {number | Pattern} speed modulation speed in cycles
|
* @example
|
||||||
// * @example
|
* $: n(run(16)).scale("c:minor:pentatonic").s("sawtooth").delay(.7).orbit(2)
|
||||||
// * note("{f g c d}%16").s("sawtooth").pump(".25:.75")
|
* $: s("bd:4!4").beat("0,4,8,11,14",16).duckorbit(2).duckattack(0.2).duckdepth(1)
|
||||||
// *
|
*
|
||||||
// */
|
*/
|
||||||
// export const { pump } = registerControl(['pump', 'pumpdepth']);
|
export const { duck } = registerControl('duckorbit', 'duck');
|
||||||
|
|
||||||
// /**
|
/**
|
||||||
// * modulate the amplitude of an orbit to create a "sidechain" like effect
|
* the amount of ducking applied to target orbit
|
||||||
// *
|
*
|
||||||
// * @name pumpdepth
|
* @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
|
||||||
// * note("{f g c d}%16").s("sawtooth").pump(".25").depth("<.25 .5 .75 1>")
|
* 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>"))
|
||||||
// *
|
*
|
||||||
// */
|
*/
|
||||||
// export const { pumpdepth } = registerControl('pumpdepth');
|
|
||||||
|
export const { duckdepth } = registerControl('duckdepth');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the attack time of the duck effect
|
||||||
|
*
|
||||||
|
* @name duckattack
|
||||||
|
* @param {number | Pattern} time
|
||||||
|
* @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 0 0.4>").duckdepth(1))
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const { duckattack } = registerControl('duckattack', 'duckatt');
|
||||||
|
|
||||||
export const { drive } = registerControl('drive');
|
export const { drive } = registerControl('drive');
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ https://rohandrape.net/?t=hmt
|
|||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { timeCat, register, silence } from './pattern.mjs';
|
import { timeCat, register, silence, stack, pure, _morph } from './pattern.mjs';
|
||||||
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
|
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
|
||||||
import Fraction, { lcm } from './fraction.mjs';
|
import Fraction, { lcm } from './fraction.mjs';
|
||||||
|
|
||||||
@@ -196,3 +196,26 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps,
|
|||||||
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
||||||
return _euclidLegato(pulses, steps, rotation, pat);
|
return _euclidLegato(pulses, steps, rotation, pat);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A 'euclid' variant with an additional parameter that morphs the resulting
|
||||||
|
* rhythm from 0 (no morphing) to 1 (completely 'even'). For example
|
||||||
|
* `sound("bd").euclidish(3,8,0)` would be the same as
|
||||||
|
* `sound("bd").euclid(3,8)`, and `sound("bd").euclidish(3,8,1)` would be the
|
||||||
|
* same as `sound("bd bd bd")`. `sound("bd").euclidish(3,8,0.5)` would have a
|
||||||
|
* groove somewhere between.
|
||||||
|
* Inspired by the work of Malcom Braff.
|
||||||
|
* @name euclidish
|
||||||
|
* @synonyms eish
|
||||||
|
* @memberof Pattern
|
||||||
|
* @param {number} pulses the number of onsets
|
||||||
|
* @param {number} steps the number of steps to fill
|
||||||
|
* @param {number} groove exists between the extremes of 0 (straight euclidian) and 1 (straight pulse)
|
||||||
|
* @example
|
||||||
|
* sound("hh").euclidish(7,12,sine.slow(8))
|
||||||
|
* .pan(sine.slow(8))
|
||||||
|
*/
|
||||||
|
export const { euclidish, eish } = register(['euclidish', 'eish'], function (pulses, steps, perc, pat) {
|
||||||
|
const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc);
|
||||||
|
return pat.struct(morphed).setSteps(steps);
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://codeberg.org/uzu/strudel/
|
|||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
import Fraction from './fraction.mjs';
|
import Fraction from './fraction.mjs';
|
||||||
|
import { stringifyValues } from './util.mjs';
|
||||||
|
|
||||||
export class Hap {
|
export class Hap {
|
||||||
/*
|
/*
|
||||||
@@ -148,13 +149,7 @@ export class Hap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showWhole(compact = false) {
|
showWhole(compact = false) {
|
||||||
return `${this.whole == undefined ? '~' : this.whole.show()}: ${
|
return `${this.whole == undefined ? '~' : this.whole.show()}: ${stringifyValues(this.value, compact)}`;
|
||||||
typeof this.value === 'object'
|
|
||||||
? compact
|
|
||||||
? JSON.stringify(this.value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
|
|
||||||
: JSON.stringify(this.value)
|
|
||||||
: this.value
|
|
||||||
}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
combineContext(b) {
|
combineContext(b) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ let debounce = 1000,
|
|||||||
|
|
||||||
export function errorLogger(e, origin = 'cyclist') {
|
export function errorLogger(e, origin = 'cyclist') {
|
||||||
//TODO: add some kind of debug flag that enables this while in dev mode
|
//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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/core",
|
"name": "@strudel/core",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Port of Tidal Cycles to JavaScript",
|
"description": "Port of Tidal Cycles to JavaScript",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import {
|
|||||||
numeralArgs,
|
numeralArgs,
|
||||||
parseNumeral,
|
parseNumeral,
|
||||||
pairs,
|
pairs,
|
||||||
|
zipWith,
|
||||||
|
stringifyValues,
|
||||||
} from './util.mjs';
|
} from './util.mjs';
|
||||||
import drawLine from './drawLine.mjs';
|
import drawLine from './drawLine.mjs';
|
||||||
import { logger } from './logger.mjs';
|
import { logger } from './logger.mjs';
|
||||||
@@ -852,14 +854,29 @@ export class Pattern {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
log(func = (_, hap) => `[hap] ${hap.showWhole(true)}`, getData = (_, hap) => ({ hap })) {
|
/**
|
||||||
|
* Writes the content of the current event to the console (visible in the side menu).
|
||||||
|
* @name log
|
||||||
|
* @memberof Pattern
|
||||||
|
* @example
|
||||||
|
* s("bd sd").log()
|
||||||
|
*/
|
||||||
|
log(func = (hap) => `[hap] ${hap.showWhole(true)}`, getData = (hap) => ({ hap })) {
|
||||||
return this.onTrigger((...args) => {
|
return this.onTrigger((...args) => {
|
||||||
logger(func(...args), undefined, getData(...args));
|
logger(func(...args), undefined, getData(...args));
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
logValues(func = id) {
|
/**
|
||||||
return this.log((_, hap) => func(hap.value));
|
* A simplified version of `log` which writes all "values" (various configurable parameters)
|
||||||
|
* within the event to the console (visible in the side menu).
|
||||||
|
* @name logValues
|
||||||
|
* @memberof Pattern
|
||||||
|
* @example
|
||||||
|
* s("bd sd").gain("0.25 0.5 1").n("2 1 0").logValues()
|
||||||
|
*/
|
||||||
|
logValues(func = (value) => `[hap] ${stringifyValues(value, true)}`) {
|
||||||
|
return this.log((hap) => func(hap.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
@@ -1288,7 +1305,7 @@ export function sequenceP(pats) {
|
|||||||
* @synonyms polyrhythm, pr
|
* @synonyms polyrhythm, pr
|
||||||
* @example
|
* @example
|
||||||
* stack("g3", "b3", ["e4", "d4"]).note()
|
* stack("g3", "b3", ["e4", "d4"]).note()
|
||||||
* // "g3,b3,[e4,d4]".note()
|
* // "g3,b3,[e4 d4]".note()
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* // As a chained function:
|
* // As a chained function:
|
||||||
@@ -1569,7 +1586,7 @@ export const func = curry((a, b) => reify(b).func(a));
|
|||||||
/**
|
/**
|
||||||
* Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.
|
* Registers a new pattern method. The method is added to the Pattern class + the standalone function is returned from register.
|
||||||
*
|
*
|
||||||
* @param {string} name name of the function
|
* @param {string | string[]} name name of the function, or an array of names to be used as synonyms
|
||||||
* @param {function} func function with 1 or more params, where last is the current pattern
|
* @param {function} func function with 1 or more params, where last is the current pattern
|
||||||
* @noAutocomplete
|
* @noAutocomplete
|
||||||
*
|
*
|
||||||
@@ -3400,3 +3417,69 @@ export const { beat } = register(
|
|||||||
['beat'],
|
['beat'],
|
||||||
__beat((x) => x.innerJoin()),
|
__beat((x) => x.innerJoin()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const _morph = (from, to, by) => {
|
||||||
|
by = Fraction(by);
|
||||||
|
const dur = Fraction(1).div(from.length);
|
||||||
|
const positions = (list) => {
|
||||||
|
const result = [];
|
||||||
|
for (const [pos, value] of list.entries()) {
|
||||||
|
if (value) {
|
||||||
|
result.push([Fraction(pos).div(list.length), value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const arcs = zipWith(
|
||||||
|
([posa, valuea], [posb, valueb]) => {
|
||||||
|
const b = by.mul(posb - posa).add(posa);
|
||||||
|
const e = b.add(dur);
|
||||||
|
return new TimeSpan(b, e);
|
||||||
|
},
|
||||||
|
positions(from),
|
||||||
|
positions(to),
|
||||||
|
);
|
||||||
|
function query(state) {
|
||||||
|
const cycle = state.span.begin.sam();
|
||||||
|
const cycleArc = state.span.cycleArc();
|
||||||
|
const result = [];
|
||||||
|
for (const whole of arcs) {
|
||||||
|
const part = whole.intersection(cycleArc);
|
||||||
|
if (part !== undefined) {
|
||||||
|
result.push(
|
||||||
|
new Hap(
|
||||||
|
whole.withTime((x) => x.add(cycle)),
|
||||||
|
part.withTime((x) => x.add(cycle)),
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return new Pattern(query).splitQueries();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes two binary rhythms represented as lists of 1s and 0s, and a number
|
||||||
|
* between 0 and 1 that morphs between them. The two lists should contain the same
|
||||||
|
* number of true values.
|
||||||
|
* @example
|
||||||
|
* sound("hh").struct(morph([1,0,1,0,1,0,1,0], // straight rhythm
|
||||||
|
* [1,1,0,1,0,1,0], // wonky rhythm
|
||||||
|
* 0.25 // creates a slightly wonky rhythm
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* @example
|
||||||
|
* sound("hh").struct(morph("1:0:1:0:1:0:1:0", // straight rhythm
|
||||||
|
* "1:1:0:1:0:1:0", // wonky rhythm
|
||||||
|
* sine.slow(8) // slowly morph between the rhythms
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
export const morph = (frompat, topat, bypat) => {
|
||||||
|
frompat = reify(frompat);
|
||||||
|
topat = reify(topat);
|
||||||
|
bypat = reify(bypat);
|
||||||
|
return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by))));
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ This program is free software: you can redistribute it and/or modify it under th
|
|||||||
|
|
||||||
import Fraction from 'fraction.js';
|
import Fraction from 'fraction.js';
|
||||||
|
|
||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TimeSpan,
|
TimeSpan,
|
||||||
@@ -55,6 +55,8 @@ import {
|
|||||||
expand,
|
expand,
|
||||||
} from '../index.mjs';
|
} from '../index.mjs';
|
||||||
|
|
||||||
|
import { log, logValues } from '../pattern.mjs';
|
||||||
|
|
||||||
import { steady } from '../signal.mjs';
|
import { steady } from '../signal.mjs';
|
||||||
|
|
||||||
import { n, s } from '../controls.mjs';
|
import { n, s } from '../controls.mjs';
|
||||||
@@ -1306,4 +1308,40 @@ describe('Pattern', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('log', () => {
|
||||||
|
it('logs to console', () => {
|
||||||
|
const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
const pattern = pure('a').log();
|
||||||
|
const haps = pattern.queryArc(0, 1);
|
||||||
|
|
||||||
|
// Force a trigger
|
||||||
|
haps.forEach((hap) => {
|
||||||
|
hap.context?.onTrigger?.(hap);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||||
|
'%c[hap] 0/1 → 1/1: a',
|
||||||
|
'background-color: black;color:white;border-radius:15px',
|
||||||
|
);
|
||||||
|
mockConsoleLog.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('logValues', () => {
|
||||||
|
it('logs values to console', () => {
|
||||||
|
const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
const pattern = pure('a').note('c#').logValues();
|
||||||
|
const haps = pattern.queryArc(0, 1);
|
||||||
|
|
||||||
|
// Force a trigger
|
||||||
|
haps.forEach((hap) => {
|
||||||
|
hap.context?.onTrigger?.(hap);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||||
|
'%c[hap] value:a note:c#',
|
||||||
|
'background-color: black;color:white;border-radius:15px',
|
||||||
|
);
|
||||||
|
mockConsoleLog.mockRestore();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export class TimeSpan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
intersection(other) {
|
intersection(other) {
|
||||||
// Intersection of two timespans, returns None if they don't intersect.
|
// Intersection of two timespans, returns undefined if they don't intersect.
|
||||||
const intersect_begin = this.begin.max(other.begin);
|
const intersect_begin = this.begin.max(other.begin);
|
||||||
const intersect_end = this.end.min(other.end);
|
const intersect_end = this.end.min(other.end);
|
||||||
|
|
||||||
|
|||||||
@@ -487,3 +487,13 @@ export function getCurrentKeyboardState() {
|
|||||||
// }
|
// }
|
||||||
// return lcm((x * y) / gcd(x, y), ...z);
|
// return lcm((x * y) / gcd(x, y), ...z);
|
||||||
// };
|
// };
|
||||||
|
|
||||||
|
// Takes values -- typically derived from events, i.e. `hap`s -- and renders them
|
||||||
|
// into a readable format
|
||||||
|
export function stringifyValues(value, compact = false) {
|
||||||
|
return typeof value === 'object'
|
||||||
|
? compact
|
||||||
|
? JSON.stringify(value).slice(1, -1).replaceAll('"', '').replaceAll(',', ' ')
|
||||||
|
: JSON.stringify(value)
|
||||||
|
: value;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/csound",
|
"name": "@strudel/csound",
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"description": "csound bindings for strudel",
|
"description": "csound bindings for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/draw",
|
"name": "@strudel/draw",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Helpers for drawing with Strudel",
|
"description": "Helpers for drawing with Strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/embed",
|
"name": "@strudel/embed",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
"description": "Embeddable Web Component to load a Strudel REPL into an iframe",
|
||||||
"main": "embed.js",
|
"main": "embed.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/gamepad",
|
"name": "@strudel/gamepad",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Gamepad Inputs for strudel",
|
"description": "Gamepad Inputs for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/hydra",
|
"name": "@strudel/hydra",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Hydra integration for strudel",
|
"description": "Hydra integration for strudel",
|
||||||
"main": "hydra.mjs",
|
"main": "hydra.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/midi",
|
"name": "@strudel/midi",
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"description": "Midi API for strudel",
|
"description": "Midi API for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/mini",
|
"name": "@strudel/mini",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Mini notation for strudel",
|
"description": "Mini notation for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mondolang",
|
"name": "mondolang",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "a language for functional composition that translates to js",
|
"description": "a language for functional composition that translates to js",
|
||||||
"main": "mondo.mjs",
|
"main": "mondo.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/mondo",
|
"name": "@strudel/mondo",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "mondo notation for strudel",
|
"description": "mondo notation for strudel",
|
||||||
"main": "mondough.mjs",
|
"main": "mondough.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/motion",
|
"name": "@strudel/motion",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "DeviceMotion API for strudel",
|
"description": "DeviceMotion API for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/mqtt",
|
"name": "@strudel/mqtt",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "MQTT API for strudel",
|
"description": "MQTT API for strudel",
|
||||||
"main": "mqtt.mjs",
|
"main": "mqtt.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/osc",
|
"name": "@strudel/osc",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "OSC messaging for strudel",
|
"description": "OSC messaging for strudel",
|
||||||
"main": "osc.mjs",
|
"main": "osc.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/reference",
|
"name": "@strudel/reference",
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"description": "Headless reference of all strudel functions",
|
"description": "Headless reference of all strudel functions",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/repl",
|
"name": "@strudel/repl",
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"description": "Strudel REPL as a Web Component",
|
"description": "Strudel REPL as a Web Component",
|
||||||
"module": "index.mjs",
|
"module": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/serial",
|
"name": "@strudel/serial",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Webserial API for strudel",
|
"description": "Webserial API for strudel",
|
||||||
"main": "serial.mjs",
|
"main": "serial.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/soundfonts",
|
"name": "@strudel/soundfonts",
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"description": "Soundsfont support for strudel",
|
"description": "Soundsfont support for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { getAudioContext } from './superdough.mjs';
|
import { getAudioContext } from './superdough.mjs';
|
||||||
import { clamp, nanFallback } from './util.mjs';
|
import { clamp, nanFallback } from './util.mjs';
|
||||||
|
import { getNoiseBuffer } from './noise.mjs';
|
||||||
|
|
||||||
|
export const noises = ['pink', 'white', 'brown', 'crackle'];
|
||||||
|
|
||||||
export function gainNode(value) {
|
export function gainNode(value) {
|
||||||
const node = getAudioContext().createGain();
|
const node = getAudioContext().createGain();
|
||||||
@@ -206,7 +209,8 @@ export function getVibratoOscillator(param, value, t) {
|
|||||||
// 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) {
|
||||||
const constantNode = audioContext.createConstantSource();
|
const constantNode = new ConstantSourceNode(audioContext);
|
||||||
|
|
||||||
constantNode.start(startTime);
|
constantNode.start(startTime);
|
||||||
constantNode.stop(stopTime);
|
constantNode.stop(stopTime);
|
||||||
constantNode.onended = () => {
|
constantNode.onended = () => {
|
||||||
@@ -216,9 +220,17 @@ export function webAudioTimeout(audioContext, onComplete, startTime, stopTime) {
|
|||||||
}
|
}
|
||||||
const mod = (freq, range = 1, type = 'sine') => {
|
const mod = (freq, range = 1, type = 'sine') => {
|
||||||
const ctx = getAudioContext();
|
const ctx = getAudioContext();
|
||||||
const osc = ctx.createOscillator();
|
let osc;
|
||||||
osc.type = type;
|
if (noises.includes(type)) {
|
||||||
osc.frequency.value = freq;
|
osc = ctx.createBufferSource();
|
||||||
|
osc.buffer = getNoiseBuffer(type, 2);
|
||||||
|
osc.loop = true;
|
||||||
|
} else {
|
||||||
|
osc = ctx.createOscillator();
|
||||||
|
osc.type = type;
|
||||||
|
osc.frequency.value = freq;
|
||||||
|
}
|
||||||
|
|
||||||
osc.start();
|
osc.start();
|
||||||
const g = new GainNode(ctx, { gain: range });
|
const g = new GainNode(ctx, { gain: range });
|
||||||
osc.connect(g); // -range, range
|
osc.connect(g); // -range, range
|
||||||
@@ -253,7 +265,7 @@ export function applyFM(param, value, begin) {
|
|||||||
|
|
||||||
modulator = fmmod.node;
|
modulator = fmmod.node;
|
||||||
stop = fmmod.stop;
|
stop = fmmod.stop;
|
||||||
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].find((v) => v !== undefined)) {
|
if (![fmAttack, fmDecay, fmSustain, fmRelease, fmVelocity].some((v) => v !== undefined)) {
|
||||||
// no envelope by default
|
// no envelope by default
|
||||||
modulator.connect(param);
|
modulator.connect(param);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
let log = (msg) => console.log(msg);
|
let log = (msg) => console.log(msg);
|
||||||
|
|
||||||
|
export function errorLogger(e, origin = 'cyclist') {
|
||||||
|
//TODO: add some kind of debug flag that enables this while in dev mode
|
||||||
|
// console.error(e);
|
||||||
|
logger(`[${origin}] error: ${e.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
export const logger = (...args) => log(...args);
|
export const logger = (...args) => log(...args);
|
||||||
|
|
||||||
export const setLogger = (fn) => {
|
export const setLogger = (fn) => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getAudioContext } from './superdough.mjs';
|
|||||||
let noiseCache = {};
|
let noiseCache = {};
|
||||||
|
|
||||||
// lazy generates noise buffers and keeps them forever
|
// lazy generates noise buffers and keeps them forever
|
||||||
function getNoiseBuffer(type, density) {
|
export function getNoiseBuffer(type, density) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
if (noiseCache[type]) {
|
if (noiseCache[type]) {
|
||||||
return noiseCache[type];
|
return noiseCache[type];
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "superdough",
|
"name": "superdough",
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
"description": "simple web audio synth and sampler intended for live coding. inspired by superdirt and webdirt.",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import './reverb.mjs';
|
|||||||
import './vowel.mjs';
|
import './vowel.mjs';
|
||||||
import { clamp, nanFallback, _mod, cycleToSeconds, secondsToCycle } from './util.mjs';
|
import { clamp, nanFallback, _mod, cycleToSeconds, secondsToCycle } from './util.mjs';
|
||||||
import workletsUrl from './worklets.mjs?audioworklet';
|
import workletsUrl from './worklets.mjs?audioworklet';
|
||||||
import { createFilter, gainNode, getCompressor, getWorklet } from './helpers.mjs';
|
import { createFilter, gainNode, getCompressor, getWorklet, webAudioTimeout } from './helpers.mjs';
|
||||||
import { map } from 'nanostores';
|
import { map } from 'nanostores';
|
||||||
import { logger } from './logger.mjs';
|
import { logger, errorLogger } from './logger.mjs';
|
||||||
import { loadBuffer } from './sampler.mjs';
|
import { loadBuffer } from './sampler.mjs';
|
||||||
|
|
||||||
export const DEFAULT_MAX_POLYPHONY = 128;
|
export const DEFAULT_MAX_POLYPHONY = 128;
|
||||||
@@ -331,16 +331,18 @@ function getDelay(orbit, delaytime, delayfeedback, t, channels) {
|
|||||||
//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);
|
||||||
if (!delays[orbit]) {
|
if (!orbits[orbit].delayNode) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
const dly = ac.createFeedbackDelay(1, delaytime, delayfeedback);
|
||||||
dly.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..
|
||||||
connectToDestination(dly, channels);
|
connectToOrbit(dly, orbit);
|
||||||
delays[orbit] = dly;
|
orbits[orbit].delayNode = dly;
|
||||||
}
|
}
|
||||||
delays[orbit].delayTime.value !== delaytime && delays[orbit].delayTime.setValueAtTime(delaytime, t);
|
orbits[orbit].delayNode.delayTime.value !== delaytime &&
|
||||||
delays[orbit].feedback.value !== delayfeedback && delays[orbit].feedback.setValueAtTime(delayfeedback, t);
|
orbits[orbit].delayNode.delayTime.setValueAtTime(delaytime, t);
|
||||||
return delays[orbit];
|
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 = {}) {
|
||||||
@@ -365,22 +367,6 @@ export function getLfo(audioContext, begin, end, properties = {}) {
|
|||||||
return getWorklet(audioContext, 'lfo-processor', lfoprops);
|
return getWorklet(audioContext, 'lfo-processor', lfoprops);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getSyncedLfo(audioContext, time, end, cps, cycle, properties = {}) {
|
|
||||||
const frequency = cycle / cps;
|
|
||||||
|
|
||||||
return getWorklet(audioContext, 'lfo-processor', {
|
|
||||||
frequency,
|
|
||||||
depth: 1,
|
|
||||||
skew: 0,
|
|
||||||
phaseoffset: 0,
|
|
||||||
time,
|
|
||||||
end,
|
|
||||||
shape: 1,
|
|
||||||
dcoffset: -0.5,
|
|
||||||
...properties,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
|
function getPhaser(time, end, frequency = 1, depth = 0.5, centerFrequency = 1000, sweep = 2000) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
|
const lfoGain = getLfo(ac, time, end, { frequency, depth: sweep * 2 });
|
||||||
@@ -412,31 +398,73 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reverbs = {};
|
//type orbit {
|
||||||
|
// gain: number,
|
||||||
|
// reverbNode: reverbNode
|
||||||
|
// delayNode: delayNode
|
||||||
|
//}
|
||||||
|
let orbits = {};
|
||||||
|
function connectToOrbit(node, orbit) {
|
||||||
|
if (orbits[orbit] == null) {
|
||||||
|
errorLogger(new Error('target orbit does not exist'), 'superdough');
|
||||||
|
}
|
||||||
|
node.connect(orbits[orbit].gain);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setOrbit(audioContext, orbit, channels) {
|
||||||
|
if (orbits[orbit] == null) {
|
||||||
|
orbits[orbit] = {
|
||||||
|
gain: new GainNode(audioContext, { gain: 1 }),
|
||||||
|
};
|
||||||
|
connectToDestination(orbits[orbit].gain, channels);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function duckOrbit(audioContext, targetOrbit, t, attacktime = 0.1, duckdepth = 1) {
|
||||||
|
const targetArr = [targetOrbit].flat();
|
||||||
|
|
||||||
|
targetArr.forEach((target) => {
|
||||||
|
if (orbits[target] == null) {
|
||||||
|
errorLogger(new Error(`duck target orbit ${target} does not exist`), 'superdough');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
webAudioTimeout(
|
||||||
|
audioContext,
|
||||||
|
() => {
|
||||||
|
orbits[target].gain.gain.cancelScheduledValues(t);
|
||||||
|
const currVal = orbits[target].gain.gain.value;
|
||||||
|
orbits[target].gain.gain.linearRampToValueAtTime(clamp(1 - Math.pow(duckdepth, 0.5), 0.01, currVal), t);
|
||||||
|
orbits[target].gain.gain.exponentialRampToValueAtTime(1, t + Math.max(0.002, attacktime));
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
t - 0.01,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let hasChanged = (now, before) => now !== undefined && now !== before;
|
let hasChanged = (now, before) => now !== undefined && now !== before;
|
||||||
function getReverb(orbit, duration, fade, lp, dim, ir, channels) {
|
function getReverb(orbit, duration, fade, lp, dim, ir) {
|
||||||
// If no reverb has been created for a given orbit, create one
|
// If no reverb has been created for a given orbit, create one
|
||||||
if (!reverbs[orbit]) {
|
if (!orbits[orbit].reverbNode) {
|
||||||
const ac = getAudioContext();
|
const ac = getAudioContext();
|
||||||
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
const reverb = ac.createReverb(duration, fade, lp, dim, ir);
|
||||||
connectToDestination(reverb, channels);
|
connectToOrbit(reverb, orbit);
|
||||||
reverbs[orbit] = reverb;
|
orbits[orbit].reverbNode = reverb;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
hasChanged(duration, reverbs[orbit].duration) ||
|
hasChanged(duration, orbits[orbit].reverbNode.duration) ||
|
||||||
hasChanged(fade, reverbs[orbit].fade) ||
|
hasChanged(fade, orbits[orbit].reverbNode.fade) ||
|
||||||
hasChanged(lp, reverbs[orbit].lp) ||
|
hasChanged(lp, orbits[orbit].reverbNode.lp) ||
|
||||||
hasChanged(dim, reverbs[orbit].dim) ||
|
hasChanged(dim, orbits[orbit].reverbNode.dim) ||
|
||||||
reverbs[orbit].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
|
||||||
reverbs[orbit].generate(duration, fade, lp, dim, ir);
|
orbits[orbit].reverbNode.generate(duration, fade, lp, dim, ir);
|
||||||
}
|
}
|
||||||
return reverbs[orbit];
|
return orbits[orbit].reverbNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let analysers = {},
|
export let analysers = {},
|
||||||
@@ -479,8 +507,7 @@ function effectSend(input, effect, wet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function resetGlobalEffects() {
|
export function resetGlobalEffects() {
|
||||||
delays = {};
|
orbits = {};
|
||||||
reverbs = {};
|
|
||||||
analysers = {};
|
analysers = {};
|
||||||
analysersData = {};
|
analysersData = {};
|
||||||
}
|
}
|
||||||
@@ -533,6 +560,9 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
gain = getDefaultValue('gain'),
|
gain = getDefaultValue('gain'),
|
||||||
postgain = getDefaultValue('postgain'),
|
postgain = getDefaultValue('postgain'),
|
||||||
density = getDefaultValue('density'),
|
density = getDefaultValue('density'),
|
||||||
|
duckorbit,
|
||||||
|
duckattack,
|
||||||
|
duckdepth,
|
||||||
// filters
|
// filters
|
||||||
fanchor = getDefaultValue('fanchor'),
|
fanchor = getDefaultValue('fanchor'),
|
||||||
drive = 0.69,
|
drive = 0.69,
|
||||||
@@ -571,6 +601,7 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
coarse,
|
coarse,
|
||||||
|
|
||||||
crush,
|
crush,
|
||||||
|
dry,
|
||||||
shape,
|
shape,
|
||||||
shapevol = getDefaultValue('shapevol'),
|
shapevol = getDefaultValue('shapevol'),
|
||||||
distort,
|
distort,
|
||||||
@@ -604,7 +635,13 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
const orbitChannels = mapChannelNumbers(
|
const orbitChannels = mapChannelNumbers(
|
||||||
multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'),
|
multiChannelOrbits && orbit > 0 ? [orbit * 2 - 1, orbit * 2] : getDefaultValue('channels'),
|
||||||
);
|
);
|
||||||
|
|
||||||
const channels = value.channels != null ? mapChannelNumbers(value.channels) : orbitChannels;
|
const channels = value.channels != null ? mapChannelNumbers(value.channels) : orbitChannels;
|
||||||
|
setOrbit(ac, orbit, channels, t, cycle, cps);
|
||||||
|
|
||||||
|
if (duckorbit != null) {
|
||||||
|
duckOrbit(ac, duckorbit, t, duckattack, duckdepth);
|
||||||
|
}
|
||||||
|
|
||||||
gain = applyGainCurve(nanFallback(gain, 1));
|
gain = applyGainCurve(nanFallback(gain, 1));
|
||||||
postgain = applyGainCurve(postgain);
|
postgain = applyGainCurve(postgain);
|
||||||
@@ -791,7 +828,6 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
// last gain
|
// last gain
|
||||||
const post = new GainNode(ac, { gain: postgain });
|
const post = new GainNode(ac, { gain: postgain });
|
||||||
chain.push(post);
|
chain.push(post);
|
||||||
connectToDestination(post, channels);
|
|
||||||
|
|
||||||
// delay
|
// delay
|
||||||
let delaySend;
|
let delaySend;
|
||||||
@@ -826,6 +862,14 @@ export const superdough = async (value, t, hapDuration, cps = 0.5, cycle = 0.5)
|
|||||||
analyserSend = effectSend(post, analyserNode, 1);
|
analyserSend = effectSend(post, analyserNode, 1);
|
||||||
audioNodes.push(analyserSend);
|
audioNodes.push(analyserSend);
|
||||||
}
|
}
|
||||||
|
if (dry != null) {
|
||||||
|
dry = applyGainCurve(dry);
|
||||||
|
const dryGain = new GainNode(ac, { gain: dry });
|
||||||
|
chain.push(dryGain);
|
||||||
|
connectToOrbit(dryGain, orbit);
|
||||||
|
} else {
|
||||||
|
connectToOrbit(post, orbit);
|
||||||
|
}
|
||||||
|
|
||||||
// connect chain elements together
|
// connect chain elements together
|
||||||
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
chain.slice(1).reduce((last, current) => last.connect(current), chain[0]);
|
||||||
|
|||||||
@@ -9,12 +9,13 @@ import {
|
|||||||
getVibratoOscillator,
|
getVibratoOscillator,
|
||||||
webAudioTimeout,
|
webAudioTimeout,
|
||||||
getWorklet,
|
getWorklet,
|
||||||
|
noises,
|
||||||
} from './helpers.mjs';
|
} from './helpers.mjs';
|
||||||
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
|
import { getNoiseMix, getNoiseOscillator } from './noise.mjs';
|
||||||
|
|
||||||
const getFrequencyFromValue = (value) => {
|
const getFrequencyFromValue = (value, defaultNote = 36) => {
|
||||||
let { note, freq } = value;
|
let { note, freq } = value;
|
||||||
note = note || 36;
|
note = note || defaultNote;
|
||||||
if (typeof note === 'string') {
|
if (typeof note === 'string') {
|
||||||
note = noteToMidi(note); // e.g. c3 => 48
|
note = noteToMidi(note); // e.g. c3 => 48
|
||||||
}
|
}
|
||||||
@@ -40,7 +41,17 @@ const waveformAliases = [
|
|||||||
['saw', 'sawtooth'],
|
['saw', 'sawtooth'],
|
||||||
['sin', 'sine'],
|
['sin', 'sine'],
|
||||||
];
|
];
|
||||||
const noises = ['pink', 'white', 'brown', 'crackle'];
|
|
||||||
|
function makeSaturationCurve(amount, n_samples) {
|
||||||
|
const k = typeof amount === 'number' ? amount : 50;
|
||||||
|
const curve = new Float32Array(n_samples);
|
||||||
|
|
||||||
|
for (let i = 0; i < n_samples; i++) {
|
||||||
|
const x = (i * 2) / n_samples - 1;
|
||||||
|
curve[i] = Math.tanh(x * k);
|
||||||
|
}
|
||||||
|
return curve;
|
||||||
|
}
|
||||||
|
|
||||||
export function registerSynthSounds() {
|
export function registerSynthSounds() {
|
||||||
[...waveforms].forEach((s) => {
|
[...waveforms].forEach((s) => {
|
||||||
@@ -84,6 +95,75 @@ export function registerSynthSounds() {
|
|||||||
{ type: 'synth', prebake: true },
|
{ type: 'synth', prebake: true },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerSound(
|
||||||
|
'sbd',
|
||||||
|
(t, value, onended) => {
|
||||||
|
const { duration, decay = 0.5, pdecay = 0.5, penv = 36, clip } = value;
|
||||||
|
const ctx = getAudioContext();
|
||||||
|
const attackhold = 0.02;
|
||||||
|
const noiselvl = 1.2;
|
||||||
|
const noisedecay = 0.025;
|
||||||
|
const mixGain = 1;
|
||||||
|
|
||||||
|
const o = ctx.createOscillator();
|
||||||
|
o.type = 'triangle';
|
||||||
|
o.frequency.value = getFrequencyFromValue(value, 29);
|
||||||
|
o.detune.setValueAtTime(penv * 100, 0);
|
||||||
|
o.detune.setValueAtTime(penv * 100, t);
|
||||||
|
o.detune.exponentialRampToValueAtTime(0.001, t + pdecay);
|
||||||
|
const g = gainNode(1);
|
||||||
|
g.gain.setValueAtTime(1, t + attackhold);
|
||||||
|
g.gain.exponentialRampToValueAtTime(0.001, t + attackhold + decay);
|
||||||
|
o.start(t);
|
||||||
|
|
||||||
|
const noise = getNoiseOscillator('brown', t, 2);
|
||||||
|
const noiseGain = gainNode(1);
|
||||||
|
noiseGain.gain.setValueAtTime(noiselvl, t);
|
||||||
|
noiseGain.gain.exponentialRampToValueAtTime(0.001, t + noisedecay);
|
||||||
|
|
||||||
|
const sat = new WaveShaperNode(ctx);
|
||||||
|
// tri to sine diode shaper emulation
|
||||||
|
sat.curve = makeSaturationCurve(2, ctx.sampleRate);
|
||||||
|
|
||||||
|
const mix = gainNode(mixGain);
|
||||||
|
|
||||||
|
o.onended = () => {
|
||||||
|
o.disconnect();
|
||||||
|
g.disconnect();
|
||||||
|
sat.disconnect();
|
||||||
|
noise.node.disconnect();
|
||||||
|
noiseGain.disconnect();
|
||||||
|
mix.disconnect();
|
||||||
|
onended();
|
||||||
|
};
|
||||||
|
|
||||||
|
const node = o.connect(sat).connect(g).connect(mix);
|
||||||
|
noise.node.connect(noiseGain).connect(mix);
|
||||||
|
|
||||||
|
const holdEnd = t + decay;
|
||||||
|
let end = holdEnd + 0.01;
|
||||||
|
if (clip != null) {
|
||||||
|
end = Math.min(t + clip * duration, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
// prevent clicking
|
||||||
|
mix.gain.setValueAtTime(mixGain, end - 0.01);
|
||||||
|
mix.gain.linearRampToValueAtTime(0, end);
|
||||||
|
|
||||||
|
o.stop(end);
|
||||||
|
noise.stop(end);
|
||||||
|
|
||||||
|
return {
|
||||||
|
node,
|
||||||
|
stop: (endTime) => {
|
||||||
|
o.stop(endTime);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{ type: 'synth', prebake: true },
|
||||||
|
);
|
||||||
|
|
||||||
registerSound(
|
registerSound(
|
||||||
'supersaw',
|
'supersaw',
|
||||||
(begin, value, onended) => {
|
(begin, value, onended) => {
|
||||||
@@ -121,10 +201,7 @@ export function registerSynthSounds() {
|
|||||||
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);
|
||||||
// https://codeberg.org/uzu/strudel/issues/1428
|
|
||||||
// if you think about re-enabling this, please test with fm > 1 first
|
|
||||||
// it's like 10x gain, so it's really dangerous
|
|
||||||
let envGain = gainNode(1);
|
let envGain = gainNode(1);
|
||||||
envGain = o.connect(envGain);
|
envGain = o.connect(envGain);
|
||||||
|
|
||||||
@@ -136,7 +213,7 @@ export function registerSynthSounds() {
|
|||||||
destroyAudioWorkletNode(o);
|
destroyAudioWorkletNode(o);
|
||||||
envGain.disconnect();
|
envGain.disconnect();
|
||||||
onended();
|
onended();
|
||||||
// fm?.stop();
|
fm?.stop();
|
||||||
vibratoOscillator?.stop();
|
vibratoOscillator?.stop();
|
||||||
},
|
},
|
||||||
begin,
|
begin,
|
||||||
|
|||||||
@@ -8,18 +8,28 @@ import FFT from './fft.js';
|
|||||||
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
const clamp = (num, min, max) => Math.min(Math.max(num, min), max);
|
||||||
const _mod = (n, m) => ((n % m) + m) % m;
|
const _mod = (n, m) => ((n % m) + m) % m;
|
||||||
|
|
||||||
|
// Restrict phase to the range [0, maxPhase) via wrapping
|
||||||
|
function wrapPhase(phase, maxPhase = 1) {
|
||||||
|
if (phase >= maxPhase) {
|
||||||
|
phase -= maxPhase;
|
||||||
|
} else if (phase < 0) {
|
||||||
|
phase += maxPhase;
|
||||||
|
}
|
||||||
|
return phase;
|
||||||
|
}
|
||||||
const blockSize = 128;
|
const blockSize = 128;
|
||||||
// adjust waveshape to remove frequencies above nyquist to 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) {
|
||||||
// 0 <= phase < 1
|
dt = Math.min(dt, 1 - dt);
|
||||||
|
// Start of cycle
|
||||||
if (phase < dt) {
|
if (phase < dt) {
|
||||||
phase /= dt;
|
phase /= dt;
|
||||||
// 2 * (phase - phase^2/2 - 0.5)
|
// 2 * (phase - phase^2/2 - 0.5)
|
||||||
return phase + phase - phase * phase - 1;
|
return phase + phase - phase * phase - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -1 < phase < 0
|
// End of cycle
|
||||||
else if (phase > 1 - dt) {
|
else if (phase > 1 - dt) {
|
||||||
phase = (phase - 1) / dt;
|
phase = (phase - 1) / dt;
|
||||||
// 2 * (phase^2/2 + phase + 0.5)
|
// 2 * (phase^2/2 + phase + 0.5)
|
||||||
@@ -115,7 +125,6 @@ class LFOProcessor extends AudioWorkletProcessor {
|
|||||||
|
|
||||||
process(inputs, outputs, parameters) {
|
process(inputs, outputs, parameters) {
|
||||||
const begin = parameters['begin'][0];
|
const begin = parameters['begin'][0];
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
if (currentTime >= parameters.end[0]) {
|
if (currentTime >= parameters.end[0]) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -143,7 +152,6 @@ class LFOProcessor extends AudioWorkletProcessor {
|
|||||||
if (this.phase == null) {
|
if (this.phase == null) {
|
||||||
this.phase = _mod(time * frequency + phaseoffset, 1);
|
this.phase = _mod(time * frequency + phaseoffset, 1);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const dt = frequency / sampleRate;
|
const dt = frequency / sampleRate;
|
||||||
for (let n = 0; n < blockSize; n++) {
|
for (let n = 0; n < blockSize; n++) {
|
||||||
for (let i = 0; i < output.length; i++) {
|
for (let i = 0; i < output.length; i++) {
|
||||||
@@ -305,7 +313,6 @@ class LadderProcessor extends AudioWorkletProcessor {
|
|||||||
const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000);
|
const drive = clamp(Math.exp(parameters.drive[0]), 0.1, 2000);
|
||||||
|
|
||||||
let cutoff = parameters.frequency[0];
|
let cutoff = parameters.frequency[0];
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
cutoff = (cutoff * 2 * _PI) / sampleRate;
|
cutoff = (cutoff * 2 * _PI) / sampleRate;
|
||||||
cutoff = cutoff > 1 ? 1 : cutoff;
|
cutoff = cutoff > 1 ? 1 : cutoff;
|
||||||
|
|
||||||
@@ -438,18 +445,13 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
process(input, outputs, params) {
|
process(input, outputs, params) {
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
if (currentTime <= params.begin[0]) {
|
if (currentTime <= params.begin[0]) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
if (currentTime >= params.end[0]) {
|
if (currentTime >= params.end[0]) {
|
||||||
// this.port.postMessage({ type: 'onended' });
|
// this.port.postMessage({ type: 'onended' });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let frequency = params.frequency[0];
|
|
||||||
//apply detune in cents
|
|
||||||
frequency = frequency * Math.pow(2, params.detune[0] / 1200);
|
|
||||||
|
|
||||||
const output = outputs[0];
|
const output = outputs[0];
|
||||||
const voices = params.voices[0];
|
const voices = params.voices[0];
|
||||||
@@ -460,9 +462,6 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
|
|
||||||
for (let n = 0; n < voices; n++) {
|
for (let n = 0; n < voices; n++) {
|
||||||
const isOdd = (n & 1) == 1;
|
const isOdd = (n & 1) == 1;
|
||||||
|
|
||||||
//applies unison "spread" detune in semitones
|
|
||||||
const freq = applySemitoneDetuneToFrequency(frequency, getUnisonDetune(voices, freqspread, n));
|
|
||||||
let gainL = gain1;
|
let gainL = gain1;
|
||||||
let gainR = gain2;
|
let gainR = gain2;
|
||||||
// invert right and left gain
|
// invert right and left gain
|
||||||
@@ -470,21 +469,21 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
gainL = gain2;
|
gainL = gain2;
|
||||||
gainR = gain1;
|
gainR = gain1;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
const dt = freq / sampleRate;
|
|
||||||
|
|
||||||
for (let i = 0; i < output[0].length; i++) {
|
for (let i = 0; i < output[0].length; i++) {
|
||||||
|
// Main detuning
|
||||||
|
let freq = applySemitoneDetuneToFrequency(params.frequency[i] ?? params.frequency[0], params.detune[0] / 100);
|
||||||
|
// Individual voice detuning
|
||||||
|
freq = applySemitoneDetuneToFrequency(freq, getUnisonDetune(voices, freqspread, n));
|
||||||
|
// We must wrap this here because it is passed into sawblep below which
|
||||||
|
// has domain [0, 1]
|
||||||
|
const dt = _mod(freq / sampleRate, 1);
|
||||||
this.phase[n] = this.phase[n] ?? Math.random();
|
this.phase[n] = this.phase[n] ?? Math.random();
|
||||||
const v = waveshapes.sawblep(this.phase[n], dt);
|
const v = waveshapes.sawblep(this.phase[n], dt);
|
||||||
|
|
||||||
output[0][i] = output[0][i] + v * gainL;
|
output[0][i] = output[0][i] + v * gainL;
|
||||||
output[1][i] = output[1][i] + v * gainR;
|
output[1][i] = output[1][i] + v * gainR;
|
||||||
|
|
||||||
this.phase[n] += dt;
|
this.phase[n] = wrapPhase(this.phase[n] + dt);
|
||||||
|
|
||||||
if (this.phase[n] > 1.0) {
|
|
||||||
this.phase[n] = this.phase[n] - 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@@ -493,7 +492,7 @@ class SuperSawOscillatorProcessor extends AudioWorkletProcessor {
|
|||||||
|
|
||||||
registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor);
|
registerProcessor('supersaw-oscillator', SuperSawOscillatorProcessor);
|
||||||
|
|
||||||
// Phase Vocoder sourced from // sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
// Phase Vocoder sourced from https://github.com/olvb/phaze/tree/master?tab=readme-ov-file
|
||||||
const BUFFERED_BLOCK_SIZE = 2048;
|
const BUFFERED_BLOCK_SIZE = 2048;
|
||||||
|
|
||||||
function genHannWindow(length) {
|
function genHannWindow(length) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/tonal",
|
"name": "@strudel/tonal",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Tonal functions for strudel",
|
"description": "Tonal functions for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
+22
-17
@@ -88,13 +88,14 @@ function scaleOffset(scale, offset, note) {
|
|||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
* @memberof Pattern
|
* @memberof Pattern
|
||||||
* @name transpose
|
* @name transpose
|
||||||
|
* @synonyms trans
|
||||||
* @example
|
* @example
|
||||||
* "c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).note()
|
* "c2 c3".fast(2).transpose("<0 -2 5 3>".slow(2)).note()
|
||||||
* @example
|
* @example
|
||||||
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
|
* "c2 c3".fast(2).transpose("<1P -2M 4P 3m>".slow(2)).note()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const transpose = register('transpose', function (intervalOrSemitones, pat) {
|
export const { transpose, trans } = register(['transpose', 'trans'], function transposeFn(intervalOrSemitones, pat) {
|
||||||
return pat.withHap((hap) => {
|
return pat.withHap((hap) => {
|
||||||
const note = hap.value.note ?? hap.value;
|
const note = hap.value.note ?? hap.value;
|
||||||
if (typeof note === 'number') {
|
if (typeof note === 'number') {
|
||||||
@@ -142,6 +143,7 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa
|
|||||||
* @name scaleTranspose
|
* @name scaleTranspose
|
||||||
* @param {offset} offset number of steps inside the scale
|
* @param {offset} offset number of steps inside the scale
|
||||||
* @returns Pattern
|
* @returns Pattern
|
||||||
|
* @synonyms scaleTrans, strans
|
||||||
* @example
|
* @example
|
||||||
* "-8 [2,4,6]"
|
* "-8 [2,4,6]"
|
||||||
* .scale('C4 bebop major')
|
* .scale('C4 bebop major')
|
||||||
@@ -149,22 +151,25 @@ export const transpose = register('transpose', function (intervalOrSemitones, pa
|
|||||||
* .note()
|
* .note()
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const scaleTranspose = register('scaleTranspose', function (offset /* : number | string */, pat) {
|
export const { scaleTranspose, scaleTrans, strans } = register(
|
||||||
return pat.withHap((hap) => {
|
['scaleTranspose', 'scaleTrans', 'strans'],
|
||||||
if (!hap.context.scale) {
|
function (offset /* : number | string */, pat) {
|
||||||
throw new Error('can only use scaleTranspose after .scale');
|
return pat.withHap((hap) => {
|
||||||
}
|
if (!hap.context.scale) {
|
||||||
if (typeof hap.value === 'object')
|
throw new Error('can only use scaleTranspose after .scale');
|
||||||
return hap.withValue(() => ({
|
}
|
||||||
...hap.value,
|
if (typeof hap.value === 'object')
|
||||||
note: scaleOffset(hap.context.scale, Number(offset), hap.value.note),
|
return hap.withValue(() => ({
|
||||||
}));
|
...hap.value,
|
||||||
if (typeof hap.value !== 'string') {
|
note: scaleOffset(hap.context.scale, Number(offset), hap.value.note),
|
||||||
throw new Error('can only use scaleTranspose with notes');
|
}));
|
||||||
}
|
if (typeof hap.value !== 'string') {
|
||||||
return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value));
|
throw new Error('can only use scaleTranspose with notes');
|
||||||
});
|
}
|
||||||
});
|
return hap.withValue(() => scaleOffset(hap.context.scale, Number(offset), hap.value));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
|
* Turns numbers into notes in the scale (zero indexed). Also sets scale for other scale operations, like {@link Pattern#scaleTranspose}.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/transpiler",
|
"name": "@strudel/transpiler",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
"description": "Transpiler for strudel user code. Converts syntactically correct but semantically meaningless JS into evaluatable strudel code.",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/web",
|
"name": "@strudel/web",
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
|
"description": "Easy to setup, opiniated bundle of Strudel for the browser.",
|
||||||
"module": "web.mjs",
|
"module": "web.mjs",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/webaudio",
|
"name": "@strudel/webaudio",
|
||||||
"version": "1.2.3",
|
"version": "1.2.4",
|
||||||
"description": "Web Audio helpers for Strudel",
|
"description": "Web Audio helpers for Strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@strudel/xen",
|
"name": "@strudel/xen",
|
||||||
"version": "1.2.2",
|
"version": "1.2.3",
|
||||||
"description": "Xenharmonic API for strudel",
|
"description": "Xenharmonic API for strudel",
|
||||||
"main": "index.mjs",
|
"main": "index.mjs",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -3036,6 +3036,145 @@ exports[`runs examples > example "dry" 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/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 → 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 → 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 ]",
|
||||||
|
"[ 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 → 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 → 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 → 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 → 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 ]",
|
||||||
|
"[ 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 → 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 → 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 → 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 → 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 ]",
|
||||||
|
"[ 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 → 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 → 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 → 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 → 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 ]",
|
||||||
|
"[ 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 → 4/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "duckdepth" example index 0 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 → 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 → 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 ]",
|
||||||
|
"[ 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 → 1/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||||
|
"[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:0.9 ]",
|
||||||
|
"[ 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.2 duckdepth:0.9 ]",
|
||||||
|
"[ 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.2 duckdepth:0.9 ]",
|
||||||
|
"[ 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.2 duckdepth:0.9 ]",
|
||||||
|
"[ 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.2 duckdepth:0.9 ]",
|
||||||
|
"[ 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.2 duckdepth:0.6 ]",
|
||||||
|
"[ 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.2 duckdepth:0.6 ]",
|
||||||
|
"[ 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.2 duckdepth:0.6 ]",
|
||||||
|
"[ 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.2 duckdepth:0.6 ]",
|
||||||
|
"[ 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.2 duckdepth:0.6 ]",
|
||||||
|
"[ 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:0 ]",
|
||||||
|
"[ 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:0 ]",
|
||||||
|
"[ 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:0 ]",
|
||||||
|
"[ 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:0 ]",
|
||||||
|
"[ 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:0 ]",
|
||||||
|
"[ 31/8 → 4/1 | note:C4 s:sawtooth delay:0.7 orbit:2 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "duckorbit" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/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/2 → 9/16 | 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 ]",
|
||||||
|
"[ 7/8 → 15/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 1/1 → 17/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 5/4 → 21/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 3/2 → 25/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 27/16 → 7/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 15/8 → 31/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 2/1 → 33/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 9/4 → 37/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 5/2 → 41/16 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 43/16 → 11/4 | s:bd n:4 duckorbit:2 duckattack:0.2 duckdepth:1 ]",
|
||||||
|
"[ 23/8 → 47/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 ]",
|
||||||
|
"[ 13/4 → 53/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 ]",
|
||||||
|
"[ 59/16 → 15/4 | 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 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
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 ]",
|
||||||
@@ -3271,6 +3410,39 @@ exports[`runs examples > example "euclidRot" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "euclidish" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/12 | s:hh pan:0.5 ]",
|
||||||
|
"[ 13/84 → 5/21 | s:hh pan:0.5606253170575308 ]",
|
||||||
|
"[ 15/56 → 59/168 | s:hh pan:0.604413082836085 ]",
|
||||||
|
"[ 71/168 → 85/168 | s:hh pan:0.6629314122869361 ]",
|
||||||
|
"[ 97/168 → 37/56 | s:hh pan:0.7190455010067492 ]",
|
||||||
|
"[ 29/42 → 65/84 | s:hh pan:0.7580531369037533 ]",
|
||||||
|
"[ 71/84 → 13/14 | s:hh pan:0.8080762739548087 ]",
|
||||||
|
"[ 1/1 → 13/12 | s:hh pan:0.8535533905932737 ]",
|
||||||
|
"[ 451099417/393511398 → 322594689/262340932 | s:hh pan:0.891768001805729 ]",
|
||||||
|
"[ 335923379/262340932 → 536677685/393511398 | s:hh pan:0.9222657853371297 ]",
|
||||||
|
"[ 1122946175/787022796 → 99044284/65585233 | s:hh pan:0.9501869591788796 ]",
|
||||||
|
"[ 1238122213/787022796 → 651853723/393511398 | s:hh pan:0.9721673436944069 ]",
|
||||||
|
"[ 335923379/196755699 → 469759583/262340932 | s:hh pan:0.9868472639237561 ]",
|
||||||
|
"[ 729434777/393511398 → 1524454787/787022796 | s:hh pan:0.9967009321321423 ]",
|
||||||
|
"[ 2/1 → 25/12 | s:hh pan:1 ]",
|
||||||
|
"[ 15/7 → 187/84 | s:hh pan:0.9968561049466214 ]",
|
||||||
|
"[ 16/7 → 199/84 | s:hh pan:0.9874639560909118 ]",
|
||||||
|
"[ 17/7 → 211/84 | s:hh pan:0.9719416651541839 ]",
|
||||||
|
"[ 18/7 → 223/84 | s:hh pan:0.9504844339512095 ]",
|
||||||
|
"[ 19/7 → 235/84 | s:hh pan:0.9233620996141421 ]",
|
||||||
|
"[ 20/7 → 247/84 | s:hh pan:0.890915741234015 ]",
|
||||||
|
"[ 3/1 → 37/12 | s:hh pan:0.8535533905932737 ]",
|
||||||
|
"[ 1238122213/393511398 → 847276553/262340932 | s:hh pan:0.8106731928589048 ]",
|
||||||
|
"[ 860605243/262340932 → 1323700481/393511398 | s:hh pan:0.7677528833339 ]",
|
||||||
|
"[ 2696991767/787022796 → 230214750/65585233 | s:hh pan:0.7175585019834292 ]",
|
||||||
|
"[ 2812167805/787022796 → 1438876519/393511398 | s:hh pan:0.6644931595798675 ]",
|
||||||
|
"[ 729434777/196755699 → 994441447/262340932 | s:hh pan:0.6139286689554151 ]",
|
||||||
|
"[ 1516457573/393511398 → 3098500379/787022796 | s:hh pan:0.557342689325327 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "every" example index 0 1`] = `
|
exports[`runs examples > example "every" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:g3 ]",
|
"[ 0/1 → 1/4 | note:g3 ]",
|
||||||
@@ -3706,6 +3878,144 @@ exports[`runs examples > example "fmsustain" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "fmwave" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:D3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:E3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:F3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:G3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:D3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:E3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:F3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:G3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:D3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:E3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:F3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:G3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:D3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:E3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:F3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:G3 s:sine fmwave:sine fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:D3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:E3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:F3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:G3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:D3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:E3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:F3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:G3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:D3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:E3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:F3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:G3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:D3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:E3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:F3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:G3 s:sine fmwave:square fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:D3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:E3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:F3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:G3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:D3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:E3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:F3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:G3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:D3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:E3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:F3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:G3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:D3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:E3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:F3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:G3 s:sine fmwave:sawtooth fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:D3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:E3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:F3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:G3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:D3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:E3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:F3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:G3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:D3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:E3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:F3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:G3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:D3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:E3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:F3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:G3 s:sine fmwave:crackle fmi:4 fmh:2.01 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "fmwave" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/16 | note:50 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 1/16 → 1/8 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 1/8 → 3/16 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 3/16 → 1/4 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 1/4 → 5/16 | note:50 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 5/16 → 3/8 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 3/8 → 7/16 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 7/16 → 1/2 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 1/2 → 9/16 | note:50 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 9/16 → 5/8 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 5/8 → 11/16 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 11/16 → 3/4 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 3/4 → 13/16 | note:50 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 13/16 → 7/8 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 7/8 → 15/16 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 15/16 → 1/1 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 1/1 → 17/16 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 17/16 → 9/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 9/8 → 19/16 | note:64 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 19/16 → 5/4 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 5/4 → 21/16 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 21/16 → 11/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 11/8 → 23/16 | note:64 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 23/16 → 3/2 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 3/2 → 25/16 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 25/16 → 13/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 13/8 → 27/16 | note:64 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 27/16 → 7/4 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 7/4 → 29/16 | note:57 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 29/16 → 15/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 15/8 → 31/16 | note:64 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 31/16 → 2/1 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 2/1 → 33/16 | note:53 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 33/16 → 17/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 17/8 → 35/16 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 35/16 → 9/4 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 9/4 → 37/16 | note:53 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 37/16 → 19/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 19/8 → 39/16 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 39/16 → 5/2 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 5/2 → 41/16 | note:53 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 41/16 → 21/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 21/8 → 43/16 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 43/16 → 11/4 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 11/4 → 45/16 | note:53 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 45/16 → 23/8 | note:60 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 23/8 → 47/16 | note:65 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 47/16 → 3/1 | note:69 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 3/1 → 49/16 | note:55 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 49/16 → 25/8 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 25/8 → 51/16 | note:67 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 51/16 → 13/4 | note:71 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 13/4 → 53/16 | note:55 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 53/16 → 27/8 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 27/8 → 55/16 | note:67 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 55/16 → 7/2 | note:71 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 7/2 → 57/16 | note:55 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 57/16 → 29/8 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 29/8 → 59/16 | note:67 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 59/16 → 15/4 | note:71 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 15/4 → 61/16 | note:55 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 61/16 → 31/8 | note:62 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 31/8 → 63/16 | note:67 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
"[ 63/16 → 4/1 | note:71 s:sawtooth fmwave:brown fmi:0.6 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "focus" example index 0 1`] = `
|
exports[`runs examples > example "focus" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | s:sd ]",
|
"[ 0/1 → 1/8 | s:sd ]",
|
||||||
@@ -5147,6 +5457,40 @@ exports[`runs examples > example "lock" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "log" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/2 | s:bd ]",
|
||||||
|
"[ 1/2 → 1/1 | s:sd ]",
|
||||||
|
"[ 1/1 → 3/2 | s:bd ]",
|
||||||
|
"[ 3/2 → 2/1 | s:sd ]",
|
||||||
|
"[ 2/1 → 5/2 | s:bd ]",
|
||||||
|
"[ 5/2 → 3/1 | s:sd ]",
|
||||||
|
"[ 3/1 → 7/2 | s:bd ]",
|
||||||
|
"[ 7/2 → 4/1 | s:sd ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "logValues" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ (0/1 → 1/3) ⇝ 1/2 | s:bd gain:0.25 n:2 ]",
|
||||||
|
"[ 0/1 ⇜ (1/3 → 1/2) | s:bd gain:0.5 n:1 ]",
|
||||||
|
"[ (1/2 → 2/3) ⇝ 1/1 | s:sd gain:0.5 n:1 ]",
|
||||||
|
"[ 1/2 ⇜ (2/3 → 1/1) | s:sd gain:1 n:0 ]",
|
||||||
|
"[ (1/1 → 4/3) ⇝ 3/2 | s:bd gain:0.25 n:2 ]",
|
||||||
|
"[ 1/1 ⇜ (4/3 → 3/2) | s:bd gain:0.5 n:1 ]",
|
||||||
|
"[ (3/2 → 5/3) ⇝ 2/1 | s:sd gain:0.5 n:1 ]",
|
||||||
|
"[ 3/2 ⇜ (5/3 → 2/1) | s:sd gain:1 n:0 ]",
|
||||||
|
"[ (2/1 → 7/3) ⇝ 5/2 | s:bd gain:0.25 n:2 ]",
|
||||||
|
"[ 2/1 ⇜ (7/3 → 5/2) | s:bd gain:0.5 n:1 ]",
|
||||||
|
"[ (5/2 → 8/3) ⇝ 3/1 | s:sd gain:0.5 n:1 ]",
|
||||||
|
"[ 5/2 ⇜ (8/3 → 3/1) | s:sd gain:1 n:0 ]",
|
||||||
|
"[ (3/1 → 10/3) ⇝ 7/2 | s:bd gain:0.25 n:2 ]",
|
||||||
|
"[ 3/1 ⇜ (10/3 → 7/2) | s:bd gain:0.5 n:1 ]",
|
||||||
|
"[ (7/2 → 11/3) ⇝ 4/1 | s:sd gain:0.5 n:1 ]",
|
||||||
|
"[ 7/2 ⇜ (11/3 → 4/1) | s:sd gain:1 n:0 ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "loop" example index 0 1`] = `
|
exports[`runs examples > example "loop" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/1 | s:casio loop:1 ]",
|
"[ 0/1 → 1/1 | s:casio loop:1 ]",
|
||||||
@@ -5826,6 +6170,48 @@ exports[`runs examples > example "miditouch" example index 0 1`] = `
|
|||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "morph" example index 0 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:hh ]",
|
||||||
|
"[ 25/112 → 39/112 | s:hh ]",
|
||||||
|
"[ 27/56 → 17/28 | s:hh ]",
|
||||||
|
"[ 83/112 → 97/112 | s:hh ]",
|
||||||
|
"[ 1/1 → 9/8 | s:hh ]",
|
||||||
|
"[ 137/112 → 151/112 | s:hh ]",
|
||||||
|
"[ 83/56 → 45/28 | s:hh ]",
|
||||||
|
"[ 195/112 → 209/112 | s:hh ]",
|
||||||
|
"[ 2/1 → 17/8 | s:hh ]",
|
||||||
|
"[ 249/112 → 263/112 | s:hh ]",
|
||||||
|
"[ 139/56 → 73/28 | s:hh ]",
|
||||||
|
"[ 307/112 → 321/112 | s:hh ]",
|
||||||
|
"[ 3/1 → 25/8 | s:hh ]",
|
||||||
|
"[ 361/112 → 375/112 | s:hh ]",
|
||||||
|
"[ 195/56 → 101/28 | s:hh ]",
|
||||||
|
"[ 419/112 → 433/112 | s:hh ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`runs examples > example "morph" example index 1 1`] = `
|
||||||
|
[
|
||||||
|
"[ 0/1 → 1/8 | s:hh ]",
|
||||||
|
"[ 11/56 → 9/28 | s:hh ]",
|
||||||
|
"[ 13/28 → 33/56 | s:hh ]",
|
||||||
|
"[ 41/56 → 6/7 | s:hh ]",
|
||||||
|
"[ 1/1 → 9/8 | s:hh ]",
|
||||||
|
"[ 303934523/262340932 → 673454279/524681864 | s:hh ]",
|
||||||
|
"[ 188758485/131170466 → 820619173/524681864 | s:hh ]",
|
||||||
|
"[ 451099417/262340932 → 967784067/524681864 | s:hh ]",
|
||||||
|
"[ 2/1 → 17/8 | s:hh ]",
|
||||||
|
"[ 15/7 → 127/56 | s:hh ]",
|
||||||
|
"[ 17/7 → 143/56 | s:hh ]",
|
||||||
|
"[ 19/7 → 159/56 | s:hh ]",
|
||||||
|
"[ 3/1 → 25/8 | s:hh ]",
|
||||||
|
"[ 828616387/262340932 → 1722818007/524681864 | s:hh ]",
|
||||||
|
"[ 451099417/131170466 → 1869982901/524681864 | s:hh ]",
|
||||||
|
"[ 975781281/262340932 → 2017147795/524681864 | s:hh ]",
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`runs examples > example "mousex" example index 0 1`] = `
|
exports[`runs examples > example "mousex" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/4 | note:C3 ]",
|
"[ 0/1 → 1/4 | note:C3 ]",
|
||||||
|
|||||||
Reference in New Issue
Block a user