Compare commits

...

28 Commits

Author SHA1 Message Date
Felix Roos c9e4b16460 Merge branch 'main' into claviature-take2 2024-07-08 18:03:50 +02: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
Felix Roos 6350dfa31f claviature tweaks 2024-04-03 21:31:17 +02:00
Felix Roos fe691adb28 Merge branch 'main' into claviature-take2 2024-03-30 16:08:08 +01:00
Felix Roos 187307c6bc half working claviature 2024-03-28 15:06:50 +01:00
33 changed files with 862 additions and 486 deletions
+13 -26
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');
}
+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;
+6
View File
@@ -127,6 +127,12 @@ registerWidget('_scope', (id, options = {}, pat) => {
return pat.tag(id).scope({ ...options, ctx, id });
});
registerWidget('_claviature', (id, options = {}, pat) => {
options = { height: 75, width: 640, ...options };
const ctx = getCanvasWidget(id, options).getContext('2d');
return pat.tag(id).claviature({ ...options, ctx, id });
});
registerWidget('_pitchwheel', (id, options = {}, pat) => {
let _size = options.size || 200;
options = { width: _size, height: _size, ...options, size: _size / 5 };
+12 -8
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');
+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;
}
+13 -3
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()
+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 || [],
+114
View File
@@ -0,0 +1,114 @@
import { Pattern, noteToMidi } from '@strudel/core';
const blackPattern = [0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0];
export const tokenizeNote = (note) => {
if (typeof note !== 'string') {
return [];
}
const [pc, acc = '', oct] = note.match(/^([a-gA-G])([#bs]*)([0-9])?$/)?.slice(1) || [];
if (!pc) {
return [];
}
return [pc, acc, oct ? Number(oct) : undefined];
};
const accs = { '#': 1, b: -1, s: 1 };
const toMidi = (note) => {
if (typeof note === 'number') {
return note;
}
const [pc, acc, oct] = tokenizeNote(note);
if (!pc) {
throw new Error('not a note: "' + note + '"');
}
const chroma = { c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }[pc.toLowerCase()];
const offset = acc?.split('').reduce((o, char) => o + accs[char], 0) || 0;
return (Number(oct) + 1) * 12 + chroma + offset;
};
const getMidiKeys = (range, offset) => {
const white /* : number[] */ = [];
const black /* : number[] */ = [];
const to = noteToMidi(range[1]);
for (let i = offset; i <= to; i++) {
//
(blackPattern[i % 12] ? black : white).push(i);
}
return [white, black];
};
const whiteWidth = (midi, topWidth) => (midi % 12 > 4 ? 7 / 4 : 5 / 3) * topWidth;
const whiteX = (midi, offset, topWidth) =>
Array.from({ length: midi - offset }, (_, i) => i + offset).reduce(
(sum, m) => (!blackPattern[m % 12] ? sum + whiteWidth(m, topWidth) : sum),
0,
); // TODO: calculate mathematically
/* const blackX = (index, offset, topWidth) => {
const cDiff = 12 - (offset % 12);
console.log('cDiff', cDiff);
const cOffset = whiteX(cDiff + offset);
const blackOffset = cOffset + cDiff * topWidth;
return (index - offset) * topWidth + blackOffset;
}; */
const parseNote = (note) => (typeof note === 'number' ? note : toMidi(note));
export function claviature(haps, options) {
const {
ctx = getDrawContext(),
range = ['C1', 'D3'],
scaleX = 1,
scaleY = 1,
palette = [getTheme().foreground, getTheme().background],
strokeWidth = 0,
stroke = getTheme().foreground,
upperWidth = 14,
upperHeight = 100,
lowerHeight = 45,
} = options || {};
const offset = parseNote(range[0]);
const colorizedMidi = haps.map((hap) => ({
keys: [parseNote(hap.value.note)],
color: hap.value.color || getTheme().selection,
}));
/* const to = parseNote(range[1]);
const totalKeys = to - offset + 1; */
/* const width = totalKeys * topWidth + topWidth + strokeWidth * 2;
const height = whiteHeight; */
const topWidth = upperWidth * scaleX;
const [white, black] = getMidiKeys(range, offset);
const whiteHeight = (upperHeight + lowerHeight) * scaleY;
const blackHeight = upperHeight * scaleY;
const cDiff = 12 - (offset % 12);
const cOffset = whiteX(cDiff + offset);
const blackOffset = cOffset;
const blackX = (midi) => (midi - offset) * topWidth + blackOffset;
const getColor = (midi) => colorizedMidi.find(({ keys }) => keys.includes(midi))?.color;
ctx.clearRect(0, 0, ctx.canvas.width * 2, ctx.canvas.height * 2);
ctx.strokeStyle = 'white';
ctx.strokeWidth = strokeWidth;
white.forEach((midi) => {
ctx.fillStyle = getColor(midi) ?? palette[1];
const x = whiteX(midi, offset, topWidth);
const width = whiteWidth(midi, topWidth);
ctx.fillRect(x, 0, width, whiteHeight);
ctx.strokeRect(x, 0, width, whiteHeight);
});
black.forEach((midi) => {
ctx.fillStyle = getColor(midi) ?? palette[0];
const x = blackX(midi, offset, topWidth);
//ctx.strokeRect(x, 0, topWidth, blackHeight);
ctx.fillRect(x, 0, topWidth, blackHeight);
});
}
Pattern.prototype.claviature = function (options) {
return this.draw((haps) => claviature(haps, options), { id: options.id });
};
+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);
}
+1
View File
@@ -3,4 +3,5 @@ export * from './color.mjs';
export * from './draw.mjs';
export * from './pianoroll.mjs';
export * from './spiral.mjs';
export * from './claviature.mjs';
export * from './pitchwheel.mjs';
+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 }));
};
+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 -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 = {}) {
+330 -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 ]",
]
`;
@@ -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 ]",
+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