mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-10 05:08:30 +00:00
* 0.5 default cps * 1 -> 0.5 cps defaults * start moving examples to 2Hz * more 2Hz doc edits * small tweaks * format * adapt cycles page * adapt pitch page * tonal page * accumulation * synth page * adapt conditional-modifiers * audio effects page * adapt signals doc * fix: errors for signals * adapt signals page * start time modifiers * adapt time modifiers * adapt factories * hydra + pattern intro * adapt mini notation page * start recipes * adapt recipes page * use code_v1 table * delete old dbdump + add new csv based tool * fix: tests * fix: cpm * shuffle featured patterns * fix: snapshot --------- Co-authored-by: Felix Roos <flix91@gmail.com>
176 lines
4.8 KiB
JavaScript
176 lines
4.8 KiB
JavaScript
import { Cyclist } from './cyclist.mjs';
|
|
import { evaluate as _evaluate } from './evaluate.mjs';
|
|
import { logger } from './logger.mjs';
|
|
import { setTime } from './time.mjs';
|
|
import { evalScope } from './evaluate.mjs';
|
|
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
|
|
|
|
export function repl({
|
|
interval,
|
|
defaultOutput,
|
|
onSchedulerError,
|
|
onEvalError,
|
|
beforeEval,
|
|
afterEval,
|
|
getTime,
|
|
transpiler,
|
|
onToggle,
|
|
editPattern,
|
|
onUpdateState,
|
|
}) {
|
|
const state = {
|
|
schedulerError: undefined,
|
|
evalError: undefined,
|
|
code: '// LOADING',
|
|
activeCode: '// LOADING',
|
|
pattern: undefined,
|
|
miniLocations: [],
|
|
widgets: [],
|
|
pending: false,
|
|
started: false,
|
|
};
|
|
|
|
const updateState = (update) => {
|
|
Object.assign(state, update);
|
|
state.isDirty = state.code !== state.activeCode;
|
|
state.error = state.evalError || state.schedulerError;
|
|
onUpdateState?.(state);
|
|
};
|
|
|
|
const scheduler = new Cyclist({
|
|
interval,
|
|
onTrigger: getTrigger({ defaultOutput, getTime }),
|
|
onError: onSchedulerError,
|
|
getTime,
|
|
onToggle: (started) => {
|
|
updateState({ started });
|
|
onToggle?.(started);
|
|
},
|
|
});
|
|
let pPatterns = {};
|
|
let allTransform;
|
|
|
|
const hush = function () {
|
|
pPatterns = {};
|
|
allTransform = undefined;
|
|
return silence;
|
|
};
|
|
|
|
const setPattern = (pattern, autostart = true) => {
|
|
pattern = editPattern?.(pattern) || pattern;
|
|
scheduler.setPattern(pattern, autostart);
|
|
};
|
|
setTime(() => scheduler.now()); // TODO: refactor?
|
|
|
|
const stop = () => scheduler.stop();
|
|
const start = () => scheduler.start();
|
|
const pause = () => scheduler.pause();
|
|
const toggle = () => scheduler.toggle();
|
|
const setCps = (cps) => scheduler.setCps(cps);
|
|
const setCpm = (cpm) => scheduler.setCps(cpm / 60);
|
|
const all = function (transform) {
|
|
allTransform = transform;
|
|
return silence;
|
|
};
|
|
|
|
// set pattern methods that use this repl via closure
|
|
const injectPatternMethods = () => {
|
|
Pattern.prototype.p = function (id) {
|
|
pPatterns[id] = this;
|
|
return this;
|
|
};
|
|
Pattern.prototype.q = function (id) {
|
|
return silence;
|
|
};
|
|
try {
|
|
for (let i = 1; i < 10; ++i) {
|
|
Object.defineProperty(Pattern.prototype, `d${i}`, {
|
|
get() {
|
|
return this.p(i);
|
|
},
|
|
configurable: true,
|
|
});
|
|
Object.defineProperty(Pattern.prototype, `p${i}`, {
|
|
get() {
|
|
return this.p(i);
|
|
},
|
|
configurable: true,
|
|
});
|
|
Pattern.prototype[`q${i}`] = silence;
|
|
}
|
|
} catch (err) {
|
|
console.warn('injectPatternMethods: error:', err);
|
|
}
|
|
const cpm = register('cpm', function (cpm, pat) {
|
|
return pat._fast(cpm / 60 / scheduler.cps);
|
|
});
|
|
evalScope({
|
|
all,
|
|
hush,
|
|
cpm,
|
|
setCps,
|
|
setcps: setCps,
|
|
setCpm,
|
|
setcpm: setCpm,
|
|
});
|
|
};
|
|
|
|
const evaluate = async (code, autostart = true, shouldHush = true) => {
|
|
if (!code) {
|
|
throw new Error('no code to evaluate');
|
|
}
|
|
try {
|
|
updateState({ code, pending: true });
|
|
injectPatternMethods();
|
|
await beforeEval?.({ code });
|
|
shouldHush && hush();
|
|
let { pattern, meta } = await _evaluate(code, transpiler);
|
|
if (Object.keys(pPatterns).length) {
|
|
pattern = stack(...Object.values(pPatterns));
|
|
}
|
|
if (allTransform) {
|
|
pattern = allTransform(pattern);
|
|
}
|
|
if (!isPattern(pattern)) {
|
|
const message = `got "${typeof evaluated}" instead of pattern`;
|
|
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));
|
|
}
|
|
logger(`[eval] code updated`);
|
|
setPattern(pattern, autostart);
|
|
updateState({
|
|
miniLocations: meta?.miniLocations || [],
|
|
widgets: meta?.widgets || [],
|
|
activeCode: code,
|
|
pattern,
|
|
evalError: undefined,
|
|
schedulerError: undefined,
|
|
pending: false,
|
|
});
|
|
afterEval?.({ code, pattern, meta });
|
|
return pattern;
|
|
} catch (err) {
|
|
logger(`[eval] error: ${err.message}`, 'error');
|
|
updateState({ evalError: err, pending: false });
|
|
onEvalError?.(err);
|
|
}
|
|
};
|
|
const setCode = (code) => updateState({ code });
|
|
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state };
|
|
}
|
|
|
|
export const getTrigger =
|
|
({ getTime, defaultOutput }) =>
|
|
async (hap, deadline, duration, cps) => {
|
|
try {
|
|
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
|
await defaultOutput(hap, deadline, duration, cps);
|
|
}
|
|
if (hap.context.onTrigger) {
|
|
// call signature of output / onTrigger is different...
|
|
await hap.context.onTrigger(getTime() + deadline, hap, getTime(), cps);
|
|
}
|
|
} catch (err) {
|
|
logger(`[cyclist] error: ${err.message}`, 'error');
|
|
}
|
|
};
|