Compare commits

...

38 Commits

Author SHA1 Message Date
Jade (Rose) Rowland dd12b49f4d withduration 2024-08-13 15:00:16 -04:00
Jade (Rose) Rowland 18d4fe0785 change legato to fill 2024-08-08 00:05:19 -04:00
Jade (Rose) Rowland 4e38988777 formatting 2024-07-13 23:31:56 -04:00
Jade (Rose) Rowland 308eda677c add tests 2024-07-13 13:21:43 -04:00
Jade (Rose) Rowland fdd167d62d working 2024-07-13 13:20:05 -04:00
Jade (Rose) Rowland 85bccdcc19 Merge branch 'main' of github.com:daslyfe/strudel 2024-07-13 13:17:28 -04:00
Felix Roos f514cd85b1 Merge pull request #1136 from tidalcycles/cm-commands
export comment commands
2024-06-25 18:13:27 +02:00
Felix Roos d53929cf68 export comment commands 2024-06-25 10:00:26 +02:00
Alex McLean a4bd0ae100 Chop chop (#1078)
* failing test

* support chopping chops
2024-06-24 17:19:22 +01:00
Alex McLean 283a071c86 Fix bug in Fraction.lcm (#1133)
* bugfix lcm
2024-06-18 22:58:08 +01:00
Jade (Rose) Rowland 6350cfdb9c Merge pull request #1129 from mindofmatthew/worker-path-in-modules
Fix clock worker dependency path in module builds
2024-06-17 23:32:12 -04:00
Matthew Kaney b72e71b0bd Update base path build setting for core and repl packages 2024-06-11 10:02:57 -04:00
Matthew Kaney 758b94776d Configure base url for @strudel/web package 2024-06-10 19:10:17 -04:00
Jade (Rose) Rowland e62d5759e7 revert 2024-06-10 00:00:43 -04:00
Felix Roos c403a92542 Merge pull request #1126 from tidalcycles/labeled-statements-doc
Labeled statements doc
2024-06-04 00:26:48 +02:00
Felix Roos 269d6dde31 remove stack from workshop 2024-06-04 00:22:49 +02:00
Felix Roos 666c695c63 don't use labeled statements in example for now 2024-06-04 00:10:46 +02:00
Felix Roos 771eb31d3f use labels instead of stacks 2024-06-04 00:05:51 +02:00
Felix Roos c05bbf3422 don't clear hydra in minirepl for now 2024-06-03 23:53:08 +02:00
Felix Roos 364f511a7c Merge pull request #1125 from tidalcycles/viz-doc
doc: visual functions + refactor onPaint
2024-06-03 22:40:21 +02:00
Felix Roos 300022d8ac no custom line height 2024-06-03 20:34:51 +02:00
Felix Roos 357fbf8c82 snapshot 2024-06-02 23:14:32 +02:00
Felix Roos 1acb675f5a doc: visual functions 2024-06-02 23:12:31 +02:00
Felix Roos 25981092b1 snapshot 2024-06-02 14:11:52 +02:00
Felix Roos e6b55baed0 fix: step based naming 2024-06-02 14:08:56 +02:00
Felix Roos 6adf1ff977 snapshot 2024-06-02 14:03:30 +02:00
Felix Roos b6e277f1ea use inline scope in examples 2024-06-02 14:03:05 +02:00
Felix Roos d0510f862a clear hydra in mini repl 2024-06-02 14:02:39 +02:00
Felix Roos 4a87df6426 small example tweaks 2024-06-02 14:00:47 +02:00
Felix Roos bf90867685 fix: mini repl punchcard + claviature flags 2024-06-02 10:49:07 +02:00
Felix Roos 3dc5fd0e7e fix: editPattern bug 2024-06-02 10:48:20 +02:00
Felix Roos a7650f1af3 add autodraw flag + scope 2024-06-02 03:08:16 +02:00
Felix Roos aadb2fee84 support onload inline viz with autodraw 2024-06-02 03:05:26 +02:00
Felix Roos bf391ad6ce simplify: painters are now handled by Drawer 2024-06-02 03:03:36 +02:00
Felix Roos faba4a4e4e implement onPaint with pattern state 2024-06-02 03:00:57 +02:00
Felix Roos 94e411aa7a add beforeStart callback 2024-06-02 02:34:33 +02:00
Felix Roos 0e6a17fc77 start visual feedback article 2024-06-01 15:42:09 +02:00
Felix Roos 375c68775c make sure draw logic works with multiple repls 2024-06-01 15:41:55 +02:00
37 changed files with 956 additions and 491 deletions
+14 -26
View File
@@ -1,4 +1,5 @@
import { closeBrackets } from '@codemirror/autocomplete';
export { toggleComment, toggleBlockComment, toggleLineComment, toggleBlockCommentByLine } from '@codemirror/commands';
// import { search, highlightSelectionMatches } from '@codemirror/search';
import { history } from '@codemirror/commands';
import { javascript } from '@codemirror/lang-javascript';
@@ -140,27 +141,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 +170,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 +188,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 +227,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 +240,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');
}
+1 -1
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',
+1 -1
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;
+13 -9
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');
@@ -1521,7 +1525,7 @@ export const { cps } = registerControl('cps');
* note("c a f e").s("piano").clip("<.5 1 2>")
*
*/
export const { clip, legato } = registerControl('clip', 'legato');
export const { clip } = registerControl('clip');
/**
* Sets the duration of the event in cycles. Similar to clip / legato, it also cuts samples off at the end if they exceed the duration.
+16 -4
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) {
+2 -2
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;
}
+2 -2
View File
@@ -119,10 +119,10 @@ export const lcm = (...fractions) => {
if (fractions.length === 0) {
return undefined;
}
const x = fractions.pop();
return fractions.reduce(
(lcm, fraction) => (lcm === undefined || fraction === undefined ? undefined : lcm.lcm(fraction)),
fraction(1),
x,
);
};
+91 -4
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
@@ -1721,6 +1729,75 @@ export const { compressSpan, compressspan } = register(['compressSpan', 'compres
return pat._compress(span.begin, span.end);
});
/**
* fills in the gap between consecutive notes
*
* @name fill
* @memberof Pattern
* @param {Pattern | number | array} fill relative note length
* @returns Pattern
* @example
* s("sawtooth").euclid(11,16).fill("<1 .5>")._pianoroll()
* @example
* // second array parameter is "lookahead" which is the number of cycles in the future to query for the next event
* s("supersaw").euclid(7,16).fill("1:<1 .15>")._pianoroll()
*/
export const fill = register('fill', function (legato, pat) {
let multiplier = legato;
let lookahead = 1;
if (Array.isArray(legato)) {
multiplier = legato[0] ?? 1;
lookahead = legato[1] ?? lookahead;
}
let spanEnd;
let spanBegin;
return pat
.withQuerySpan((span) => {
spanEnd = span.end;
spanBegin = span.begin;
return span.withEnd((e) => {
return e.add(lookahead);
})
// .withBegin(e => e.sub(lookahead));
})
.withHaps((haps) => {
const newHaps = [];
haps
.sort((a, b) => {
return a.part.begin.sub(b.part.begin).valueOf();
})
.forEach((hap, i) => {
const nextHap = haps[i + 1];
const partbegin = hap.part.begin;
let partend = hap.part.end;
const wholebegin = hap.whole.begin;
// filter out haps that were not part of the original span
if (wholebegin.gte(spanEnd) || wholebegin.lt(spanBegin)) {
return;
}
let wholeend = hap.whole.end;
if (nextHap != null) {
partend = nextHap.part.begin;
wholeend = nextHap.whole.begin;
}
let wholeduration = wholeend.sub(wholebegin).mul(multiplier);
let partduration = partend.sub(partbegin).mul(multiplier);
partend = partbegin.add(partduration);
wholeend = wholebegin.add(wholeduration);
const newPart = new TimeSpan(partbegin, partend);
const newWhole = new TimeSpan(wholebegin, wholeend);
newHaps.push(new Hap(newWhole, newPart, hap.value, hap.context));
});
return newHaps;
});
});
/**
* speeds up a pattern like fast, but rather than it playing multiple times as fast would it instead leaves a gap in the remaining space of the cycle. For example, the following will play the sound pattern "bd sn" only once but compressed into the first half of the cycle, i.e. twice as fast.
* @name fastGap
@@ -2523,7 +2600,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 +2618,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 +2648,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()
@@ -2794,8 +2873,16 @@ export const s_tour = function (pat, ...many) {
export const chop = register('chop', function (n, pat) {
const slices = Array.from({ length: n }, (x, i) => i);
const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
const merge = function (a, b) {
if ('begin' in a && 'end' in a && a.begin !== undefined && a.end !== undefined) {
const d = a.end - a.begin;
b = { begin: a.begin + b.begin * d, end: a.begin + b.end * d };
}
// return a;
return Object.assign({}, a, b);
};
const func = function (o) {
return sequence(slice_objects.map((slice_o) => Object.assign({}, o, slice_o)));
return sequence(slice_objects.map((slice_o) => merge(o, slice_o)));
};
return pat.squeezeBind(func).setTactus(__tactus ? Fraction(n).mulmaybe(pat.tactus) : undefined);
});
+13 -4
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 || [],
+39
View File
@@ -937,6 +937,9 @@ describe('Pattern', () => {
.firstCycle(),
);
});
it('Can chop chops', () => {
expect(pure({ s: 'bev' }).chop(2).chop(2).firstCycle()).toStrictEqual(pure({ s: 'bev' }).chop(4).firstCycle());
});
});
describe('range', () => {
it('Can be patterned', () => {
@@ -1209,4 +1212,40 @@ describe('Pattern', () => {
}
});
});
describe('s_expand', () => {
it('can expand four things in half', () => {
expect(
sameFirst(
sequence(0, 1, 2, 3).s_expand(1, 0.5),
s_cat(sequence(0, 1, 2, 3), sequence(0, 1, 2, 3).s_expand(0.5)),
),
);
});
it('can expand five things in half', () => {
expect(
sameFirst(
sequence(0, 1, 2, 3, 4).s_expand(1, 0.5),
s_cat(sequence(0, 1, 2, 3, 4), sequence(0, 1, 2, 3, 4).s_expand(0.5)),
),
);
});
});
describe('stepJoin', () => {
it('can join a pattern with a tactus of 2', () => {
expect(
sameFirst(
sequence(pure(pure('a')), pure(pure('b').setTactus(2))).stepJoin(),
s_cat(pure('a'), pure('b').setTactus(2)),
),
);
});
it('can join a pattern with a tactus of 0.5', () => {
expect(
sameFirst(
sequence(pure(pure('a')), pure(pure('b').setTactus(0.5))).stepJoin(),
s_cat(pure('a'), pure('b').setTactus(0.5)),
),
);
});
});
});
+8
View File
@@ -61,6 +61,14 @@ export class TimeSpan {
// Applies given function to the end time of the timespan"""
return new TimeSpan(this.begin, func_time(this.end));
}
withBegin(func_time) {
// Applies given function to the end time of the timespan"""
return new TimeSpan(func_time(this.begin), this.end);
}
withDuration(func_time) {
const duration = this.end.sub(this.begin)
return new TimeSpan(this.begin, this.begin.add(func_time(duration)))
}
withCycle(func_time) {
// Like withTime, but time is relative to relative to the cycle (i.e. the
+1
View File
@@ -4,6 +4,7 @@ import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [],
build: {
lib: {
+1 -1
View File
@@ -53,7 +53,7 @@ Pattern.prototype.animate = function ({ callback, sync = false, smear = 0.5 } =
return silence;
};
export const { x, y, w, h, angle, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'angle', 'r', 'fill', 'smear');
export const { x, y, w, h, angle, r, fill, smear } = createParams('x', 'y', 'w', 'h', 'angle', 'r', 'smear');
export const rescale = register('rescale', function (f, pat) {
return pat.mul(x(f).w(f).y(f).h(f));
+23 -13
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);
}
+8 -3
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 };
}
+17
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) =>
+27
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 }));
};
+1
View File
@@ -6,6 +6,7 @@ import replace from '@rollup/plugin-replace';
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [],
build: {
lib: {
+2 -1
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) {
+1
View File
@@ -5,6 +5,7 @@ import replace from '@rollup/plugin-replace';
// https://vitejs.dev/config/
export default defineConfig({
base: './',
plugins: [],
build: {
lib: {
+1 -1
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 = {}) {
+412 -240
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 ]",
]
`;
@@ -3953,6 +3953,88 @@ exports[`runs examples > example "layer" example index 0 1`] = `
]
`;
exports[`runs examples > example "legato" example index 0 1`] = `
[
"[ 0/1 → 1/8 | s:sawtooth ]",
"[ 1/8 → 3/16 | s:sawtooth ]",
"[ 3/16 → 5/16 | s:sawtooth ]",
"[ 5/16 → 3/8 | s:sawtooth ]",
"[ 3/8 → 1/2 | s:sawtooth ]",
"[ 1/2 → 9/16 | s:sawtooth ]",
"[ 9/16 → 11/16 | s:sawtooth ]",
"[ 11/16 → 3/4 | s:sawtooth ]",
"[ 3/4 → 7/8 | s:sawtooth ]",
"[ 7/8 → 15/16 | s:sawtooth ]",
"[ 15/16 → 1/1 | s:sawtooth ]",
"[ 1/1 → 17/16 | s:sawtooth ]",
"[ 9/8 → 37/32 | s:sawtooth ]",
"[ 19/16 → 5/4 | s:sawtooth ]",
"[ 21/16 → 43/32 | s:sawtooth ]",
"[ 11/8 → 23/16 | s:sawtooth ]",
"[ 3/2 → 49/32 | s:sawtooth ]",
"[ 25/16 → 13/8 | s:sawtooth ]",
"[ 27/16 → 55/32 | s:sawtooth ]",
"[ 7/4 → 29/16 | s:sawtooth ]",
"[ 15/8 → 61/32 | s:sawtooth ]",
"[ 31/16 → 63/32 | s:sawtooth ]",
"[ 2/1 → 17/8 | s:sawtooth ]",
"[ 17/8 → 35/16 | s:sawtooth ]",
"[ 35/16 → 37/16 | s:sawtooth ]",
"[ 37/16 → 19/8 | s:sawtooth ]",
"[ 19/8 → 5/2 | s:sawtooth ]",
"[ 5/2 → 41/16 | s:sawtooth ]",
"[ 41/16 → 43/16 | s:sawtooth ]",
"[ 43/16 → 11/4 | s:sawtooth ]",
"[ 11/4 → 23/8 | s:sawtooth ]",
"[ 23/8 → 47/16 | s:sawtooth ]",
"[ 47/16 → 3/1 | s:sawtooth ]",
"[ 3/1 → 49/16 | s:sawtooth ]",
"[ 25/8 → 101/32 | s:sawtooth ]",
"[ 51/16 → 13/4 | s:sawtooth ]",
"[ 53/16 → 107/32 | s:sawtooth ]",
"[ 27/8 → 55/16 | s:sawtooth ]",
"[ 7/2 → 113/32 | s:sawtooth ]",
"[ 57/16 → 29/8 | s:sawtooth ]",
"[ 59/16 → 119/32 | s:sawtooth ]",
"[ 15/4 → 61/16 | s:sawtooth ]",
"[ 31/8 → 125/32 | s:sawtooth ]",
"[ 63/16 → 127/32 | s:sawtooth ]",
]
`;
exports[`runs examples > example "legato" example index 1 1`] = `
[
"[ 0/1 → 3/16 | s:supersaw ]",
"[ 3/16 → 5/16 | s:supersaw ]",
"[ 5/16 → 7/16 | s:supersaw ]",
"[ 7/16 → 5/8 | s:supersaw ]",
"[ 5/8 → 3/4 | s:supersaw ]",
"[ 3/4 → 7/8 | s:supersaw ]",
"[ 7/8 → 1/1 | s:supersaw ]",
"[ 1/1 → 19/16 | s:supersaw ]",
"[ 19/16 → 21/16 | s:supersaw ]",
"[ 21/16 → 23/16 | s:supersaw ]",
"[ 23/16 → 13/8 | s:supersaw ]",
"[ 13/8 → 7/4 | s:supersaw ]",
"[ 7/4 → 15/8 | s:supersaw ]",
"[ 15/8 → 2/1 | s:supersaw ]",
"[ 2/1 → 35/16 | s:supersaw ]",
"[ 35/16 → 37/16 | s:supersaw ]",
"[ 37/16 → 39/16 | s:supersaw ]",
"[ 39/16 → 21/8 | s:supersaw ]",
"[ 21/8 → 11/4 | s:supersaw ]",
"[ 11/4 → 23/8 | s:supersaw ]",
"[ 23/8 → 3/1 | s:supersaw ]",
"[ 3/1 → 51/16 | s:supersaw ]",
"[ 51/16 → 53/16 | s:supersaw ]",
"[ 53/16 → 55/16 | s:supersaw ]",
"[ 55/16 → 29/8 | s:supersaw ]",
"[ 29/8 → 15/4 | s:supersaw ]",
"[ 15/4 → 31/8 | s:supersaw ]",
"[ 31/8 → 4/1 | s:supersaw ]",
]
`;
exports[`runs examples > example "leslie" example index 0 1`] = `
[
"[ 0/1 → 1/1 | n:0 s:supersquare leslie:0 ]",
@@ -4042,19 +4124,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 +5167,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 +5316,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 +6335,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 +6693,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 +7297,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 ]",
+14
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,
};
@@ -178,6 +191,7 @@ evalScope(
},
);
// TBD: use transpiler to support labeled statements
export const queryCode = async (code, cycles = 1) => {
const { pattern } = await evaluate(code);
const haps = pattern.sortHapsByPart().queryArc(0, cycles);
+1
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' },
+22 -11
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(); // TBD: doesn't work with multiple MiniRepl's on a page
}
},
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>
+15 -15
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
+9 -7
View File
@@ -90,11 +90,13 @@ src(s0).kaleid(H("<4 5 6>"))
.modulateScale(osc(2,-0.25,1))
.out()
//
stack(
s("bd*4,[hh:0:<.5 1>]*8,~ rim").bank("RolandTR909").speed(.9),
note("[<g1!3 <bb1 <f1 d1>>>]*3").s("sawtooth")
.room(.75).sometimes(add(note(12))).clip(.3)
.lpa(.05).lpenv(-4).lpf(2000).lpq(8).ftype('24db')
).fft(4)
.scope({pos:0,smear:.95})`}
$: s("bd*4,[hh:0:<.5 1>]*8,~ rim").bank("RolandTR909").speed(.9)
$: note("[<g1!3 <bb1 <f1 d1>>>]\*3").s("sawtooth")
.room(.75).sometimes(add(note(12))).clip(.3)
.lpa(.05).lpenv(-4).lpf(2000).lpq(8).ftype('24db')
all(x=>x.fft(4).scope({pos:0,smear:.95}))`}
/>
+3 -9
View File
@@ -20,11 +20,7 @@ Strudel also supports midi via [webmidi](https://npmjs.com/package/webmidi).
Either connect a midi device or use the IAC Driver (Mac) or Midi Through Port (Linux) for internal midi messages.
If no outputName is given, it uses the first midi output it finds.
<MiniRepl
client:idle
tune={`stack("<C^7 A7 Dm7 G7>".voicings('lefthand'), "<C3 A2 D3 G2>").note()
.midi()`}
/>
<MiniRepl client:idle tune={`chord("<C^7 A7 Dm7 G7>").voicing().midi()`} />
In the console, you will see a log of the available MIDI devices as soon as you run the code, e.g. `Midi connected! Using "Midi Through Port-0".`
@@ -45,10 +41,8 @@ But you can also control cc messages separately like this:
<MiniRepl
client:idle
tune={`stack(
note("c a f e"),
ccv(sine.segment(16).slow(4)).ccn(74)
).midi()`}
tune={`$: note("c a f e").midi()
$: ccv(sine.segment(16).slow(4)).ccn(74).midi()`}
/>
# SuperDirt API
+7 -9
View File
@@ -268,11 +268,10 @@ With it, you can enter any sample name(s) to query from [freesound.org](https://
<MiniRepl
client:idle
tune={`samples('shabda:bass:4,hihat:4,rimshot:2')
stack(
n("0 1 2 3 0 1 2 3").s('bass'),
n("0 1*2 2 3*2").s('hihat'),
n("~ 0 ~ 1 ~ 0 0 1").s('rimshot')
).clip(1)`}
$: n("0 1 2 3 0 1 2 3").s('bass')
$: n("0 1*2 2 3*2").s('hihat').clip(1)
$: n("~ 0 ~ 1 ~ 0 0 1").s('rimshot')`}
/>
You can also generate artificial voice samples with any text, in multiple languages.
@@ -282,10 +281,9 @@ Note that the language code and the gender parameters are optional and default t
client:idle
tune={`samples('shabda/speech:the_drum,forever')
samples('shabda/speech/fr-FR/m:magnifique')
stack(
s("the_drum*2").chop(16).speed(rand.range(0.85,1.1)),
s("forever magnifique").slow(4).late(0.125)
)`}
$: s("the_drum*2").chop(16).speed(rand.range(0.85,1.1))
$: s("forever magnifique").slow(4).late(0.125)`}
/>
# Sampler Effects
+6 -21
View File
@@ -112,29 +112,14 @@ Also, samples are always loaded from a URL rather than from the disk, although [
## Evaluation
The Strudel REPL does not support [block based evaluation](https://github.com/tidalcycles/strudel/issues/34) yet.
You can use the following "workaround" to create multiple patterns that can be turned on and off:
You can use labeled statements and `_` to mute:
```
let a = note("c a f e")
<MiniRepl
client:visible
tune={`$: n("[0 .. 8]*8/9").scale("C:minor:pentatonic")
let b = s("bd sd")
stack(
a,
// b
)
```
Alternatively, you could write everything as one `stack` and use `.hush()` to silence a pattern:
```
stack(
note("c a f e"),
s("bd sd").hush()
)
```
Note that strudel will always use the last statement in your code as the pattern for querying
\_$: s("bd\*4").bank('RolandTR909')`}
/>
## Tempo
+12 -11
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)
`}
/>
-14
View File
@@ -55,20 +55,6 @@ Transposes notes inside the scale by the number of steps:
.note()`}
/>
### voicings(range?)
Turns chord symbols into voicings, using the smoothest voice leading possible:
<MiniRepl
client:only="react"
tune={`stack(
"<C^7 A7 Dm7 G7>".voicings('lefthand'),
"<C3 A2 D3 G2>"
).note()`}
/>
Note: This function might be removed, as `voicing` (without s) is a newer implementation.
### rootNotes(octave = 2)
Turns chord symbols into root notes of chords in given octave.
+100
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} />
+35 -46
View File
@@ -60,11 +60,10 @@ We will learn how to automate with waves later...
<MiniRepl
client:visible
tune={`stack(
sound("hh*16").gain("[.25 1]*4"),
sound("bd*4,[~ sd:1]*2")
) `}
punchcard
tune={`$: sound("hh*16").gain("[.25 1]*4")
$: sound("bd*4,[~ sd:1]*2")`}
punchcard
/>
<Box>
@@ -76,31 +75,21 @@ Rhythm is all about dynamics!
</Box>
**stacks within stacks**
Let's combine all of the above into a little tune:
<MiniRepl
client:visible
tune={`stack(
stack(
sound("hh*8").gain("[.25 1]*4"),
sound("bd*4,[~ sd:1]*2")
),
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
.sound("sawtooth").lpf("200 1000 200 1000"),
note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>")
.sound("sawtooth").vowel("<a e i o>")
)`}
tune={`$: sound("hh*8").gain("[.25 1]*4")
$: sound("bd*4,[~ sd:1]*2")
$: note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
.sound("sawtooth").lpf("200 1000 200 1000")
$: note("<[c3,g3,e4] [bb2,f3,d4] [a2,f3,c4] [bb2,g3,eb4]>")
.sound("sawtooth").vowel("<a e i o>")`}
/>
<Box>
Try to identify the individual parts of the stacks, pay attention to where the commas are.
The 3 parts (drums, bassline, chords) are exactly as earlier, just stacked together, separated by comma.
</Box>
**shape the sound with an adsr envelope**
<MiniRepl
@@ -151,11 +140,10 @@ Can you guess what they do?
<MiniRepl
client:visible
tune={`stack(
note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
.sound("gm_electric_guitar_muted"),
sound("<bd rim>").bank("RolandTR707")
).delay(".5")`}
tune={`$: note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
.sound("gm_electric_guitar_muted").delay(.5)
$: sound("bd rim").bank("RolandTR707").delay(".5")`}
/>
<Box>
@@ -199,31 +187,32 @@ Add a delay too!
<MiniRepl
client:visible
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),
n("<4 [3@3 4] [<2 0> ~@16] ~>")
tune={`$: note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
.sound("gm_electric_guitar_muted").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)
)`}
.room(2).gain(.5)`}
/>
Let's add a bass to make this complete:
<MiniRepl
client:visible
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),
n("<4 [3@3 4] [<2 0> ~@16] ~>")
tune={`$: note("[~ [<[d3,a3,f4]!2 [d3,bb3,g4]!2> ~]]*2")
.sound("gm_electric_guitar_muted").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),
n("[0 [~ 0] 4 [3 2] [0 ~] [0 ~] <0 2> ~]/2")
.room(2).gain(.4)
$: n("[0 [~ 0] 4 [3 2] [0 ~] [0 ~] <0 2> ~]/2")
.scale("D2:minor")
.sound("sawtooth,triangle").lpf(800)
)`}
.sound("sawtooth,triangle").lpf(800)`}
/>
<Box>
@@ -266,7 +255,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>
+24 -14
View File
@@ -314,11 +314,11 @@ Let's recap what we've learned in this chapter:
New functions:
| Name | Description | Example |
| ----- | ----------------------------------- | --------------------------------------------------------------------------------- |
| note | set pitch as number or letter | <MiniRepl client:visible tune={`note("b g e c").sound("piano")`} /> |
| scale | interpret `n` as scale degree | <MiniRepl client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | play patterns in parallel (read on) | <MiniRepl client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
| Name | Description | Example |
| ----- | ----------------------------- | --------------------------------------------------------------------------------- |
| note | set pitch as number or letter | <MiniRepl client:visible tune={`note("b g e c").sound("piano")`} /> |
| scale | interpret `n` as scale degree | <MiniRepl client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| $: | play patterns in parallel | <MiniRepl client:visible tune={'$: s("bd sd")\n$: note("c eb g")'} /> |
## Examples
@@ -356,25 +356,35 @@ New functions:
<Box>
It's called `stack` 😙
You can use `$:` 😙
</Box>
## Playing multiple patterns
If you want to play multiple patterns at the same time, make sure to write `$:` before each:
<MiniRepl
client:visible
tune={`stack(
note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
.sound("gm_synth_bass_1").lpf(800),
n(\`<
tune={`$: note("<[c2 c3]*4 [bb1 bb2]*4 [f2 f3]*4 [eb2 eb3]*4>")
.sound("gm_synth_bass_1").lpf(800)
$: n(\`<
[~ 0] 2 [0 2] [~ 2]
[~ 0] 1 [0 1] [~ 1]
[~ 0] 3 [0 3] [~ 3]
[~ 0] 2 [0 2] [~ 2]
>*4\`).scale("C4:minor")
.sound("gm_synth_strings_1"),
sound("bd*4, [~ <sd cp>]*2, [~ hh]*4")
.bank("RolandTR909")
)`}
.sound("gm_synth_strings_1")
$: sound("bd*4, [~ <sd cp>]*2, [~ hh]*4")
.bank("RolandTR909")`}
/>
<Box>
Try changing `$` to `_$` to mute a part!
</Box>
This is starting to sound like actual music! We have sounds, we have notes, now the last piece of the puzzle is missing: [effects](/workshop/first-effects)
+13 -21
View File
@@ -25,20 +25,16 @@ This is the same as:
<MiniRepl
client:visible
tune={`stack(
n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(0),
n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(1).rev()
)`}
tune={`$: n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(0)
$: n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(1).rev()`}
/>
Let's visualize what happens here:
<MiniRepl
client:visible
tune={`stack(
n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(0).color("cyan"),
n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(1).color("magenta").rev()
)`}
tune={`$: n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(0).color("cyan")
$: n("0 1 [4 3] 2 0 2 [~ 3] 4").sound("jazz").pan(1).color("magenta").rev()`}
punchcard
/>
@@ -56,11 +52,9 @@ This is like doing
<MiniRepl
client:visible
tune={`stack(
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(0.5).color('cyan'),
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(1).color('magenta'),
note("c2, eb3 g3 [bb3 c4]").s("piano").slow(1.5).color('yellow')
)`}
tune={`$: note("c2, eb3 g3 [bb3 c4]").s("piano").slow(0.5).color('cyan')
$: note("c2, eb3 g3 [bb3 c4]").s("piano").slow(1).color('magenta')
$: note("c2, eb3 g3 [bb3 c4]").s("piano").slow(1.5).color('yellow')`}
punchcard
/>
@@ -110,17 +104,15 @@ We can add as often as we like:
<MiniRepl
client:visible
tune={`stack(
n("0 [2 4] <3 5> [~ <4 1>]".add("<0 [0,2,4]>"))
tune={`$: n("0 [2 4] <3 5> [~ <4 1>]".add("<0 [0,2,4]>"))
.scale("C5:minor")
.sound("gm_xylophone")
.room(.4).delay(.125),
note("c2 [eb3,g3]".add("<0 <1 -1>>"))
.room(.4).delay(.125)
$: note("c2 [eb3,g3]".add("<0 <1 -1>>"))
.adsr("[.1 0]:.2:[1 0]")
.sound("gm_acoustic_bass")
.room(.5),
n("0 1 [2 3] 2").sound("jazz").jux(rev)
)`}
.room(.5)
$: n("0 1 [2 3] 2").sound("jazz").jux(rev)`}
/>
**ply**
@@ -165,7 +157,7 @@ off is also useful for modifying other sounds, and can even be nested:
client:visible
tune={`s("bd sd [rim bd] sd,[~ hh]*4").bank("CasioRZ1")
.off(2/16, x=>x.speed(1.5).gain(.25)
.off(3/16, y=>y.vowel("<a e i o>*8")))`}
.off(3/16, y=>y.vowel("<a e i o>*8")))`}
/>
| name | description | example |
+1 -1
View File
@@ -39,7 +39,7 @@ This page is just a listing of all functions covered in the workshop!
| --------- | ----------------------------- | --------------------------------------------------------------------------------- |
| note | set pitch as number or letter | <MiniRepl client:visible tune={`note("b g e c").sound("piano")`} /> |
| n + scale | set note in scale | <MiniRepl client:visible tune={`n("6 4 2 0").scale("C:minor").sound("piano")`} /> |
| stack | play patterns in parallel | <MiniRepl client:visible tune={`stack(s("bd sd"),note("c eb g"))`} /> |
| $: | play patterns in parallel | <MiniRepl client:visible tune={'$: s("bd sd")\n$: note("c eb g")'} /> |
## Audio Effects