This commit is contained in:
Felix Roos 2022-02-21 01:00:12 +01:00
parent 3b248ae94c
commit 966de74d5b
10 changed files with 301 additions and 39 deletions

12
docs/dist/parse.js vendored
View file

@ -1,6 +1,7 @@
import * as krill from "../_snowpack/link/repl/krill-parser.js";
import * as strudel from "../_snowpack/link/strudel.js";
import {Scale, Note, Interval} from "../_snowpack/pkg/@tonaljs/tonal.js";
import {addMiniLocations} from "./shapeshifter.js";
const {pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence} = strudel;
const applyOptions = (parent) => (pat, i) => {
const ast = parent.source_[i];
@ -39,6 +40,7 @@ function resolveReplications(ast) {
{
type_: "element",
source_: child.source_,
location_: child.location_,
options_: {
operator: {
type_: "stretch",
@ -80,7 +82,15 @@ export function patternifyAST(ast) {
return silence;
}
if (typeof ast.source_ !== "object") {
return ast.source_;
if (!addMiniLocations) {
return ast.source_;
}
if (!ast.location_) {
console.warn("no location for", ast);
return ast.source_;
}
const {start, end} = ast.location_;
return pure(ast.source_).withLocation({start, end});
}
return patternifyAST(ast.source_);
case "stretch":

View file

@ -10,6 +10,7 @@ const { Pattern } = strudel;
const isNote = (name) => /^[a-gC-G][bs]?[0-9]$/.test(name);
const addLocations = true;
export const addMiniLocations = true;
export default (code) => {
const ast = parseScriptWithLocation(code);
@ -23,16 +24,39 @@ export default (code) => {
return node;
}
const grandparent = parents[parents.length - 2];
const isPatternArg = (parent) =>
parent?.type === 'CallExpression' && Object.keys(Pattern.prototype.factories).includes(parent.callee.name);
const isTimeCat = parent?.type === 'ArrayExpression' && isPatternArg(grandparent);
const isMarkable = isPatternArg(parent) || isTimeCat;
const isTimeCat = parent?.type === 'ArrayExpression' && isPatternFactory(grandparent);
const isMarkable = isPatternFactory(parent) || isTimeCat;
// operator overloading => still not done
const operators = {
'*': 'fast',
'/': 'slow',
'&': 'stack',
'&&': 'append',
};
if (
node.type === 'BinaryExpression' &&
operators[node.operator] &&
['LiteralNumericExpression', 'LiteralStringExpression', 'IdentifierExpression'].includes(node.right?.type) &&
canBeOverloaded(node.left)
) {
let arg = node.left;
if (node.left.type === 'IdentifierExpression') {
arg = wrapReify(node.left);
}
return new CallExpression({
callee: new StaticMemberExpression({
property: operators[node.operator],
object: wrapReify(arg),
}),
arguments: [node.right],
});
}
// replace pseudo note variables
if (node.type === 'IdentifierExpression') {
if (isNote(node.name)) {
const value = node.name[1] === 's' ? node.name.replace('s', '#') : node.name;
if (addLocations && isMarkable) {
return addPureWithLocation(value, node, ast.locations, nodesWithLocation);
return reifyWithLocation(value, node, ast.locations, nodesWithLocation);
}
return new LiteralStringExpression({ value });
}
@ -42,7 +66,26 @@ export default (code) => {
}
if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) {
// console.log('add', node);
return addPureWithLocation(node.value, node, ast.locations, nodesWithLocation);
return reifyWithLocation(node.value, node, ast.locations, nodesWithLocation);
}
if (!addMiniLocations) {
return node;
}
// mini notation location handling
const miniFunctions = ['mini', 'm'];
const isAlreadyWrapped = parent?.type === 'CallExpression' && parent.callee.name === 'withLocationOffset';
if (node.type === 'CallExpression' && miniFunctions.includes(node.callee.name) && !isAlreadyWrapped) {
// mini('c3')
if (node.arguments.length > 1) {
// TODO: transform mini(...args) to cat(...args.map(mini)) ?
console.warn('multi arg mini locations not supported yet...');
return node;
}
return wrapLocationOffset(node, node.arguments, ast.locations, nodesWithLocation);
}
if (node.type === 'StaticMemberExpression' && miniFunctions.includes(node.property) && !isAlreadyWrapped) {
// 'c3'.mini or 'c3'.m
return wrapLocationOffset(node, node.object, ast.locations, nodesWithLocation);
}
return node;
},
@ -53,15 +96,49 @@ export default (code) => {
return codegen(shifted);
};
// turns node in pure(value).withLocation(location), where location is the node's location in the source code
// with this, the pure pattern can pass its location to the event, to know where to highlight when it's active
function addPureWithLocation(value, node, locations, nodesWithLocation) {
// console.log('addPure', value, node);
function wrapReify(node) {
return new CallExpression({
callee: new IdentifierExpression({
name: 'reify',
}),
arguments: [node],
});
}
function isPatternFactory(node) {
return node?.type === 'CallExpression' && Object.keys(Pattern.prototype.factories).includes(node.callee.name);
}
function canBeOverloaded(node) {
return (node.type === 'IdentifierExpression' && isNote(node.name)) || isPatternFactory(node);
// TODO: support sequence(c3).transpose(3).x.y.z
}
// turn node into withLocationOffset(node, location)
function wrapLocationOffset(node, stringNode, locations, nodesWithLocation) {
// console.log('wrapppp', stringNode);
const expression = {
type: 'CallExpression',
callee: {
type: 'IdentifierExpression',
name: 'withLocationOffset',
},
arguments: [node, getLocationObject(stringNode, locations)],
};
nodesWithLocation.push(expression);
// console.log('wrapped', codegen(expression));
return expression;
}
// turns node in reify(value).withLocation(location), where location is the node's location in the source code
// with this, the reified pattern can pass its location to the event, to know where to highlight when it's active
function reifyWithLocation(value, node, locations, nodesWithLocation) {
// console.log('reifyWithLocation', value, node);
const withLocation = new CallExpression({
callee: new StaticMemberExpression({
object: new CallExpression({
callee: new IdentifierExpression({
name: 'pure',
name: 'reify',
}),
arguments: [new LiteralStringExpression({ value })],
}),
@ -82,7 +159,7 @@ function getLocationObject(node, locations) {
console.log("locationAST", locationAST);*/
/*const callAST = parseScript(
`pure(${node.name}).withLocation(${JSON.stringify(
`reify(${node.name}).withLocation(${JSON.stringify(
ast.locations.get(node)
)})`
).statements[0].expression;*/

11
docs/dist/tonal.js vendored
View file

@ -2,7 +2,7 @@ import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js";
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
const Pattern = _Pattern;
function toNoteEvent(event) {
if (typeof event === "string") {
if (typeof event === "string" || typeof event === "number") {
return {value: event};
}
if (event.value) {
@ -53,13 +53,20 @@ Pattern.prototype._mapNotes = function(func) {
Pattern.prototype._transpose = function(intervalOrSemitones) {
return this._mapNotes(({value, scale}) => {
const interval = !isNaN(Number(intervalOrSemitones)) ? Interval.fromSemitones(intervalOrSemitones) : String(intervalOrSemitones);
if (typeof value === "number") {
const semitones = typeof interval === "string" ? Interval.semitones(interval) || 0 : interval;
return {value: value + semitones};
}
return {value: Note.transpose(value, interval), scale};
});
};
Pattern.prototype._scaleTranspose = function(offset) {
return this._mapNotes(({value, scale}) => {
if (!scale) {
throw new Error("can only use scaleOffset after .scale");
throw new Error("can only use scaleTranspose after .scale");
}
if (typeof value !== "string") {
throw new Error("can only use scaleTranspose with notes");
}
return {value: scaleTranspose(scale, Number(offset), value), scale};
});

View file

@ -30,6 +30,8 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent}) {
setError(void 0);
setActiveCode(_code);
} catch (err) {
err.message = "evaluation error: " + err.message;
console.warn(err);
setError(err);
}
};
@ -68,6 +70,7 @@ function useRepl({tune, defaultSynth, autolink = true, onEvent}) {
try {
return pattern?.query(span) || [];
} catch (err) {
err.message = "query error: " + err.message;
setError(err);
return [];
}

View file

@ -18,7 +18,7 @@ Pattern.prototype.voicings = function(range) {
range = ["F3", "A4"];
}
return this.fmapNested((event) => {
lastVoicing = getVoicing(event.value, lastVoicing, range);
lastVoicing = getVoicing(event.value?.value || event.value, lastVoicing, range);
return stack(...lastVoicing);
});
};