move evaluate/evaluateBlock to different functions
This commit is contained in:
parent
1a7f464998
commit
160ef84b47
4 changed files with 152 additions and 109 deletions
|
|
@ -44,7 +44,7 @@ export const evalBlock = (strudelMirror) => {
|
||||||
if (block) {
|
if (block) {
|
||||||
// Flash the block being evaluated
|
// Flash the block being evaluated
|
||||||
strudelMirror.flash(200, { from: a, to: b });
|
strudelMirror.flash(200, { from: a, to: b });
|
||||||
strudelMirror.repl.evaluate(block, true, true, { range });
|
strudelMirror.repl.evaluateBlock(block, true, { range });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -187,6 +187,48 @@ export function repl({
|
||||||
};
|
};
|
||||||
setTime(() => scheduler.now()); // TODO: refactor?
|
setTime(() => scheduler.now()); // TODO: refactor?
|
||||||
|
|
||||||
|
// Helper function to apply pattern transformations (solo, each, all)
|
||||||
|
// this should be abstracted more
|
||||||
|
function applyPatternTransforms(pattern) {
|
||||||
|
const allPatterns = Object.values(pPatterns);
|
||||||
|
|
||||||
|
if (allPatterns.length) {
|
||||||
|
let patterns = [];
|
||||||
|
let soloActive = false;
|
||||||
|
for (const [key, value] of Object.entries(pPatterns)) {
|
||||||
|
// handle soloed patterns ex: S$: s("bd!4")
|
||||||
|
const isSolod = key.length > 1 && key.startsWith('S');
|
||||||
|
if (isSolod && soloActive === false) {
|
||||||
|
// first time we see a soloed pattern, clear existing patterns
|
||||||
|
patterns = [];
|
||||||
|
soloActive = true;
|
||||||
|
}
|
||||||
|
if (!soloActive || (soloActive && isSolod)) {
|
||||||
|
const valWithState = value.withState((state) => state.setControls({ id: key }));
|
||||||
|
patterns.push(valWithState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (eachTransform) {
|
||||||
|
// Explicit lambda so only element (not index and array) are passed
|
||||||
|
patterns = patterns.map((x) => eachTransform(x));
|
||||||
|
}
|
||||||
|
pattern = stack(...patterns);
|
||||||
|
} else if (eachTransform) {
|
||||||
|
pattern = eachTransform(pattern);
|
||||||
|
}
|
||||||
|
if (allTransforms.length) {
|
||||||
|
for (const transform of allTransforms) {
|
||||||
|
pattern = transform(pattern);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isPattern(pattern)) {
|
||||||
|
pattern = silence;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pattern;
|
||||||
|
}
|
||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
codeBlocks = {};
|
codeBlocks = {};
|
||||||
blockPatterns.clear();
|
blockPatterns.clear();
|
||||||
|
|
@ -306,7 +348,7 @@ export function repl({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const evaluate = async (code, autostart = true, blockBased = false, options = {}) => {
|
const evaluate = async (code, autostart = true) => {
|
||||||
if (!code) {
|
if (!code) {
|
||||||
throw new Error('no code to evaluate');
|
throw new Error('no code to evaluate');
|
||||||
}
|
}
|
||||||
|
|
@ -314,20 +356,60 @@ export function repl({
|
||||||
updateState({ code, pending: true });
|
updateState({ code, pending: true });
|
||||||
await injectPatternMethods();
|
await injectPatternMethods();
|
||||||
setTime(() => scheduler.now()); // TODO: refactor?
|
setTime(() => scheduler.now()); // TODO: refactor?
|
||||||
await beforeEval?.({ code, blockBased });
|
await beforeEval?.({ code, blockBased: false });
|
||||||
|
allTransforms = []; // reset all transforms
|
||||||
|
|
||||||
|
codeBlocks = {};
|
||||||
|
hush();
|
||||||
|
|
||||||
|
if (mondo) {
|
||||||
|
code = `mondolang\`${code}\``;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { pattern, meta } = await _evaluate(code, transpiler, transpilerOptions);
|
||||||
|
|
||||||
|
pattern = applyPatternTransforms(pattern);
|
||||||
|
|
||||||
|
logger(`[eval] code updated`);
|
||||||
|
pattern = await setPattern(pattern, autostart);
|
||||||
|
updateState({
|
||||||
|
miniLocations: meta?.miniLocations || [],
|
||||||
|
widgets: meta?.widgets || [],
|
||||||
|
sliders: meta?.sliders || [],
|
||||||
|
activeCode: code,
|
||||||
|
pattern,
|
||||||
|
evalError: undefined,
|
||||||
|
schedulerError: undefined,
|
||||||
|
pending: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEval?.({ code, pattern, meta, range: undefined, widgetRemoved: false });
|
||||||
|
return pattern;
|
||||||
|
} catch (err) {
|
||||||
|
logger(`[eval] error: ${err.message}`, 'error');
|
||||||
|
console.error(err);
|
||||||
|
updateState({ evalError: err, pending: false });
|
||||||
|
onEvalError?.(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const evaluateBlock = async (code, autostart = true, options = {}) => {
|
||||||
|
if (!code) {
|
||||||
|
throw new Error('no code to evaluate');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
updateState({ code, pending: true });
|
||||||
|
await injectPatternMethods();
|
||||||
|
setTime(() => scheduler.now()); // TODO: refactor?
|
||||||
|
await beforeEval?.({ code, blockBased: true });
|
||||||
allTransforms = []; // reset all transforms
|
allTransforms = []; // reset all transforms
|
||||||
|
|
||||||
const transpilerOptionsWithBlock = {
|
const transpilerOptionsWithBlock = {
|
||||||
...transpilerOptions,
|
...transpilerOptions,
|
||||||
blockBased,
|
blockBased: true,
|
||||||
range: options.range || [],
|
range: options.range || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!blockBased) {
|
|
||||||
codeBlocks = {};
|
|
||||||
hush();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mondo) {
|
if (mondo) {
|
||||||
code = `mondolang\`${code}\``;
|
code = `mondolang\`${code}\``;
|
||||||
}
|
}
|
||||||
|
|
@ -337,101 +419,61 @@ export function repl({
|
||||||
// Track activeVisualizer cleanup: check if any block's visualizer was removed
|
// Track activeVisualizer cleanup: check if any block's visualizer was removed
|
||||||
let widgetRemoved = false;
|
let widgetRemoved = false;
|
||||||
|
|
||||||
if (blockBased) {
|
const labels = meta.labels || [];
|
||||||
const labels = meta.labels || [];
|
|
||||||
|
|
||||||
// Store code blocks in dictionary using labels as keys
|
// Store code blocks in dictionary using labels as keys
|
||||||
if (labels.length > 0) {
|
if (labels.length > 0) {
|
||||||
for (let i = 0; i < labels.length; i++) {
|
for (let i = 0; i < labels.length; i++) {
|
||||||
// processLabeledBlock(labels, i, code, options, meta);
|
// processing transpiler output instead of code is simply to avoid
|
||||||
// processing transpiler output instead of code is simply to avoid
|
// extra regex in detecting whether or not an inline widget has been commented out
|
||||||
// extra regex in detecting whether or not an inline widget has been commented out
|
processLabeledBlock(labels, i, meta.output, options, meta);
|
||||||
processLabeledBlock(labels, i, meta.output, options, meta);
|
|
||||||
}
|
|
||||||
} else if (pattern !== silence) {
|
|
||||||
// variable/function declarations that return silence are allowed,
|
|
||||||
// but anonymous pattern blocks pose an issue for block-based evaluation
|
|
||||||
// if an anonymous pattern is evaluated multiple times it will just stack and get louder and louder
|
|
||||||
|
|
||||||
// it's very common for users to write code prefixed with '$'
|
|
||||||
// but to modify and override existing patterns, the patterns must be labeled,
|
|
||||||
// otherwise we'll have no idea of which pattern is being overridden
|
|
||||||
|
|
||||||
// (we probably need to update the docs on this)
|
|
||||||
|
|
||||||
// we could easily enable it, but it would confuse a lot of people
|
|
||||||
throw new Error(
|
|
||||||
'anonymous labels disabled for block based evaluation (see https://strudel.cc/blog/#label-notation)',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
} else if (pattern !== silence) {
|
||||||
|
// variable/function declarations that return silence are allowed,
|
||||||
|
// but anonymous pattern blocks pose an issue for block-based evaluation
|
||||||
|
// if an anonymous pattern is evaluated multiple times it will just stack and get louder and louder
|
||||||
|
|
||||||
// For block-based evaluation, collect from all blocks
|
// it's very common for users to write code prefixed with '$'
|
||||||
// The highlight/widget/slider systems will handle selective updates based on the range
|
// but to modify and override existing patterns, the patterns must be labeled,
|
||||||
meta.miniLocations = collectFromBlocks('miniLocations');
|
// otherwise we'll have no idea of which pattern is being overridden
|
||||||
meta.widgets = collectFromBlocks('widgets');
|
|
||||||
meta.sliders = collectFromBlocks('sliders');
|
|
||||||
|
|
||||||
// Track activeVisualizer cleanup: check if any block's visualizer was removed
|
// (we probably need to update the docs on this)
|
||||||
const blocksToUpdate = labels.map((label) => label.name);
|
|
||||||
|
|
||||||
for (const [key, block] of Object.entries(codeBlocks)) {
|
// we could easily enable it, but it would confuse a lot of people
|
||||||
if (blocksToUpdate.includes(key)) {
|
throw new Error(
|
||||||
// This block was just updated
|
'anonymous labels disabled for block based evaluation (see https://strudel.cc/blog/#label-notation)',
|
||||||
if (block.activeVisualizer !== null) {
|
|
||||||
// Block now has a visualizer, update tracking
|
|
||||||
lastActiveVisualizerLabel = key;
|
|
||||||
} else if (lastActiveVisualizerLabel === key) {
|
|
||||||
// This block lost its visualizer, trigger cleanup
|
|
||||||
widgetRemoved = true;
|
|
||||||
lastActiveVisualizerLabel = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const allPatterns = Object.values(pPatterns);
|
|
||||||
|
|
||||||
if (blockBased) {
|
|
||||||
pPatterns = Object.fromEntries(
|
|
||||||
Object.entries(pPatterns).filter(([key]) => {
|
|
||||||
return Object.keys(codeBlocks).includes(key);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allPatterns.length) {
|
meta.miniLocations = collectFromBlocks('miniLocations');
|
||||||
let patterns = [];
|
meta.widgets = collectFromBlocks('widgets');
|
||||||
let soloActive = false;
|
meta.sliders = collectFromBlocks('sliders');
|
||||||
for (const [key, value] of Object.entries(pPatterns)) {
|
|
||||||
// handle soloed patterns ex: S$: s("bd!4")
|
// Track activeVisualizer cleanup: check if any block's visualizer was removed
|
||||||
const isSolod = key.length > 1 && key.startsWith('S');
|
const blocksToUpdate = labels.map((label) => label.name);
|
||||||
if (isSolod && soloActive === false) {
|
|
||||||
// first time we see a soloed pattern, clear existing patterns
|
// this is the hackiest bit
|
||||||
patterns = [];
|
for (const [key, block] of Object.entries(codeBlocks)) {
|
||||||
soloActive = true;
|
if (blocksToUpdate.includes(key)) {
|
||||||
|
// This block was just updated
|
||||||
|
if (block.activeVisualizer !== null) {
|
||||||
|
// Block now has a visualizer, update tracking
|
||||||
|
lastActiveVisualizerLabel = key;
|
||||||
|
} else if (lastActiveVisualizerLabel === key) {
|
||||||
|
// This block lost its visualizer, trigger cleanup
|
||||||
|
widgetRemoved = true;
|
||||||
|
lastActiveVisualizerLabel = null;
|
||||||
}
|
}
|
||||||
if (!soloActive || (soloActive && isSolod)) {
|
|
||||||
const valWithState = value.withState((state) => state.setControls({ id: key }));
|
|
||||||
patterns.push(valWithState);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (eachTransform) {
|
|
||||||
// Explicit lambda so only element (not index and array) are passed
|
|
||||||
patterns = patterns.map((x) => eachTransform(x));
|
|
||||||
}
|
|
||||||
pattern = stack(...patterns);
|
|
||||||
} else if (eachTransform) {
|
|
||||||
pattern = eachTransform(pattern);
|
|
||||||
}
|
|
||||||
if (allTransforms.length) {
|
|
||||||
for (const transform of allTransforms) {
|
|
||||||
pattern = transform(pattern);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isPattern(pattern)) {
|
pPatterns = Object.fromEntries(
|
||||||
pattern = silence;
|
Object.entries(pPatterns).filter(([key]) => {
|
||||||
}
|
return Object.keys(codeBlocks).includes(key);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
pattern = applyPatternTransforms(pattern);
|
||||||
|
|
||||||
logger(`[eval] code updated`);
|
logger(`[eval] code updated`);
|
||||||
pattern = await setPattern(pattern, autostart);
|
pattern = await setPattern(pattern, autostart);
|
||||||
|
|
@ -455,24 +497,25 @@ export function repl({
|
||||||
onEvalError?.(err);
|
onEvalError?.(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCode = (code) => updateState({ code });
|
const setCode = (code) => updateState({ code });
|
||||||
return { scheduler, evaluate, start, stop, pause, setCps, setPattern, setCode, toggle, state };
|
return { scheduler, evaluate, evaluateBlock, start, stop, pause, setCps, setPattern, setCode, toggle, state };
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTrigger =
|
export const getTrigger =
|
||||||
({ getTime, defaultOutput }) =>
|
({ getTime, defaultOutput }) =>
|
||||||
async (hap, deadline, duration, cps, t) => {
|
async (hap, deadline, duration, cps, t) => {
|
||||||
// ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger)
|
// ^ this signature is different from hap.context.onTrigger, as set by Pattern.onTrigger(onTrigger)
|
||||||
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
|
// TODO: get rid of deadline after https://codeberg.org/uzu/strudel/pulls/1004
|
||||||
try {
|
try {
|
||||||
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
if (!hap.context.onTrigger || !hap.context.dominantTrigger) {
|
||||||
await defaultOutput(hap, deadline, duration, cps, t);
|
await defaultOutput(hap, deadline, duration, cps, t);
|
||||||
}
|
|
||||||
if (hap.context.onTrigger) {
|
|
||||||
// call signature of output / onTrigger is different...
|
|
||||||
await hap.context.onTrigger(hap, getTime(), cps, t);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
errorLogger(err, 'getTrigger');
|
|
||||||
}
|
}
|
||||||
};
|
if (hap.context.onTrigger) {
|
||||||
|
// call signature of output / onTrigger is different...
|
||||||
|
await hap.context.onTrigger(hap, getTime(), cps, t);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
errorLogger(err, 'getTrigger');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ export function transpiler(input, options = {}) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
leave(node, parent, prop, index) { },
|
leave(node, parent, prop, index) {},
|
||||||
});
|
});
|
||||||
|
|
||||||
let { body } = ast;
|
let { body } = ast;
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ const skippedExamples = [
|
||||||
'accelerationX',
|
'accelerationX',
|
||||||
'defaultmidimap',
|
'defaultmidimap',
|
||||||
'midimaps',
|
'midimaps',
|
||||||
'clearScope'
|
'clearScope',
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('runs examples', () => {
|
describe('runs examples', () => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue