Merge pull request #1125 from tidalcycles/viz-doc

doc: visual functions + refactor onPaint
This commit is contained in:
Felix Roos 2024-06-03 22:40:21 +02:00 committed by GitHub
commit 364f511a7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 646 additions and 348 deletions

View File

@ -140,27 +140,25 @@ export class StrudelMirror {
this.root = root;
this.miniLocations = [];
this.widgets = [];
this.painters = [];
this.drawTime = drawTime;
this.drawContext = drawContext;
this.onDraw = onDraw || this.draw;
this.id = id || s4();
this.drawer = new Drawer((haps, time) => {
this.drawer = new Drawer((haps, time, _, painters) => {
const currentFrame = haps.filter((hap) => hap.isActive(time));
this.highlight(currentFrame, time);
this.onDraw(haps, time, this.painters);
this.onDraw(haps, time, painters);
}, drawTime);
this.prebaked = prebake();
autodraw && this.drawFirstFrame();
this.repl = repl({
...replOptions,
id,
onToggle: (started) => {
replOptions?.onToggle?.(started);
if (started) {
this.adjustDrawTime();
this.drawer.start(this.repl.scheduler);
// stop other repls when this one is started
document.dispatchEvent(
@ -171,20 +169,11 @@ export class StrudelMirror {
} else {
this.drawer.stop();
updateMiniLocations(this.editor, []);
cleanupDraw(false);
cleanupDraw(true, id);
}
},
beforeEval: async () => {
cleanupDraw();
this.painters = [];
const self = this;
// this is similar to repl.mjs > injectPatternMethods
// maybe there is a solution without prototype hacking, but hey, it works
// we need to do this befor every eval to make sure it works with multiple StrudelMirror's side by side
Pattern.prototype.onPaint = function (onPaint) {
self.painters.push(onPaint);
return this;
};
cleanupDraw(true, id);
await this.prebaked;
await replOptions?.beforeEval?.();
},
@ -198,8 +187,11 @@ export class StrudelMirror {
updateWidgets(this.editor, widgets);
updateMiniLocations(this.editor, this.miniLocations);
replOptions?.afterEval?.(options);
this.adjustDrawTime();
this.drawer.invalidate();
// if no painters are set (.onPaint was not called), then we only need the present moment (for highlighting)
const drawTime = options.pattern.getPainters().length ? this.drawTime : [0, 0];
this.drawer.setDrawTime(drawTime);
// invalidate drawer after we've set the appropriate drawTime
this.drawer.invalidate(this.repl.scheduler);
},
});
this.editor = initEditor({
@ -234,13 +226,8 @@ export class StrudelMirror {
};
document.addEventListener('start-repl', this.onStartRepl);
}
// adjusts draw time depending on if there are painters
adjustDrawTime() {
// when no painters are set, [0,0] is enough (just highlighting)
this.drawer.setDrawTime(this.painters.length ? this.drawTime : [0, 0]);
}
draw(haps, time) {
this.painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime));
draw(haps, time, painters) {
painters?.forEach((painter) => painter(this.drawContext, time, haps, this.drawTime));
}
async drawFirstFrame() {
if (!this.onDraw) {
@ -252,7 +239,7 @@ export class StrudelMirror {
await this.repl.evaluate(this.code, false);
this.drawer.invalidate(this.repl.scheduler, -0.001);
// draw at -0.001 to avoid haps at 0 to be visualized as active
this.onDraw?.(this.drawer.visibleHaps, -0.001, this.painters);
this.onDraw?.(this.drawer.visibleHaps, -0.001, this.drawer.painters);
} catch (err) {
console.warn('first frame could not be painted');
}

View File

@ -12,7 +12,7 @@ export const settings = {
gutterBackground: 'transparent',
gutterForeground: '#0f380f',
light: true,
customStyle: '.cm-line { line-height: 1 }',
// customStyle: '.cm-line { line-height: 1 }',
};
export default createTheme({
theme: 'light',

View File

@ -17,7 +17,7 @@ export const settings = {
lineBackground: '#00000040',
gutterBackground: 'transparent',
gutterForeground: '#8a919966',
customStyle: '.cm-line { line-height: 1 }',
// customStyle: '.cm-line { line-height: 1 }',
};
let punctuation = colorD;

View File

@ -191,7 +191,7 @@ export const { attack, att } = registerControl('attack', 'att');
* note("c e g b g e")
* .fm(4)
* .fmh("<1 2 1.5 1.61>")
* .scope()
* ._scope()
*
*/
export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh');
@ -205,7 +205,7 @@ export const { fmh } = registerControl(['fmh', 'fmi'], 'fmh');
* @example
* note("c e g b g e")
* .fm("<0 1 2 8 32>")
* .scope()
* ._scope()
*
*/
export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm');
@ -221,7 +221,7 @@ export const { fmi, fm } = registerControl(['fmi', 'fmh'], 'fm');
* .fmdecay(.2)
* .fmsustain(0)
* .fmenv("<exp lin>")
* .scope()
* ._scope()
*
*/
export const { fmenv } = registerControl('fmenv');
@ -234,7 +234,7 @@ export const { fmenv } = registerControl('fmenv');
* note("c e g b g e")
* .fm(4)
* .fmattack("<0 .05 .1 .2>")
* .scope()
* ._scope()
*
*/
export const { fmattack } = registerControl('fmattack');
@ -248,7 +248,7 @@ export const { fmattack } = registerControl('fmattack');
* .fm(4)
* .fmdecay("<.01 .05 .1 .2>")
* .fmsustain(.4)
* .scope()
* ._scope()
*
*/
export const { fmdecay } = registerControl('fmdecay');
@ -262,7 +262,7 @@ export const { fmdecay } = registerControl('fmdecay');
* .fm(4)
* .fmdecay(.1)
* .fmsustain("<1 .75 .5 0>")
* .scope()
* ._scope()
*
*/
export const { fmsustain } = registerControl('fmsustain');
@ -392,7 +392,7 @@ export const { loop } = registerControl('loop');
* @synonyms loopb
* @example
* s("space").loop(1)
* .loopBegin("<0 .125 .25>").scope()
* .loopBegin("<0 .125 .25>")._scope()
*/
export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb');
/**
@ -405,7 +405,7 @@ export const { loopBegin, loopb } = registerControl('loopBegin', 'loopb');
* @synonyms loope
* @example
* s("space").loop(1)
* .loopEnd("<1 .75 .5 .25>").scope()
* .loopEnd("<1 .75 .5 .25>")._scope()
*/
export const { loopEnd, loope } = registerControl('loopEnd', 'loope');
/**
@ -800,10 +800,12 @@ export const { fanchor } = registerControl('fanchor');
* @example
* note("a e")
* .vib("<.5 1 2 4 8 16>")
* ._scope()
* @example
* // change the modulation depth with ":"
* note("a e")
* .vib("<.5 1 2 4 8 16>:12")
* ._scope()
*/
export const { vib, vibrato, v } = registerControl(['vib', 'vibmod'], 'vibrato', 'v');
/**
@ -824,10 +826,12 @@ export const { noise } = registerControl('noise');
* @example
* note("a e").vib(4)
* .vibmod("<.25 .5 1 2 12>")
* ._scope()
* @example
* // change the vibrato frequency with ":"
* note("a e")
* .vibmod("<.25 .5 1 2 12>:8")
* ._scope()
*/
export const { vibmod, vmod } = registerControl(['vibmod', 'vib'], 'vmod');
export const { hcutoff, hpf, hp } = registerControl(['hcutoff', 'hresonance', 'hpenv'], 'hpf', 'hp');

View File

@ -8,8 +8,19 @@ import createClock from './zyklus.mjs';
import { logger } from './logger.mjs';
export class Cyclist {
constructor({ interval, onTrigger, onToggle, onError, getTime, latency = 0.1, setInterval, clearInterval }) {
constructor({
interval,
onTrigger,
onToggle,
onError,
getTime,
latency = 0.1,
setInterval,
clearInterval,
beforeStart,
}) {
this.started = false;
this.beforeStart = beforeStart;
this.cps = 0.5;
this.num_ticks_since_cps_change = 0;
this.lastTick = 0; // absolute time when last tick (clock callback) happened
@ -82,7 +93,8 @@ export class Cyclist {
this.started = v;
this.onToggle?.(v);
}
start() {
async start() {
await this.beforeStart?.();
this.num_ticks_since_cps_change = 0;
this.num_cycles_at_cps_change = 0;
if (!this.pattern) {
@ -103,10 +115,10 @@ export class Cyclist {
this.lastEnd = 0;
this.setStarted(false);
}
setPattern(pat, autostart = false) {
async setPattern(pat, autostart = false) {
this.pattern = pat;
if (autostart && !this.started) {
this.start();
await this.start();
}
}
setCps(cps = 0.5) {

View File

@ -37,11 +37,11 @@ function safeEval(str, options = {}) {
return Function(body)();
}
export const evaluate = async (code, transpiler) => {
export const evaluate = async (code, transpiler, transpilerOptions) => {
let meta = {};
if (transpiler) {
// transform syntactically correct js code to semantically usable code
const transpiled = transpiler(code);
const transpiled = transpiler(code, transpilerOptions);
code = transpiled.output;
meta = transpiled;
}

View File

@ -94,6 +94,14 @@ export class Pattern {
return result;
}
// runs func on query state
withState(func) {
return this.withHaps((haps, state) => {
func(state);
return haps;
});
}
/**
* see `withValue`
* @noAutocomplete
@ -2523,7 +2531,7 @@ export function _polymeterListSteps(steps, ...args) {
* @param {any[]} patterns one or more patterns
* @example
* // the same as "{c d, e f g}%4"
* s_polymeterSteps(4, "c d", "e f g")
* s_polymeterSteps(4, "c d", "e f g").note()
*/
export function s_polymeterSteps(steps, ...args) {
if (args.length == 0) {
@ -2541,8 +2549,8 @@ export function s_polymeterSteps(steps, ...args) {
* *EXPERIMENTAL* - Combines the given lists of patterns with the same pulse, creating polymeters when different sized sequences are used.
* @synonyms pm
* @example
* // The same as "{c eb g, c2 g2}"
* s_polymeter("c eb g", "c2 g2")
* // The same as note("{c eb g, c2 g2}")
* s_polymeter("c eb g", "c2 g2").note()
*
*/
export function s_polymeter(...args) {
@ -2571,6 +2579,8 @@ export function s_polymeter(...args) {
/** Sequences patterns like `seq`, but each pattern has a length, relative to the whole.
* This length can either be provided as a [length, pattern] pair, or inferred from
* the pattern's 'tactus', generally inferred by the mininotation. Has the alias `timecat`.
* @name s_cat
* @synonyms timeCat, timecat
* @return {Pattern}
* @example
* s_cat([3,"e3"],[1, "g3"]).note()

View File

@ -10,6 +10,7 @@ export function repl({
defaultOutput,
onEvalError,
beforeEval,
beforeStart,
afterEval,
getTime,
transpiler,
@ -19,6 +20,7 @@ export function repl({
sync = false,
setInterval,
clearInterval,
id,
}) {
const state = {
schedulerError: undefined,
@ -32,6 +34,10 @@ export function repl({
started: false,
};
const transpilerOptions = {
id,
};
const updateState = (update) => {
Object.assign(state, update);
state.isDirty = state.code !== state.activeCode;
@ -48,6 +54,7 @@ export function repl({
},
setInterval,
clearInterval,
beforeStart,
};
// NeoCyclist uses a shared worker to communicate between instances, which is not supported on mobile chrome
@ -64,9 +71,10 @@ export function repl({
return silence;
};
const setPattern = (pattern, autostart = true) => {
const setPattern = async (pattern, autostart = true) => {
pattern = editPattern?.(pattern) || pattern;
scheduler.setPattern(pattern, autostart);
await scheduler.setPattern(pattern, autostart);
return pattern;
};
setTime(() => scheduler.now()); // TODO: refactor?
@ -139,9 +147,10 @@ export function repl({
try {
updateState({ code, pending: true });
await injectPatternMethods();
setTime(() => scheduler.now()); // TODO: refactor?
await beforeEval?.({ code });
shouldHush && hush();
let { pattern, meta } = await _evaluate(code, transpiler);
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
if (Object.keys(pPatterns).length) {
pattern = stack(...Object.values(pPatterns));
}
@ -153,7 +162,7 @@ export function repl({
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
}
logger(`[eval] code updated`);
setPattern(pattern, autostart);
pattern = await setPattern(pattern, autostart);
updateState({
miniLocations: meta?.miniLocations || [],
widgets: meta?.widgets || [],

View File

@ -36,8 +36,8 @@ function stopAnimationFrame(id) {
delete animationFrames[id];
}
}
function stopAllAnimations() {
Object.keys(animationFrames).forEach((id) => stopAnimationFrame(id));
function stopAllAnimations(replID) {
Object.keys(animationFrames).forEach((id) => (!replID || id.startsWith(replID)) && stopAnimationFrame(id));
}
let memory = {};
@ -72,18 +72,25 @@ Pattern.prototype.draw = function (fn, options) {
return this;
};
export const cleanupDraw = (clearScreen = true) => {
export const cleanupDraw = (clearScreen = true, id) => {
const ctx = getDrawContext();
clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.width);
stopAllAnimations();
if (window.strudelScheduler) {
clearInterval(window.strudelScheduler);
}
clearScreen && ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
stopAllAnimations(id);
};
Pattern.prototype.onPaint = function () {
console.warn('[draw] onPaint was not overloaded. Some drawings might not work');
return this;
Pattern.prototype.onPaint = function (painter) {
return this.withState((state) => {
if (!state.controls.painters) {
state.controls.painters = [];
}
state.controls.painters.push(painter);
});
};
Pattern.prototype.getPainters = function () {
let painters = [];
this.queryArc(0, 0, { painters });
return painters;
};
// const round = (x) => Math.round(x * 1000) / 1000;
@ -122,6 +129,7 @@ export class Drawer {
this.visibleHaps = [];
this.lastFrame = null;
this.drawTime = drawTime;
this.painters = [];
this.framer = new Framer(
() => {
if (!this.scheduler) {
@ -146,7 +154,7 @@ export class Drawer {
// add new haps with onset (think right edge bars scrolling in)
.concat(haps.filter((h) => h.hasOnset()));
const time = phase - lookahead;
onDraw(this.visibleHaps, time, this);
onDraw(this.visibleHaps, time, this, this.painters);
},
(err) => {
console.warn('draw error', err);
@ -164,11 +172,13 @@ export class Drawer {
t = t ?? scheduler.now();
this.scheduler = scheduler;
let [_, lookahead] = this.drawTime;
// +0.1 = workaround for weird holes in query..
const [begin, end] = [Math.max(t, 0), t + lookahead + 0.1];
// remove all future haps
this.visibleHaps = this.visibleHaps.filter((h) => h.whole.begin < t);
this.painters = []; // will get populated by .onPaint calls attached to the pattern
// query future haps
const futureHaps = scheduler.pattern.queryArc(begin, end); // +0.1 = workaround for weird holes in query..
const futureHaps = scheduler.pattern.queryArc(begin, end, { painters: this.painters });
// append future haps
this.visibleHaps = this.visibleHaps.concat(futureHaps);
}

View File

@ -67,6 +67,7 @@ Pattern.prototype.pianoroll = function (options = {}) {
* Displays a midi-style piano roll
*
* @name pianoroll
* @synonyms punchcard
* @param {Object} options Object containing all the optional following parameters as key value pairs:
* @param {integer} cycles number of cycles to be displayed at the same time - defaults to 4
* @param {number} playhead location of the active notes on the time axis - 0 to 1, defaults to 0.5
@ -94,7 +95,11 @@ Pattern.prototype.pianoroll = function (options = {}) {
* @param {boolean} autorange automatically calculate the minMidi and maxMidi parameters - 0 by default
*
* @example
* note("C2 A2 G2").euclid(5,8).s('piano').clip(1).color('salmon').pianoroll({vertical:1, labels:1})
* note("c2 a2 eb2")
* .euclid(5,8)
* .s('sawtooth')
* .lpenv(4).lpf(300)
* ._pianoroll({ labels: 1 })
*/
export function pianoroll({
time,
@ -113,7 +118,7 @@ export function pianoroll({
maxMidi = 90,
autorange = 0,
timeframe: timeframeProp,
fold = 0,
fold = 1,
vertical = 0,
labels = false,
fill = 1,
@ -266,7 +271,7 @@ export function getDrawOptions(drawTime, options = {}) {
let [lookbehind, lookahead] = drawTime;
lookbehind = Math.abs(lookbehind);
const cycles = lookahead + lookbehind;
const playhead = lookbehind / cycles;
const playhead = cycles !== 0 ? lookbehind / cycles : 0;
return { fold: 1, ...options, cycles, playhead };
}

View File

@ -113,6 +113,23 @@ export function pitchwheel({
return;
}
/**
* Renders a pitch circle to visualize frequencies within one octave
* @name pitchwheel
* @param {number} hapcircles
* @param {number} circle
* @param {number} edo
* @param {string} root
* @param {number} thickness
* @param {number} hapRadius
* @param {string} mode
* @param {number} margin
* @example
* n("0 .. 12").scale("C:chromatic")
* .s("sawtooth")
* .lpf(500)
* ._pitchwheel()
*/
Pattern.prototype.pitchwheel = function (options = {}) {
let { ctx = getDrawContext(), id = 1 } = options;
return this.tag(id).onPaint((_, time, haps) =>

View File

@ -125,6 +125,33 @@ function drawSpiral(options) {
});
}
/**
* Displays a spiral visual.
*
* @name spiral
* @param {Object} options Object containing all the optional following parameters as key value pairs:
* @param {number} stretch controls the rotations per cycle ratio, where 1 = 1 cycle / 360 degrees
* @param {number} size the diameter of the spiral
* @param {number} thickness line thickness
* @param {string} cap style of line ends: butt (default), round, square
* @param {string} inset number of rotations before spiral starts (default 3)
* @param {string} playheadColor color of playhead, defaults to white
* @param {number} playheadLength length of playhead in rotations, defaults to 0.02
* @param {number} playheadThickness thickness of playheadrotations, defaults to thickness
* @param {number} padding space around spiral
* @param {number} steady steadyness of spiral vs playhead. 1 = spiral doesn't move, playhead does.
* @param {number} activeColor color of active segment. defaults to foreground of theme
* @param {number} inactiveColor color of inactive segments. defaults to gutterForeground of theme
* @param {boolean} colorizeInactive wether or not to colorize inactive segments, defaults to 0
* @param {boolean} fade wether or not past and future should fade out. defaults to 1
* @param {boolean} logSpiral wether or not the spiral should be logarithmic. defaults to 0
* @example
* note("c2 a2 eb2")
* .euclid(5,8)
* .s('sawtooth')
* .lpenv(4).lpf(300)
* ._spiral({ steady: .96 })
*/
Pattern.prototype.spiral = function (options = {}) {
return this.onPaint((ctx, time, haps, drawTime) => drawSpiral({ ctx, time, haps, drawTime, ...options }));
};

View File

@ -69,6 +69,7 @@ export function transpiler(input, options = {}) {
to: node.end,
index,
type,
id: options.id,
};
emitWidgets && widgets.push(widgetConfig);
return this.replace(widgetWithLocation(node, widgetConfig));
@ -162,7 +163,7 @@ export function getWidgetID(widgetConfig) {
// that means, if we use the index index of line position as id, less garbage is generated
// return `widget_${widgetConfig.to}`; // more gargabe
//return `widget_${widgetConfig.index}_${widgetConfig.to}`; // also more garbage
return `widget_${widgetConfig.type}_${widgetConfig.index}`; // less garbage
return `${widgetConfig.id || ''}_widget_${widgetConfig.type}_${widgetConfig.index}`; // less garbage
}
function widgetWithLocation(node, widgetConfig) {

View File

@ -130,7 +130,7 @@ Pattern.prototype.fscope = function (config = {}) {
* @param {number} pos y-position relative to screen height. 0 = top, 1 = bottom of screen
* @param {number} trigger amplitude value that is used to align the scope. defaults to 0.
* @example
* s("sawtooth").scope()
* s("sawtooth")._scope()
*/
let latestColor = {};
Pattern.prototype.tscope = function (config = {}) {

View File

@ -2717,175 +2717,175 @@ exports[`runs examples > example "floor" example index 0 1`] = `
exports[`runs examples > example "fm" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:0 analyze:1 ]",
"[ 1/6 → 1/3 | note:e fmi:0 analyze:1 ]",
"[ 1/3 → 1/2 | note:g fmi:0 analyze:1 ]",
"[ 1/2 → 2/3 | note:b fmi:0 analyze:1 ]",
"[ 2/3 → 5/6 | note:g fmi:0 analyze:1 ]",
"[ 5/6 → 1/1 | note:e fmi:0 analyze:1 ]",
"[ 1/1 → 7/6 | note:c fmi:1 analyze:1 ]",
"[ 7/6 → 4/3 | note:e fmi:1 analyze:1 ]",
"[ 4/3 → 3/2 | note:g fmi:1 analyze:1 ]",
"[ 3/2 → 5/3 | note:b fmi:1 analyze:1 ]",
"[ 5/3 → 11/6 | note:g fmi:1 analyze:1 ]",
"[ 11/6 → 2/1 | note:e fmi:1 analyze:1 ]",
"[ 2/1 → 13/6 | note:c fmi:2 analyze:1 ]",
"[ 13/6 → 7/3 | note:e fmi:2 analyze:1 ]",
"[ 7/3 → 5/2 | note:g fmi:2 analyze:1 ]",
"[ 5/2 → 8/3 | note:b fmi:2 analyze:1 ]",
"[ 8/3 → 17/6 | note:g fmi:2 analyze:1 ]",
"[ 17/6 → 3/1 | note:e fmi:2 analyze:1 ]",
"[ 3/1 → 19/6 | note:c fmi:8 analyze:1 ]",
"[ 19/6 → 10/3 | note:e fmi:8 analyze:1 ]",
"[ 10/3 → 7/2 | note:g fmi:8 analyze:1 ]",
"[ 7/2 → 11/3 | note:b fmi:8 analyze:1 ]",
"[ 11/3 → 23/6 | note:g fmi:8 analyze:1 ]",
"[ 23/6 → 4/1 | note:e fmi:8 analyze:1 ]",
"[ 0/1 → 1/6 | note:c fmi:0 ]",
"[ 1/6 → 1/3 | note:e fmi:0 ]",
"[ 1/3 → 1/2 | note:g fmi:0 ]",
"[ 1/2 → 2/3 | note:b fmi:0 ]",
"[ 2/3 → 5/6 | note:g fmi:0 ]",
"[ 5/6 → 1/1 | note:e fmi:0 ]",
"[ 1/1 → 7/6 | note:c fmi:1 ]",
"[ 7/6 → 4/3 | note:e fmi:1 ]",
"[ 4/3 → 3/2 | note:g fmi:1 ]",
"[ 3/2 → 5/3 | note:b fmi:1 ]",
"[ 5/3 → 11/6 | note:g fmi:1 ]",
"[ 11/6 → 2/1 | note:e fmi:1 ]",
"[ 2/1 → 13/6 | note:c fmi:2 ]",
"[ 13/6 → 7/3 | note:e fmi:2 ]",
"[ 7/3 → 5/2 | note:g fmi:2 ]",
"[ 5/2 → 8/3 | note:b fmi:2 ]",
"[ 8/3 → 17/6 | note:g fmi:2 ]",
"[ 17/6 → 3/1 | note:e fmi:2 ]",
"[ 3/1 → 19/6 | note:c fmi:8 ]",
"[ 19/6 → 10/3 | note:e fmi:8 ]",
"[ 10/3 → 7/2 | note:g fmi:8 ]",
"[ 7/2 → 11/3 | note:b fmi:8 ]",
"[ 11/3 → 23/6 | note:g fmi:8 ]",
"[ 23/6 → 4/1 | note:e fmi:8 ]",
]
`;
exports[`runs examples > example "fmattack" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:4 fmattack:0 analyze:1 ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmattack:0 analyze:1 ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmattack:0 analyze:1 ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmattack:0 analyze:1 ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmattack:0 analyze:1 ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmattack:0 analyze:1 ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmattack:0.05 analyze:1 ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmattack:0.05 analyze:1 ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmattack:0.05 analyze:1 ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmattack:0.05 analyze:1 ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmattack:0.05 analyze:1 ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmattack:0.05 analyze:1 ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmattack:0.1 analyze:1 ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmattack:0.1 analyze:1 ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmattack:0.1 analyze:1 ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmattack:0.1 analyze:1 ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmattack:0.1 analyze:1 ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmattack:0.1 analyze:1 ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmattack:0.2 analyze:1 ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmattack:0.2 analyze:1 ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmattack:0.2 analyze:1 ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmattack:0.2 analyze:1 ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmattack:0.2 analyze:1 ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmattack:0.2 analyze:1 ]",
"[ 0/1 → 1/6 | note:c fmi:4 fmattack:0 ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmattack:0 ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmattack:0 ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmattack:0 ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmattack:0 ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmattack:0 ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmattack:0.05 ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmattack:0.05 ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmattack:0.05 ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmattack:0.05 ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmattack:0.05 ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmattack:0.05 ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmattack:0.1 ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmattack:0.1 ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmattack:0.1 ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmattack:0.1 ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmattack:0.1 ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmattack:0.1 ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmattack:0.2 ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmattack:0.2 ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmattack:0.2 ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmattack:0.2 ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmattack:0.2 ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmattack:0.2 ]",
]
`;
exports[`runs examples > example "fmdecay" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmdecay:0.01 fmsustain:0.4 analyze:1 ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmdecay:0.05 fmsustain:0.4 analyze:1 ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmdecay:0.1 fmsustain:0.4 analyze:1 ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0.4 analyze:1 ]",
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmdecay:0.01 fmsustain:0.4 ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmdecay:0.05 fmsustain:0.4 ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmdecay:0.1 fmsustain:0.4 ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0.4 ]",
]
`;
exports[`runs examples > example "fmenv" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp analyze:1 ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin analyze:1 ]",
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:exp ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmdecay:0.2 fmsustain:0 fmenv:lin ]",
]
`;
exports[`runs examples > example "fmh" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:4 fmh:1 analyze:1 ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmh:1 analyze:1 ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmh:1 analyze:1 ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmh:1 analyze:1 ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmh:1 analyze:1 ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmh:1 analyze:1 ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmh:2 analyze:1 ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmh:2 analyze:1 ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmh:2 analyze:1 ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmh:2 analyze:1 ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmh:2 analyze:1 ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmh:2 analyze:1 ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmh:1.5 analyze:1 ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmh:1.5 analyze:1 ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmh:1.5 analyze:1 ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmh:1.5 analyze:1 ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmh:1.5 analyze:1 ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmh:1.5 analyze:1 ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmh:1.61 analyze:1 ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmh:1.61 analyze:1 ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmh:1.61 analyze:1 ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmh:1.61 analyze:1 ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmh:1.61 analyze:1 ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmh:1.61 analyze:1 ]",
"[ 0/1 → 1/6 | note:c fmi:4 fmh:1 ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmh:1 ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmh:1 ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmh:1 ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmh:1 ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmh:1 ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmh:2 ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmh:2 ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmh:2 ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmh:2 ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmh:2 ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmh:2 ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmh:1.5 ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmh:1.5 ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmh:1.5 ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmh:1.5 ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmh:1.5 ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmh:1.5 ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmh:1.61 ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmh:1.61 ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmh:1.61 ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmh:1.61 ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmh:1.61 ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmh:1.61 ]",
]
`;
exports[`runs examples > example "fmsustain" example index 0 1`] = `
[
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmdecay:0.1 fmsustain:1 analyze:1 ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmdecay:0.1 fmsustain:0.75 analyze:1 ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmdecay:0.1 fmsustain:0.5 analyze:1 ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmdecay:0.1 fmsustain:0 analyze:1 ]",
"[ 0/1 → 1/6 | note:c fmi:4 fmdecay:0.1 fmsustain:1 ]",
"[ 1/6 → 1/3 | note:e fmi:4 fmdecay:0.1 fmsustain:1 ]",
"[ 1/3 → 1/2 | note:g fmi:4 fmdecay:0.1 fmsustain:1 ]",
"[ 1/2 → 2/3 | note:b fmi:4 fmdecay:0.1 fmsustain:1 ]",
"[ 2/3 → 5/6 | note:g fmi:4 fmdecay:0.1 fmsustain:1 ]",
"[ 5/6 → 1/1 | note:e fmi:4 fmdecay:0.1 fmsustain:1 ]",
"[ 1/1 → 7/6 | note:c fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
"[ 7/6 → 4/3 | note:e fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
"[ 4/3 → 3/2 | note:g fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
"[ 3/2 → 5/3 | note:b fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
"[ 5/3 → 11/6 | note:g fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
"[ 11/6 → 2/1 | note:e fmi:4 fmdecay:0.1 fmsustain:0.75 ]",
"[ 2/1 → 13/6 | note:c fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
"[ 13/6 → 7/3 | note:e fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
"[ 7/3 → 5/2 | note:g fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
"[ 5/2 → 8/3 | note:b fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
"[ 8/3 → 17/6 | note:g fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
"[ 17/6 → 3/1 | note:e fmi:4 fmdecay:0.1 fmsustain:0.5 ]",
"[ 3/1 → 19/6 | note:c fmi:4 fmdecay:0.1 fmsustain:0 ]",
"[ 19/6 → 10/3 | note:e fmi:4 fmdecay:0.1 fmsustain:0 ]",
"[ 10/3 → 7/2 | note:g fmi:4 fmdecay:0.1 fmsustain:0 ]",
"[ 7/2 → 11/3 | note:b fmi:4 fmdecay:0.1 fmsustain:0 ]",
"[ 11/3 → 23/6 | note:g fmi:4 fmdecay:0.1 fmsustain:0 ]",
"[ 23/6 → 4/1 | note:e fmi:4 fmdecay:0.1 fmsustain:0 ]",
]
`;
@ -4042,19 +4042,19 @@ exports[`runs examples > example "loopAtCps" example index 0 1`] = `
exports[`runs examples > example "loopBegin" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:space loop:1 loopBegin:0 analyze:1 ]",
"[ 1/1 → 2/1 | s:space loop:1 loopBegin:0.125 analyze:1 ]",
"[ 2/1 → 3/1 | s:space loop:1 loopBegin:0.25 analyze:1 ]",
"[ 3/1 → 4/1 | s:space loop:1 loopBegin:0 analyze:1 ]",
"[ 0/1 → 1/1 | s:space loop:1 loopBegin:0 ]",
"[ 1/1 → 2/1 | s:space loop:1 loopBegin:0.125 ]",
"[ 2/1 → 3/1 | s:space loop:1 loopBegin:0.25 ]",
"[ 3/1 → 4/1 | s:space loop:1 loopBegin:0 ]",
]
`;
exports[`runs examples > example "loopEnd" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:space loop:1 loopEnd:1 analyze:1 ]",
"[ 1/1 → 2/1 | s:space loop:1 loopEnd:0.75 analyze:1 ]",
"[ 2/1 → 3/1 | s:space loop:1 loopEnd:0.5 analyze:1 ]",
"[ 3/1 → 4/1 | s:space loop:1 loopEnd:0.25 analyze:1 ]",
"[ 0/1 → 1/1 | s:space loop:1 loopEnd:1 ]",
"[ 1/1 → 2/1 | s:space loop:1 loopEnd:0.75 ]",
"[ 2/1 → 3/1 | s:space loop:1 loopEnd:0.5 ]",
"[ 3/1 → 4/1 | s:space loop:1 loopEnd:0.25 ]",
]
`;
@ -5085,34 +5085,34 @@ exports[`runs examples > example "phasersweep" example index 0 1`] = `
exports[`runs examples > example "pianoroll" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:C2 s:piano clip:1 color:salmon ]",
"[ (1/4 → 1/3) ⇝ 3/8 | note:C2 s:piano clip:1 color:salmon ]",
"[ 1/4 ⇜ (1/3 → 3/8) | note:A2 s:piano clip:1 color:salmon ]",
"[ 3/8 → 1/2 | note:A2 s:piano clip:1 color:salmon ]",
"[ (5/8 → 2/3) ⇝ 3/4 | note:A2 s:piano clip:1 color:salmon ]",
"[ 5/8 ⇜ (2/3 → 3/4) | note:G2 s:piano clip:1 color:salmon ]",
"[ 3/4 → 7/8 | note:G2 s:piano clip:1 color:salmon ]",
"[ 1/1 → 9/8 | note:C2 s:piano clip:1 color:salmon ]",
"[ (5/4 → 4/3) ⇝ 11/8 | note:C2 s:piano clip:1 color:salmon ]",
"[ 5/4 ⇜ (4/3 → 11/8) | note:A2 s:piano clip:1 color:salmon ]",
"[ 11/8 → 3/2 | note:A2 s:piano clip:1 color:salmon ]",
"[ (13/8 → 5/3) ⇝ 7/4 | note:A2 s:piano clip:1 color:salmon ]",
"[ 13/8 ⇜ (5/3 → 7/4) | note:G2 s:piano clip:1 color:salmon ]",
"[ 7/4 → 15/8 | note:G2 s:piano clip:1 color:salmon ]",
"[ 2/1 → 17/8 | note:C2 s:piano clip:1 color:salmon ]",
"[ (9/4 → 7/3) ⇝ 19/8 | note:C2 s:piano clip:1 color:salmon ]",
"[ 9/4 ⇜ (7/3 → 19/8) | note:A2 s:piano clip:1 color:salmon ]",
"[ 19/8 → 5/2 | note:A2 s:piano clip:1 color:salmon ]",
"[ (21/8 → 8/3) ⇝ 11/4 | note:A2 s:piano clip:1 color:salmon ]",
"[ 21/8 ⇜ (8/3 → 11/4) | note:G2 s:piano clip:1 color:salmon ]",
"[ 11/4 → 23/8 | note:G2 s:piano clip:1 color:salmon ]",
"[ 3/1 → 25/8 | note:C2 s:piano clip:1 color:salmon ]",
"[ (13/4 → 10/3) ⇝ 27/8 | note:C2 s:piano clip:1 color:salmon ]",
"[ 13/4 ⇜ (10/3 → 27/8) | note:A2 s:piano clip:1 color:salmon ]",
"[ 27/8 → 7/2 | note:A2 s:piano clip:1 color:salmon ]",
"[ (29/8 → 11/3) ⇝ 15/4 | note:A2 s:piano clip:1 color:salmon ]",
"[ 29/8 ⇜ (11/3 → 15/4) | note:G2 s:piano clip:1 color:salmon ]",
"[ 15/4 → 31/8 | note:G2 s:piano clip:1 color:salmon ]",
"[ 0/1 → 1/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (1/4 → 1/3) ⇝ 3/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 1/4 ⇜ (1/3 → 3/8) | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 3/8 → 1/2 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (5/8 → 2/3) ⇝ 3/4 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 5/8 ⇜ (2/3 → 3/4) | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 3/4 → 7/8 | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 1/1 → 9/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (5/4 → 4/3) ⇝ 11/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 5/4 ⇜ (4/3 → 11/8) | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 11/8 → 3/2 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (13/8 → 5/3) ⇝ 7/4 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 13/8 ⇜ (5/3 → 7/4) | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 7/4 → 15/8 | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 2/1 → 17/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (9/4 → 7/3) ⇝ 19/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 9/4 ⇜ (7/3 → 19/8) | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 19/8 → 5/2 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (21/8 → 8/3) ⇝ 11/4 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 21/8 ⇜ (8/3 → 11/4) | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 11/4 → 23/8 | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 3/1 → 25/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (13/4 → 10/3) ⇝ 27/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 13/4 ⇜ (10/3 → 27/8) | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 27/8 → 7/2 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (29/8 → 11/3) ⇝ 15/4 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 29/8 ⇜ (11/3 → 15/4) | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 15/4 → 31/8 | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
]
`;
@ -5234,6 +5234,63 @@ exports[`runs examples > example "pickF" example index 1 1`] = `
]
`;
exports[`runs examples > example "pitchwheel" example index 0 1`] = `
[
"[ 0/1 → 1/13 | note:C3 s:sawtooth cutoff:500 ]",
"[ 1/13 → 2/13 | note:Db3 s:sawtooth cutoff:500 ]",
"[ 2/13 → 3/13 | note:D3 s:sawtooth cutoff:500 ]",
"[ 3/13 → 4/13 | note:Eb3 s:sawtooth cutoff:500 ]",
"[ 4/13 → 5/13 | note:E3 s:sawtooth cutoff:500 ]",
"[ 5/13 → 6/13 | note:F3 s:sawtooth cutoff:500 ]",
"[ 6/13 → 7/13 | note:Gb3 s:sawtooth cutoff:500 ]",
"[ 7/13 → 8/13 | note:G3 s:sawtooth cutoff:500 ]",
"[ 8/13 → 9/13 | note:Ab3 s:sawtooth cutoff:500 ]",
"[ 9/13 → 10/13 | note:A3 s:sawtooth cutoff:500 ]",
"[ 10/13 → 11/13 | note:Bb3 s:sawtooth cutoff:500 ]",
"[ 11/13 → 12/13 | note:B3 s:sawtooth cutoff:500 ]",
"[ 12/13 → 1/1 | note:C4 s:sawtooth cutoff:500 ]",
"[ 1/1 → 14/13 | note:C3 s:sawtooth cutoff:500 ]",
"[ 14/13 → 15/13 | note:Db3 s:sawtooth cutoff:500 ]",
"[ 15/13 → 16/13 | note:D3 s:sawtooth cutoff:500 ]",
"[ 16/13 → 17/13 | note:Eb3 s:sawtooth cutoff:500 ]",
"[ 17/13 → 18/13 | note:E3 s:sawtooth cutoff:500 ]",
"[ 18/13 → 19/13 | note:F3 s:sawtooth cutoff:500 ]",
"[ 19/13 → 20/13 | note:Gb3 s:sawtooth cutoff:500 ]",
"[ 20/13 → 21/13 | note:G3 s:sawtooth cutoff:500 ]",
"[ 21/13 → 22/13 | note:Ab3 s:sawtooth cutoff:500 ]",
"[ 22/13 → 23/13 | note:A3 s:sawtooth cutoff:500 ]",
"[ 23/13 → 24/13 | note:Bb3 s:sawtooth cutoff:500 ]",
"[ 24/13 → 25/13 | note:B3 s:sawtooth cutoff:500 ]",
"[ 25/13 → 2/1 | note:C4 s:sawtooth cutoff:500 ]",
"[ 2/1 → 27/13 | note:C3 s:sawtooth cutoff:500 ]",
"[ 27/13 → 28/13 | note:Db3 s:sawtooth cutoff:500 ]",
"[ 28/13 → 29/13 | note:D3 s:sawtooth cutoff:500 ]",
"[ 29/13 → 30/13 | note:Eb3 s:sawtooth cutoff:500 ]",
"[ 30/13 → 31/13 | note:E3 s:sawtooth cutoff:500 ]",
"[ 31/13 → 32/13 | note:F3 s:sawtooth cutoff:500 ]",
"[ 32/13 → 33/13 | note:Gb3 s:sawtooth cutoff:500 ]",
"[ 33/13 → 34/13 | note:G3 s:sawtooth cutoff:500 ]",
"[ 34/13 → 35/13 | note:Ab3 s:sawtooth cutoff:500 ]",
"[ 35/13 → 36/13 | note:A3 s:sawtooth cutoff:500 ]",
"[ 36/13 → 37/13 | note:Bb3 s:sawtooth cutoff:500 ]",
"[ 37/13 → 38/13 | note:B3 s:sawtooth cutoff:500 ]",
"[ 38/13 → 3/1 | note:C4 s:sawtooth cutoff:500 ]",
"[ 3/1 → 40/13 | note:C3 s:sawtooth cutoff:500 ]",
"[ 40/13 → 41/13 | note:Db3 s:sawtooth cutoff:500 ]",
"[ 41/13 → 42/13 | note:D3 s:sawtooth cutoff:500 ]",
"[ 42/13 → 43/13 | note:Eb3 s:sawtooth cutoff:500 ]",
"[ 43/13 → 44/13 | note:E3 s:sawtooth cutoff:500 ]",
"[ 44/13 → 45/13 | note:F3 s:sawtooth cutoff:500 ]",
"[ 45/13 → 46/13 | note:Gb3 s:sawtooth cutoff:500 ]",
"[ 46/13 → 47/13 | note:G3 s:sawtooth cutoff:500 ]",
"[ 47/13 → 48/13 | note:Ab3 s:sawtooth cutoff:500 ]",
"[ 48/13 → 49/13 | note:A3 s:sawtooth cutoff:500 ]",
"[ 49/13 → 50/13 | note:Bb3 s:sawtooth cutoff:500 ]",
"[ 50/13 → 51/13 | note:B3 s:sawtooth cutoff:500 ]",
"[ 51/13 → 4/1 | note:C4 s:sawtooth cutoff:500 ]",
]
`;
exports[`runs examples > example "ply" example index 0 1`] = `
[
"[ 0/1 → 1/4 | s:bd ]",
@ -6196,67 +6253,67 @@ exports[`runs examples > example "s_cat" example index 1 1`] = `
exports[`runs examples > example "s_polymeter" example index 0 1`] = `
[
"[ 0/1 → 1/3 | c ]",
"[ 0/1 → 1/3 | c2 ]",
"[ 1/3 → 2/3 | eb ]",
"[ 1/3 → 2/3 | g2 ]",
"[ 2/3 → 1/1 | g ]",
"[ 2/3 → 1/1 | c2 ]",
"[ 1/1 → 4/3 | c ]",
"[ 1/1 → 4/3 | g2 ]",
"[ 4/3 → 5/3 | eb ]",
"[ 4/3 → 5/3 | c2 ]",
"[ 5/3 → 2/1 | g ]",
"[ 5/3 → 2/1 | g2 ]",
"[ 2/1 → 7/3 | c ]",
"[ 2/1 → 7/3 | c2 ]",
"[ 7/3 → 8/3 | eb ]",
"[ 7/3 → 8/3 | g2 ]",
"[ 8/3 → 3/1 | g ]",
"[ 8/3 → 3/1 | c2 ]",
"[ 3/1 → 10/3 | c ]",
"[ 3/1 → 10/3 | g2 ]",
"[ 10/3 → 11/3 | eb ]",
"[ 10/3 → 11/3 | c2 ]",
"[ 11/3 → 4/1 | g ]",
"[ 11/3 → 4/1 | g2 ]",
"[ 0/1 → 1/3 | note:c ]",
"[ 0/1 → 1/3 | note:c2 ]",
"[ 1/3 → 2/3 | note:eb ]",
"[ 1/3 → 2/3 | note:g2 ]",
"[ 2/3 → 1/1 | note:g ]",
"[ 2/3 → 1/1 | note:c2 ]",
"[ 1/1 → 4/3 | note:c ]",
"[ 1/1 → 4/3 | note:g2 ]",
"[ 4/3 → 5/3 | note:eb ]",
"[ 4/3 → 5/3 | note:c2 ]",
"[ 5/3 → 2/1 | note:g ]",
"[ 5/3 → 2/1 | note:g2 ]",
"[ 2/1 → 7/3 | note:c ]",
"[ 2/1 → 7/3 | note:c2 ]",
"[ 7/3 → 8/3 | note:eb ]",
"[ 7/3 → 8/3 | note:g2 ]",
"[ 8/3 → 3/1 | note:g ]",
"[ 8/3 → 3/1 | note:c2 ]",
"[ 3/1 → 10/3 | note:c ]",
"[ 3/1 → 10/3 | note:g2 ]",
"[ 10/3 → 11/3 | note:eb ]",
"[ 10/3 → 11/3 | note:c2 ]",
"[ 11/3 → 4/1 | note:g ]",
"[ 11/3 → 4/1 | note:g2 ]",
]
`;
exports[`runs examples > example "s_polymeterSteps" example index 0 1`] = `
[
"[ 0/1 → 1/4 | c ]",
"[ 0/1 → 1/4 | e ]",
"[ 1/4 → 1/2 | d ]",
"[ 1/4 → 1/2 | f ]",
"[ 1/2 → 3/4 | c ]",
"[ 1/2 → 3/4 | g ]",
"[ 3/4 → 1/1 | d ]",
"[ 3/4 → 1/1 | e ]",
"[ 1/1 → 5/4 | c ]",
"[ 1/1 → 5/4 | f ]",
"[ 5/4 → 3/2 | d ]",
"[ 5/4 → 3/2 | g ]",
"[ 3/2 → 7/4 | c ]",
"[ 3/2 → 7/4 | e ]",
"[ 7/4 → 2/1 | d ]",
"[ 7/4 → 2/1 | f ]",
"[ 2/1 → 9/4 | c ]",
"[ 2/1 → 9/4 | g ]",
"[ 9/4 → 5/2 | d ]",
"[ 9/4 → 5/2 | e ]",
"[ 5/2 → 11/4 | c ]",
"[ 5/2 → 11/4 | f ]",
"[ 11/4 → 3/1 | d ]",
"[ 11/4 → 3/1 | g ]",
"[ 3/1 → 13/4 | c ]",
"[ 3/1 → 13/4 | e ]",
"[ 13/4 → 7/2 | d ]",
"[ 13/4 → 7/2 | f ]",
"[ 7/2 → 15/4 | c ]",
"[ 7/2 → 15/4 | g ]",
"[ 15/4 → 4/1 | d ]",
"[ 15/4 → 4/1 | e ]",
"[ 0/1 → 1/4 | note:c ]",
"[ 0/1 → 1/4 | note:e ]",
"[ 1/4 → 1/2 | note:d ]",
"[ 1/4 → 1/2 | note:f ]",
"[ 1/2 → 3/4 | note:c ]",
"[ 1/2 → 3/4 | note:g ]",
"[ 3/4 → 1/1 | note:d ]",
"[ 3/4 → 1/1 | note:e ]",
"[ 1/1 → 5/4 | note:c ]",
"[ 1/1 → 5/4 | note:f ]",
"[ 5/4 → 3/2 | note:d ]",
"[ 5/4 → 3/2 | note:g ]",
"[ 3/2 → 7/4 | note:c ]",
"[ 3/2 → 7/4 | note:e ]",
"[ 7/4 → 2/1 | note:d ]",
"[ 7/4 → 2/1 | note:f ]",
"[ 2/1 → 9/4 | note:c ]",
"[ 2/1 → 9/4 | note:g ]",
"[ 9/4 → 5/2 | note:d ]",
"[ 9/4 → 5/2 | note:e ]",
"[ 5/2 → 11/4 | note:c ]",
"[ 5/2 → 11/4 | note:f ]",
"[ 11/4 → 3/1 | note:d ]",
"[ 11/4 → 3/1 | note:g ]",
"[ 3/1 → 13/4 | note:c ]",
"[ 3/1 → 13/4 | note:e ]",
"[ 13/4 → 7/2 | note:d ]",
"[ 13/4 → 7/2 | note:f ]",
"[ 7/2 → 15/4 | note:c ]",
"[ 7/2 → 15/4 | note:g ]",
"[ 15/4 → 4/1 | note:d ]",
"[ 15/4 → 4/1 | note:e ]",
]
`;
@ -6554,10 +6611,10 @@ exports[`runs examples > example "scaleTranspose" example index 0 1`] = `
exports[`runs examples > example "scope" example index 0 1`] = `
[
"[ 0/1 → 1/1 | s:sawtooth analyze:1 ]",
"[ 1/1 → 2/1 | s:sawtooth analyze:1 ]",
"[ 2/1 → 3/1 | s:sawtooth analyze:1 ]",
"[ 3/1 → 4/1 | s:sawtooth analyze:1 ]",
"[ 0/1 → 1/1 | s:sawtooth ]",
"[ 1/1 → 2/1 | s:sawtooth ]",
"[ 2/1 → 3/1 | s:sawtooth ]",
"[ 3/1 → 4/1 | s:sawtooth ]",
]
`;
@ -7158,6 +7215,39 @@ exports[`runs examples > example "speed" example index 1 1`] = `
]
`;
exports[`runs examples > example "spiral" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (1/4 → 1/3) ⇝ 3/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 1/4 ⇜ (1/3 → 3/8) | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 3/8 → 1/2 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (5/8 → 2/3) ⇝ 3/4 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 5/8 ⇜ (2/3 → 3/4) | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 3/4 → 7/8 | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 1/1 → 9/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (5/4 → 4/3) ⇝ 11/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 5/4 ⇜ (4/3 → 11/8) | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 11/8 → 3/2 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (13/8 → 5/3) ⇝ 7/4 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 13/8 ⇜ (5/3 → 7/4) | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 7/4 → 15/8 | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 2/1 → 17/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (9/4 → 7/3) ⇝ 19/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 9/4 ⇜ (7/3 → 19/8) | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 19/8 → 5/2 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (21/8 → 8/3) ⇝ 11/4 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 21/8 ⇜ (8/3 → 11/4) | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 11/4 → 23/8 | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 3/1 → 25/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (13/4 → 10/3) ⇝ 27/8 | note:c2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 13/4 ⇜ (10/3 → 27/8) | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 27/8 → 7/2 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ (29/8 → 11/3) ⇝ 15/4 | note:a2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 29/8 ⇜ (11/3 → 15/4) | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
"[ 15/4 → 31/8 | note:eb2 s:sawtooth lpenv:4 cutoff:300 ]",
]
`;
exports[`runs examples > example "splice" example index 0 1`] = `
[
"[ 0/1 → 1/8 | speed:1 unit:c begin:0 end:0.125 _slices:8 s:breaks165 ]",

View File

@ -122,6 +122,19 @@ strudel.Pattern.prototype.midi = function () {
return this;
};
strudel.Pattern.prototype._scope = function () {
return this;
};
strudel.Pattern.prototype._spiral = function () {
return this;
};
strudel.Pattern.prototype._pitchwheel = function () {
return this;
};
strudel.Pattern.prototype._pianoroll = function () {
return this;
};
const uiHelpersMocked = {
backgroundImage: id,
};

View File

@ -78,6 +78,7 @@ export const SIDEBAR: Sidebar = {
More: [
{ text: 'Recipes', link: 'recipes/recipes' },
{ text: 'Mini-Notation', link: 'learn/mini-notation' },
{ text: 'Visual Feedback', link: 'learn/visual-feedback' },
{ text: 'Offline', link: 'learn/pwa' },
{ text: 'Patterns', link: 'technical-manual/patterns' },
{ text: 'Music metadata', link: 'learn/metadata' },

View File

@ -1,7 +1,8 @@
import { useState, useRef, useCallback, useMemo, useEffect } from 'react';
import { Icon } from './Icon';
import { silence, noteToMidi, _mod } from '@strudel/core';
import { getPunchcardPainter } from '@strudel/draw';
import { clearHydra } from '@strudel/hydra';
import { getDrawContext, getPunchcardPainter } from '@strudel/draw';
import { transpiler } from '@strudel/transpiler';
import { getAudioContext, webaudioOutput, initAudioOnFirstClick } from '@strudel/webaudio';
import { StrudelMirror } from '@strudel/codemirror';
@ -28,24 +29,29 @@ export function MiniRepl({
claviature,
claviatureLabels,
maxHeight,
autodraw,
drawTime,
}) {
const code = tunes ? tunes[0] : tune;
const id = useMemo(() => s4(), []);
const canvasId = useMemo(() => `canvas-${id}`, [id]);
const shouldDraw = !!punchcard || !!claviature;
const shouldShowCanvas = !!punchcard;
const drawTime = punchcard ? [0, 4] : [0, 0];
const canvasId = shouldShowCanvas ? useMemo(() => `canvas-${id}`, [id]) : null;
autodraw = !!punchcard || !!claviature || !!autodraw;
drawTime = drawTime ?? punchcard ? [0, 4] : [-2, 2];
if (claviature) {
drawTime = [0, 0];
}
const [activeNotes, setActiveNotes] = useState([]);
const init = useCallback(({ code, shouldDraw }) => {
const drawContext = shouldDraw ? document.querySelector('#' + canvasId)?.getContext('2d') : null;
const init = useCallback(({ code, autodraw }) => {
const drawContext = canvasId ? document.querySelector('#' + canvasId)?.getContext('2d') : getDrawContext();
const editor = new StrudelMirror({
id,
defaultOutput: webaudioOutput,
getTime: () => getAudioContext().currentTime,
transpiler,
autodraw: !!shouldDraw,
autodraw,
root: containerRef.current,
initialCode: '// LOADING',
pattern: silence,
@ -56,7 +62,7 @@ export function MiniRepl({
pat = pat.onTrigger(onTrigger, false);
}
if (claviature) {
editor?.painters.push((ctx, time, haps, drawTime) => {
pat = pat.onPaint((ctx, time, haps, drawTime) => {
const active = haps
.map((hap) => hap.value.note)
.filter(Boolean)
@ -65,7 +71,7 @@ export function MiniRepl({
});
}
if (punchcard) {
editor?.painters.push(getPunchcardPainter({ labels: !!punchcardLabels }));
pat = pat.punchcard({ labels: !!punchcardLabels });
}
return pat;
},
@ -73,7 +79,12 @@ export function MiniRepl({
onUpdateState: (state) => {
setReplState({ ...state });
},
beforeEval: () => audioReady, // not doing this in prebake to make sure viz is drawn
onToggle: (playing) => {
if (!playing) {
clearHydra();
}
},
beforeStart: () => audioReady,
afterEval: ({ code }) => setVersionDefaultsFrom(code),
});
// init settings
@ -152,7 +163,7 @@ export function MiniRepl({
ref={(el) => {
if (!editorRef.current) {
containerRef.current = el;
init({ code, shouldDraw });
init({ code, autodraw });
}
}}
></div>

View File

@ -11,15 +11,15 @@ import { JsDoc } from '../../docs/JsDoc';
The following functions will return a pattern.
These are the equivalents used by the Mini Notation:
| function | mini |
| ------------------------------ | ---------------- |
| `cat(x, y)` | `"<x y>"` |
| `seq(x, y)` | `"x y"` |
| `stack(x, y)` | `"x,y"` |
| `timeCat([3,x],[2,y])` | `"x@3 y@2"` |
| `polymeter([a, b, c], [x, y])` | `"{a b c, x y}"` |
| `polymeterSteps(2, x, y, z)` | `"{x y z}%2"` |
| `silence` | `"~"` |
| function | mini |
| -------------------------------- | ---------------- |
| `cat(x, y)` | `"<x y>"` |
| `seq(x, y)` | `"x y"` |
| `stack(x, y)` | `"x,y"` |
| `s_cat([3,x],[2,y])` | `"x@3 y@2"` |
| `s_polymeter([a, b, c], [x, y])` | `"{a b c, x y}"` |
| `s_polymeterSteps(2, x, y, z)` | `"{x y z}%2"` |
| `silence` | `"~"` |
## cat
@ -45,21 +45,21 @@ As a chained function:
<JsDoc client:idle name="Pattern.stack" h={0} hideDescription />
## timeCat
## s_cat
<JsDoc client:idle name="timeCat" h={0} />
<JsDoc client:idle name="s_cat" h={0} />
## arrange
<JsDoc client:idle name="arrange" h={0} />
## polymeter
## s_polymeter
<JsDoc client:idle name="polymeter" h={0} />
<JsDoc client:idle name="s_polymeter" h={0} />
## polymeterSteps
## s_polymeterSteps
<JsDoc client:idle name="polymeterSteps" h={0} />
<JsDoc client:idle name="s_polymeterSteps" h={0} />
## silence

View File

@ -18,7 +18,7 @@ The basic waveforms are `sine`, `sawtooth`, `square` and `triangle`, which can b
client:idle
tune={`note("c2 <eb2 <g2 g1>>".fast(2))
.sound("<sawtooth square triangle sine>")
.scope()`}
._scope()`}
/>
If you don't set a `sound` but a `note` the default value for `sound` is `triangle`!
@ -28,23 +28,23 @@ If you don't set a `sound` but a `note` the default value for `sound` is `triang
You can also use noise as a source by setting the waveform to: `white`, `pink` or `brown`. These are different
flavours of noise, here written from hard to soft.
<MiniRepl client:idle tune={`sound("<white pink brown>").scope()`} />
<MiniRepl client:idle tune={`sound("<white pink brown>")._scope()`} />
Here's a more musical example of how to use noise for hihats:
<MiniRepl
client:idle
tune={`sound("bd*2,<white pink brown>*8")
.decay(.04).sustain(0).scope()`}
.decay(.04).sustain(0)._scope()`}
/>
Some amount of pink noise can also be added to any oscillator by using the `noise` paremeter:
<MiniRepl client:idle tune={`note("c3").noise("<0.1 0.25 0.5>").scope()`} />
<MiniRepl client:idle tune={`note("c3").noise("<0.1 0.25 0.5>")._scope()`} />
You can also use the `crackle` type to play some subtle noise crackles. You can control noise amount by using the `density` parameter:
<MiniRepl client:idle tune={`s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(2)).scope()`} />
<MiniRepl client:idle tune={`s("crackle*4").density("<0.01 0.04 0.2 0.5>".slow(2))._scope()`} />
### Additive Synthesis
@ -55,7 +55,7 @@ To tame the harsh sound of the basic waveforms, we can set the `n` control to li
tune={`note("c2 <eb2 <g2 g1>>".fast(2))
.sound("sawtooth")
.n("<32 16 8 4>")
.scope()`}
._scope()`}
/>
When the `n` control is used on a basic waveform, it defines the number of harmonic partials the sound is getting.
@ -65,7 +65,7 @@ You can also set `n` directly in mini notation with `sound`:
client:idle
tune={`note("c2 <eb2 <g2 g1>>".fast(2))
.sound("sawtooth:<32 16 8 4>")
.scope()`}
._scope()`}
/>
Note for tidal users: `n` in tidal is synonymous to `note` for synths only.
@ -119,13 +119,14 @@ Any sample preceded by the `wt_` prefix will be loaded as a wavetable. This mean
<MiniRepl
client:idle
tune={`samples('github:Bubobubobubobubo/Dough-Waveforms/main/');
tune={`samples('bubo:waveforms');
note("<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>")
.n("<1 2 3 4 5 6 7 8 9 10>/2").room(0.5).size(0.9)
.s('wt_flute').velocity(0.25).often(n => n.ply(2))
.release(0.125).decay("<0.1 0.25 0.3 0.4>").sustain(0)
.cutoff(2000).scope({}).cutoff("<1000 2000 4000>").fast(4)`}
.cutoff(2000).cutoff("<1000 2000 4000>").fast(4)
._scope()
`}
/>
## ZZFX
@ -159,7 +160,7 @@ It has 20 parameters in total, here is a snippet that uses all:
.tremolo(0) // 0-1 lfo volume modulation amount
//.duration(.2) // overwrite strudel event duration
//.gain(1) // change volume
.scope() // vizualise waveform (not zzfx related)
._scope() // vizualise waveform (not zzfx related)
`}
/>

View File

@ -0,0 +1,100 @@
---
title: Visual Feedback
layout: ../../layouts/MainLayout.astro
---
import { MiniRepl } from '../../docs/MiniRepl';
import { JsDoc } from '../../docs/JsDoc';
# Visual Feedback
There are several function that add visual feedback to your patterns.
## Mini Notation Highlighting
When you write mini notation with "double quotes" or \`backticks\`, the active parts of the mini notation will be highlighted:
<MiniRepl
client:idle
tune={`n("<0 2 1 3 2>*8")
.scale("<A1 D2>/4:minor:pentatonic")
.s("supersaw").lpf(300).lpenv("<4 3 2>\*4")`}
/>
You can change the color as well, even pattern it:
<MiniRepl
client:idle
tune={`n("<0 2 1 3 2>*8")
.scale("<A1 D2>/4:minor:pentatonic")
.s("supersaw").lpf(300).lpenv("<4 3 2>*4")
.color("cyan magenta")`}
/>
## Global vs Inline Visuals
The following functions all come with in 2 variants.
**Without prefix**: renders the visual to the background of the page:
<MiniRepl client:idle tune={`note("c a f e").color("white").punchcard()`} />
**With `_` prefix**: renders the visual inside the code. Allows for multiple visuals
<MiniRepl client:idle tune={`note("c a f e").color("white")._punchcard()`} />
Here we see the 2 variants for `punchcard`. The same goes for all others below.
To improve readability the following demos will all use the inline variant.
## Punchcard / Pianoroll
These 2 functions render a pianoroll style visual.
The only difference between the 2 is that `pianoroll` will render the pattern directly,
while `punchcard` will also take the transformations into account that occur afterwards:
<MiniRepl
client:idle
tune={`note("c a f e").color("white")
._punchcard()
.color("cyan")`}
autodraw
/>
Here, the `color` is still visible in the visual, even if it is applied after `_punchcard`.
On the contrary, the color is not visible when using `_pianoroll`:
<MiniRepl
client:idle
tune={`note("c a f e").color("white")
._pianoroll()
.color("cyan")`}
autodraw
/>
import Box from '@components/Box.astro';
<br />
<Box>
`punchcard` is less resource intensive because it uses the same data as used for the mini notation highlighting.
</Box>
The visual can be customized by passing options. Those options are the same for both functions.
What follows is the API doc of all the options you can pass:
<JsDoc client:idle name="pianoroll" h={0} />
## Spiral
<JsDoc client:idle name="spiral" h={0} />
## Scope
<JsDoc client:idle name="scope" h={0} />
## Pitchwheel
<JsDoc client:idle name="pitchwheel" h={0} />

View File

@ -154,7 +154,7 @@ Can you guess what they do?
tune={`stack(
note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
.sound("gm_electric_guitar_muted"),
sound("<bd rim>").bank("RolandTR707")
sound("bd rim").bank("RolandTR707")
).delay(".5")`}
/>
@ -202,7 +202,7 @@ Add a delay too!
tune={`stack(
note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
.sound("gm_electric_guitar_muted").delay(.5),
sound("<bd rim>").bank("RolandTR707").delay(.5),
sound("bd rim").bank("RolandTR707").delay(.5),
n("<4 [3@3 4] [<2 0> ~@16] ~>")
.scale("D4:minor").sound("gm_accordion:2")
.room(2).gain(.5)
@ -216,7 +216,7 @@ Let's add a bass to make this complete:
tune={`stack(
note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
.sound("gm_electric_guitar_muted").delay(.5),
sound("<bd rim>").bank("RolandTR707").delay(.5),
sound("bd rim").bank("RolandTR707").delay(.5),
n("<4 [3@3 4] [<2 0> ~@16] ~>")
.scale("D4:minor").sound("gm_accordion:2")
.room(2).gain(.4),
@ -266,7 +266,7 @@ By the way, inside Mini-Notation, `fast` is `*` and `slow` is `/`.
Instead of changing values stepwise, we can also control them with signals:
<MiniRepl client:visible tune={`sound("hh*32").gain(sine)`} punchcard punchcardLabels={false} />
<MiniRepl client:visible tune={`sound("hh*16").gain(sine)`} punchcard punchcardLabels={false} />
<Box>