Skip to content

Commit

Permalink
Merge pull request #1125 from tidalcycles/viz-doc
Browse files Browse the repository at this point in the history
doc: visual functions + refactor onPaint
  • Loading branch information
felixroos authored Jun 3, 2024
2 parents ab10863 + 300022d commit 364f511
Show file tree
Hide file tree
Showing 22 changed files with 646 additions and 348 deletions.
39 changes: 13 additions & 26 deletions packages/codemirror/codemirror.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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?.();
},
Expand All @@ -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({
Expand Down Expand Up @@ -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) {
Expand All @@ -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');
}
Expand Down
2 changes: 1 addition & 1 deletion packages/codemirror/themes/algoboy.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion packages/codemirror/themes/teletext.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
20 changes: 12 additions & 8 deletions packages/core/controls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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');
Expand All @@ -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');
Expand All @@ -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');
Expand All @@ -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');
Expand Down Expand Up @@ -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');
/**
Expand All @@ -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');
/**
Expand Down Expand Up @@ -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');
/**
Expand All @@ -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');
Expand Down
20 changes: 16 additions & 4 deletions packages/core/cyclist.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/evaluate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
16 changes: 13 additions & 3 deletions packages/core/pattern.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down Expand Up @@ -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()
Expand Down
17 changes: 13 additions & 4 deletions packages/core/repl.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function repl({
defaultOutput,
onEvalError,
beforeEval,
beforeStart,
afterEval,
getTime,
transpiler,
Expand All @@ -19,6 +20,7 @@ export function repl({
sync = false,
setInterval,
clearInterval,
id,
}) {
const state = {
schedulerError: undefined,
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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?

Expand Down Expand Up @@ -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));
}
Expand All @@ -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 || [],
Expand Down
Loading

0 comments on commit 364f511

Please sign in to comment.