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

View File

@ -1936,6 +1936,7 @@ function peg$parse(input, options) {
this.type_ = "element";
this.source_ = source;
this.options_ = options;
this.location_ = location();
}
var CommandStub = function(name, options)

View File

@ -604,6 +604,30 @@ Pattern.prototype.bootstrap = () => {
}));
return bootstrapped;
};
function withLocationOffset(pat, offset) {
let startLine;
return pat.fmap((value) => {
value = typeof value === "object" && !Array.isArray(value) ? value : {value};
let locations = value.locations || [];
startLine = startLine || locations[0].start.line;
locations = locations.map(({start, end}) => {
const colOffset = startLine === end.line ? offset.start.column : 0;
return {
start: {
...start,
line: start.line - 1 + (offset.start.line - 1) + 1,
column: start.column - 1 + colOffset
},
end: {
...end,
line: end.line - 1 + (offset.start.line - 1) + 1,
column: end.column - 1 + colOffset
}
};
});
return {...value, locations};
});
}
export {
Fraction,
TimeSpan,
@ -641,5 +665,6 @@ export {
struct,
mask,
invert,
inv
inv,
withLocationOffset
};

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);
});
};

View File

@ -41646,6 +41646,8 @@ function useRepl({ tune , defaultSynth , autolink =true , onEvent }) {
setError(undefined);
setActiveCode(_code);
} catch (err) {
err.message = 'evaluation error: ' + err.message;
console.warn(err);
setError(err);
}
};
@ -41684,6 +41686,7 @@ function useRepl({ tune , defaultSynth , autolink =true , onEvent }) {
try {
return pattern?.query(span) || [];
} catch (err) {
err.message = 'query error: ' + err.message;
setError(err);
return [];
}
@ -41794,6 +41797,7 @@ hackLiteral(String, [
Object.assign(globalThis, bootstrapped, _tone1, _tone);
const evaluate = (code)=>{
const shapeshifted = _shapeshifterDefault.default(code); // transform syntactically correct js code to semantically usable code
// console.log('shapeshifted', shapeshifted);
let evaluated = eval(shapeshifted);
if (typeof evaluated === 'function') evaluated = evaluated();
const pattern = _parse.minify(evaluated); // eval and minify (if user entered a string)
@ -41890,6 +41894,8 @@ parcelHelpers.export(exports, "invert", ()=>invert
);
parcelHelpers.export(exports, "inv", ()=>inv
);
parcelHelpers.export(exports, "withLocationOffset", ()=>withLocationOffset
);
var _fractionJs = require("fraction.js");
var _fractionJsDefault = parcelHelpers.interopDefault(_fractionJs);
var _ramda = require("ramda"); // will remove this as soon as compose is implemented here
@ -42712,6 +42718,37 @@ Pattern.prototype.bootstrap = ()=>{
}));
return bootstrapped;
};
// this is wrapped around mini patterns to offset krill parser location into the global js code space
function withLocationOffset(pat, offset) {
// console.log('with offfset',pat,offset);
let startLine;
return pat.fmap((value)=>{
value = typeof value === 'object' && !Array.isArray(value) ? value : {
value
};
let locations = value.locations || [];
startLine = startLine || locations[0].start.line;
locations = locations.map(({ start , end })=>{
const colOffset = startLine === end.line ? offset.start.column : 0;
return {
start: {
...start,
line: start.line - 1 + (offset.start.line - 1) + 1,
column: start.column - 1 + colOffset
},
end: {
...end,
line: end.line - 1 + (offset.start.line - 1) + 1,
column: end.column - 1 + colOffset
}
};
});
return {
...value,
locations
};
});
}
},{"fraction.js":"1Q5M2","ramda":"10uzi","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"1Q5M2":[function(require,module,exports) {
/**
@ -57806,7 +57843,7 @@ Pattern.prototype.voicings = function(range) {
'A4'
];
return this.fmapNested((event)=>{
lastVoicing = getVoicing(event.value, lastVoicing, range);
lastVoicing = getVoicing(event.value?.value || event.value, lastVoicing, range);
return _strudelMjs.stack(...lastVoicing);
});
};
@ -62438,7 +62475,7 @@ var _tonal = require("@tonaljs/tonal");
var _strudelMjs = require("../../strudel.mjs");
const Pattern = _strudelMjs.Pattern;
function toNoteEvent(event) {
if (typeof event === 'string') return {
if (typeof event === 'string' || typeof event === 'number') return {
value: event
};
if (event.value) return event;
@ -62488,6 +62525,12 @@ Pattern.prototype._mapNotes = function(func) {
Pattern.prototype._transpose = function(intervalOrSemitones) {
return this._mapNotes(({ value , scale })=>{
const interval = !isNaN(Number(intervalOrSemitones)) ? _tonal.Interval.fromSemitones(intervalOrSemitones) : String(intervalOrSemitones);
if (typeof value === 'number') {
const semitones = typeof interval === 'string' ? _tonal.Interval.semitones(interval) || 0 : interval;
return {
value: value + semitones
};
}
return {
value: _tonal.Note.transpose(value, interval),
scale
@ -62501,7 +62544,8 @@ Pattern.prototype._transpose = function(intervalOrSemitones) {
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
Pattern.prototype._scaleTranspose = function(offset) {
return this._mapNotes(({ value , scale })=>{
if (!scale) throw new Error('can only use scaleOffset after .scale');
if (!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
@ -62556,6 +62600,8 @@ Pattern.prototype.define('groove', (groove, pat)=>pat.groove(groove)
},{"../../strudel.mjs":"ggZqJ"}],"67UCx":[function(require,module,exports) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "addMiniLocations", ()=>addMiniLocations
);
var _indexJs = require("./shift-parser/index.js"); // npm module does not work in the browser
var _shiftTraverser = require("./shift-traverser"); // npm module does not work in the browser
var _shiftTraverserDefault = parcelHelpers.interopDefault(_shiftTraverser);
@ -62568,26 +62614,49 @@ const { Pattern } = _strudelMjs;
const isNote = (name)=>/^[a-gC-G][bs]?[0-9]$/.test(name)
;
const addLocations = true;
const addMiniLocations = true;
exports.default = (code)=>{
const ast = _indexJs.parseScriptWithLocation(code);
const nodesWithLocation = [];
const parents = [];
const shifted = replace(ast.tree, {
enter (node, parent1) {
parents.push(parent1);
enter (node, parent) {
parents.push(parent);
const isSynthetic = parents.some((p)=>nodesWithLocation.includes(p)
);
if (isSynthetic) 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 = parent1?.type === 'ArrayExpression' && isPatternArg(grandparent);
const isMarkable = isPatternArg(parent1) || 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 _shiftAst.CallExpression({
callee: new _shiftAst.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);
if (addLocations && isMarkable) return reifyWithLocation(value, node, ast.locations, nodesWithLocation);
return new _shiftAst.LiteralStringExpression({
value
});
@ -62597,7 +62666,25 @@ exports.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;
},
leave () {
@ -62606,15 +62693,50 @@ exports.default = (code)=>{
});
return _shiftCodegenDefault.default(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 _shiftAst.CallExpression({
callee: new _shiftAst.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 _shiftAst.CallExpression({
callee: new _shiftAst.StaticMemberExpression({
object: new _shiftAst.CallExpression({
callee: new _shiftAst.IdentifierExpression({
name: 'pure'
name: 'reify'
}),
arguments: [
new _shiftAst.LiteralStringExpression({
@ -62638,7 +62760,7 @@ function getLocationObject(node, locations) {
).statements[0].expression.expression;
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;*/ const loc = locations.get(node);
@ -95771,6 +95893,7 @@ parcelHelpers.export(exports, "minify", ()=>minify
var _krillParser = require("../krill-parser");
var _strudelMjs = require("../../strudel.mjs");
var _tonal = require("@tonaljs/tonal");
var _shapeshifter = require("./shapeshifter");
const { pure , Pattern , Fraction , stack , slowcat , sequence , timeCat , silence } = _strudelMjs;
const applyOptions = (parent)=>(pat, i)=>{
const ast = parent.source_[i];
@ -95816,6 +95939,7 @@ function resolveReplications(ast) {
{
type_: 'element',
source_: child.source_,
location_: child.location_,
options_: {
operator: {
type_: 'stretch',
@ -95857,7 +95981,21 @@ function patternifyAST(ast) {
return sequence(...children);
case 'element':
if (ast.source_ === '~') return silence;
if (typeof ast.source_ !== 'object') return ast.source_;
if (typeof ast.source_ !== 'object') {
if (!_shapeshifter.addMiniLocations) return ast.source_;
if (!ast.location_) {
console.warn('no location for', ast);
return ast.source_;
}
const { start , end } = ast.location_;
// return ast.source_;
// the following line expects the shapeshifter to wrap this in withLocationOffset
// because location_ is only relative to the mini string, but we need it relative to whole code
return pure(ast.source_).withLocation({
start,
end
});
}
return patternifyAST(ast.source_);
case 'stretch':
return patternifyAST(ast.source_).slow(ast.arguments_.amount);
@ -95919,7 +96057,7 @@ function minify(thing) {
return reify(thing);
}
},{"../krill-parser":"l2lgS","../../strudel.mjs":"ggZqJ","@tonaljs/tonal":"4q9Lu","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"l2lgS":[function(require,module,exports) {
},{"../krill-parser":"l2lgS","../../strudel.mjs":"ggZqJ","@tonaljs/tonal":"4q9Lu","./shapeshifter":"67UCx","@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}],"l2lgS":[function(require,module,exports) {
var parcelHelpers = require("@parcel/transformer-js/src/esmodule-helpers.js");
parcelHelpers.defineInteropFlag(exports);
parcelHelpers.export(exports, "SyntaxError", ()=>peg$SyntaxError
@ -97786,6 +97924,7 @@ function peg$parse(input, options1) {
this.type_ = "element";
this.source_ = source;
this.options_ = options;
this.location_ = location1();
};
var CommandStub = function(name, options) {
this.type_ = "command";
@ -109137,4 +109276,4 @@ exports.default = cx;
},{"@parcel/transformer-js/src/esmodule-helpers.js":"gkKU3"}]},["3uVTb"], "3uVTb", "parcelRequire94c2")
//# sourceMappingURL=index.98344030.js.map
//# sourceMappingURL=index.e1279fc8.js.map

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,6 @@
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/tutorial/index.98344030.js" defer=""></script>
<script src="/tutorial/index.e1279fc8.js" defer=""></script>
</body>
</html>