diff --git a/repl/src/evaluate.ts b/repl/src/evaluate.ts index 07388ded..0318a791 100644 --- a/repl/src/evaluate.ts +++ b/repl/src/evaluate.ts @@ -13,7 +13,7 @@ import * as uiHelpers from './ui.mjs'; import * as drawHelpers from './draw.mjs'; import gist from './gist.js'; import shapeshifter from './shapeshifter'; -import { minify } from './parse'; +import { mini } from './parse'; import * as Tone from 'tone'; import * as toneHelpers from './tone'; import * as voicingHelpers from './voicings'; @@ -37,19 +37,17 @@ hackLiteral(String, ['mini', 'm'], bootstrapped.mini); // comment out this line hackLiteral(String, ['pure', 'p'], bootstrapped.pure); // comment out this line if you panic // this will add everything to global scope, which is accessed by eval -Object.assign(globalThis, bootstrapped, Tone, toneHelpers, voicingHelpers, drawHelpers, uiHelpers, { gist, euclid }); +Object.assign(globalThis, bootstrapped, Tone, toneHelpers, voicingHelpers, drawHelpers, uiHelpers, { + gist, + euclid, + mini, +}); export const evaluate: any = async (code: string) => { const shapeshifted = shapeshifter(code); // transform syntactically correct js code to semantically usable code - // console.log('shapeshifted', shapeshifted); drawHelpers.cleanup(); + uiHelpers.cleanup(); let evaluated = await eval(shapeshifted); - if (typeof evaluated === 'function') { - evaluated = evaluated(); - } - if (typeof evaluated === 'string') { - evaluated = strudel.withLocationOffset(minify(evaluated), { start: { line: 1, column: -1 } }); - } if (evaluated?.constructor?.name !== 'Pattern') { const message = `got "${typeof evaluated}" instead of pattern`; throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.')); diff --git a/repl/src/parse.ts b/repl/src/parse.ts index 215c5a3c..509a6e61 100644 --- a/repl/src/parse.ts +++ b/repl/src/parse.ts @@ -28,7 +28,7 @@ const applyOptions = (parent: any) => (pat: any, i: number) => { const unimplemented = Object.keys(options || {}).filter((key) => key !== 'operator'); if (unimplemented.length) { console.warn( - `option${unimplemented.length > 1 ? 's' : ''} ${unimplemented.map((o) => `"${o}"`).join(', ')} not implemented` + `option${unimplemented.length > 1 ? 's' : ''} ${unimplemented.map((o) => `"${o}"`).join(', ')} not implemented`, ); } return pat; @@ -103,12 +103,10 @@ export function patternifyAST(ast: any): any { return ast.source_; } const { start, end } = ast.location_; - // should we really parse as number here? strings wont be fareyed... const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_; - // return ast.source_; - // the following line expects the shapeshifter to wrap this in withLocationOffset + // the following line expects the shapeshifter append .withMiniLocation // because location_ is only relative to the mini string, but we need it relative to whole code - return pure(value).withLocation({ start, end }); + return pure(value).withLocation([start.line, start.column, start.offset], [end.line, end.column, end.offset]); } return patternifyAST(ast.source_); case 'stretch': diff --git a/repl/src/shapeshifter.js b/repl/src/shapeshifter.js index 000a640b..a6d7eae4 100644 --- a/repl/src/shapeshifter.js +++ b/repl/src/shapeshifter.js @@ -6,7 +6,9 @@ import { IdentifierExpression, CallExpression, StaticMemberExpression, - Script, + ReturnStatement, + ArrayExpression, + LiteralNumericExpression, } from 'shift-ast'; import codegen from 'shift-codegen'; import * as strudel from '../../strudel.mjs'; @@ -18,14 +20,8 @@ const isNote = (name) => /^[a-gC-G][bs]?[0-9]$/.test(name); const addLocations = true; export const addMiniLocations = true; -/* -not supported for highlighting: -- 'b3'.p -- mini('b3') / m('b3') -- 'b3'.m / 'b3'.mini -*/ - -export default (code) => { +export default (_code) => { + const { code, addReturn } = wrapAsync(_code); const ast = parseScriptWithLocation(code); const artificialNodes = []; const parents = []; @@ -37,23 +33,20 @@ export default (code) => { return node; } - // replace template string `xxx` with 'xxx'.m + // replace template string `xxx` with mini(`xxx`) if (isBackTickString(node)) { - const minified = getMinified(node.elements[0].rawValue); - return wrapLocationOffset(minified, node, ast.locations, artificialNodes); + return minifyWithLocation(node, node, ast.locations, artificialNodes); } - // allows to use top level strings, which are normally directives... but we don't need directives - if (node.type === 'Script' && node.directives.length === 1 && !node.statements.length) { - const minified = getMinified(node.directives[0].rawValue); - const wrapped = wrapLocationOffset(minified, node.directives[0], ast.locations, artificialNodes); - return new Script({ directives: [], statements: [wrapped] }); + if (node.directives?.length === 1 && !node.statements?.length) { + const str = new LiteralStringExpression({ value: node.directives[0].rawValue }); + const wrapped = minifyWithLocation(str, node.directives[0], ast.locations, artificialNodes); + return { ...node, directives: [], statements: [wrapped] }; } - // replace double quote string "xxx" with 'xxx'.m + // replace double quote string "xxx" with mini('xxx') if (isStringWithDoubleQuotes(node, ast.locations, code)) { - const minified = getMinified(node.value); - return wrapLocationOffset(minified, node, ast.locations, artificialNodes); + return minifyWithLocation(node, node, ast.locations, artificialNodes); } // operator overloading => still not done @@ -87,7 +80,6 @@ export default (code) => { if (node.type === 'CallExpression' && node.callee.name === 'pure') { const literal = node.arguments[0]; // const value = literal[{ LiteralNumericExpression: 'value', LiteralStringExpression: 'name' }[literal.type]]; - // console.log('value',value); return reifyWithLocation(literal, node.arguments[0], ast.locations, artificialNodes); } // replace pseudo note variables @@ -103,28 +95,16 @@ export default (code) => { return new IdentifierExpression({ name: 'silence' }); } } - if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) { - // console.log('add', node); + if ( + addLocations && + ['LiteralStringExpression' /* , 'LiteralNumericExpression' */].includes(node.type) && + isMarkable + ) { + // TODO: to make LiteralNumericExpression work, we need to make sure we're not inside timeCat... return reifyWithLocation(node, node, ast.locations, artificialNodes); } - if (!addMiniLocations) { - return wrapFunction('reify', 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, artificialNodes); - } - if (node.type === 'StaticMemberExpression' && miniFunctions.includes(node.property) && !isAlreadyWrapped) { - // 'c3'.mini or 'c3'.m - return wrapLocationOffset(node, node.object, ast.locations, artificialNodes); + if (addMiniLocations) { + return addMiniNotationLocations(node, ast.locations, artificialNodes); } return node; }, @@ -132,9 +112,50 @@ export default (code) => { parents.pop(); }, }); - return codegen(shifted); + // add return to last statement (because it's wrapped in an async function artificially) + addReturn(shifted); + const generated = codegen(shifted); + return generated; }; +function wrapAsync(code) { + // wrap code in async to make await work on top level => this will create 1 line offset to locations + // this is why line offset is -1 in getLocationObject calls below + code = `(async () => { +${code} +})()`; + const addReturn = (ast) => { + const body = ast.statements[0].expression.callee.body; // actual code ast inside async function body + body.statements = body.statements + .slice(0, -1) + .concat([new ReturnStatement({ expression: body.statements.slice(-1)[0] })]); + }; + return { + code, + addReturn, + }; +} + +function addMiniNotationLocations(node, locations, artificialNodes) { + const miniFunctions = ['mini', 'm']; + // const isAlreadyWrapped = parent?.type === 'CallExpression' && parent.callee.name === 'withLocationOffset'; + if (node.type === 'CallExpression' && miniFunctions.includes(node.callee.name)) { + // 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; + } + const str = node.arguments[0]; + return minifyWithLocation(str, str, locations, artificialNodes); + } + if (node.type === 'StaticMemberExpression' && miniFunctions.includes(node.property)) { + // 'c3'.mini or 'c3'.m + return minifyWithLocation(node.object, node, locations, artificialNodes); + } + return node; +} + function wrapFunction(name, ...args) { return new CallExpression({ callee: new IdentifierExpression({ name }), @@ -142,13 +163,6 @@ function wrapFunction(name, ...args) { }); } -function getMinified(value) { - return new StaticMemberExpression({ - object: new LiteralStringExpression({ value }), - property: 'm', - }); -} - function isBackTickString(node) { return node.type === 'TemplateExpression' && node.elements.length === 1; } @@ -196,143 +210,52 @@ function canBeOverloaded(node) { // TODO: support sequence(c3).transpose(3).x.y.z } -// turn node into withLocationOffset(node, location) -function wrapLocationOffset(node, stringNode, locations, artificialNodes) { - // console.log('wrapppp', stringNode); - const expression = { - type: 'CallExpression', - callee: { - type: 'IdentifierExpression', - name: 'withLocationOffset', - }, - arguments: [node, getLocationObject(stringNode, locations)], - }; - artificialNodes.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(literalNode, node, locations, artificialNodes) { + const args = getLocationArguments(node, locations); const withLocation = new CallExpression({ callee: new StaticMemberExpression({ object: wrapFunction('reify', literalNode), property: 'withLocation', }), - arguments: [getLocationObject(node, locations)], + arguments: args, }); artificialNodes.push(withLocation); return withLocation; } -// returns ast for source location object -function getLocationObject(node, locations) { - /*const locationAST = parseScript( - "x=" + JSON.stringify(ast.locations.get(node)) - ).statements[0].expression.expression; - - console.log("locationAST", locationAST);*/ - - /*const callAST = parseScript( - `reify(${node.name}).withLocation(${JSON.stringify( - ast.locations.get(node) - )})` - ).statements[0].expression;*/ - const loc = locations.get(node); - return { - type: 'ObjectExpression', - properties: [ - { - type: 'DataProperty', - name: { - type: 'StaticPropertyName', - value: 'start', - }, - expression: { - type: 'ObjectExpression', - properties: [ - { - type: 'DataProperty', - name: { - type: 'StaticPropertyName', - value: 'line', - }, - expression: { - type: 'LiteralNumericExpression', - value: loc.start.line, - }, - }, - { - type: 'DataProperty', - name: { - type: 'StaticPropertyName', - value: 'column', - }, - expression: { - type: 'LiteralNumericExpression', - value: loc.start.column, - }, - }, - { - type: 'DataProperty', - name: { - type: 'StaticPropertyName', - value: 'offset', - }, - expression: { - type: 'LiteralNumericExpression', - value: loc.start.offset, - }, - }, - ], - }, - }, - { - type: 'DataProperty', - name: { - type: 'StaticPropertyName', - value: 'end', - }, - expression: { - type: 'ObjectExpression', - properties: [ - { - type: 'DataProperty', - name: { - type: 'StaticPropertyName', - value: 'line', - }, - expression: { - type: 'LiteralNumericExpression', - value: loc.end.line, - }, - }, - { - type: 'DataProperty', - name: { - type: 'StaticPropertyName', - value: 'column', - }, - expression: { - type: 'LiteralNumericExpression', - value: loc.end.column, - }, - }, - { - type: 'DataProperty', - name: { - type: 'StaticPropertyName', - value: 'offset', - }, - expression: { - type: 'LiteralNumericExpression', - value: loc.end.offset, - }, - }, - ], - }, - }, - ], - }; +// 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 minifyWithLocation(literalNode, node, locations, artificialNodes) { + const args = getLocationArguments(node, locations); + const withLocation = new CallExpression({ + callee: new StaticMemberExpression({ + object: wrapFunction('mini', literalNode), + property: 'withMiniLocation', + }), + arguments: args, + }); + artificialNodes.push(withLocation); + return withLocation; +} + +function getLocationArguments(node, locations) { + const loc = locations.get(node); + return [ + new ArrayExpression({ + elements: [ + new LiteralNumericExpression({ value: loc.start.line - 1 }), // the minus 1 assumes the code has been wrapped in async iife + new LiteralNumericExpression({ value: loc.start.column }), + new LiteralNumericExpression({ value: loc.start.offset }), + ], + }), + new ArrayExpression({ + elements: [ + new LiteralNumericExpression({ value: loc.end.line - 1 }), // the minus 1 assumes the code has been wrapped in async iife + new LiteralNumericExpression({ value: loc.end.column }), + new LiteralNumericExpression({ value: loc.end.offset }), + ], + }), + ]; } diff --git a/repl/src/tunes.ts b/repl/src/tunes.ts index e5666aad..17c0e930 100644 --- a/repl/src/tunes.ts +++ b/repl/src/tunes.ts @@ -358,13 +358,26 @@ stack( "c1*2".tone(membrane().chain(vol(0.8),out())) ).slow(1)`; -export const drums = `stack( +export const synthDrums = `stack( "c1*2".tone(membrane().chain(vol(0.8),out())), "~ c3".tone(noise().chain(vol(0.8),out())), "c3*4".transpose("[-24 0]*2").tone(metal(adsr(0,.015)).chain(vol(0.8),out())) ) `; +export const sampleDrums = `const drums = await players({ + bd: 'bd/BT0A0D0.wav', + sn: 'sn/ST0T0S3.wav', + hh: 'hh/000_hh3closedhh.wav' +}, 'https://loophole-letters.vercel.app/samples/tidal/') + +stack( + "", + "hh*4", + "~ " +).tone(drums.chain(out())) +`; + export const xylophoneCalling = `const t = x => x.scaleTranspose("<0 2 4 3>/4").transpose(-2) const s = x => x.scale(slowcat('C3 minor pentatonic','G3 minor pentatonic').slow(4)) const delay = new FeedbackDelay(1/8, .6).chain(vol(0.1), out()); @@ -437,71 +450,82 @@ stack( "[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("/8") ).fast(6/8)`; -export const barryHarris = `piano() -.then(p => "0,2,[7 6]" +export const barryHarris = `backgroundImage( + 'https://media.npr.org/assets/img/2017/02/03/barryharris_600dpi_wide-7eb49998aa1af377d62bb098041624c0a0d1a454.jpg', + {style:'background-size:cover'}) + +"0,2,[7 6]" .add("<0 1 2 3 4 5 7 8>") .scale('C bebop major') .transpose("<0 1 2 1>/8") .slow(2) - .tone(p.toDestination())) + .tone((await piano()).toDestination()) `; -export const blippyRhodes = `Promise.all([ - players({ - bd: 'samples/tidal/bd/BT0A0D0.wav', - sn: 'samples/tidal/sn/ST0T0S3.wav', - hh: 'samples/tidal/hh/000_hh3closedhh.wav' - }, 'https://loophole-letters.vercel.app/'), - sampler({ - E1: 'samples/rhodes/MK2Md2000.mp3', - E2: 'samples/rhodes/MK2Md2012.mp3', - E3: 'samples/rhodes/MK2Md2024.mp3', - E4: 'samples/rhodes/MK2Md2036.mp3', - E5: 'samples/rhodes/MK2Md2048.mp3', - E6: 'samples/rhodes/MK2Md2060.mp3', - E7: 'samples/rhodes/MK2Md2072.mp3' - }, 'https://loophole-letters.vercel.app/') -]) - .then(([drums, rhodes])=>{ - const delay = new FeedbackDelay(1/12, .4).chain(vol(0.3), out()); - rhodes = rhodes.chain(vol(0.5).connect(delay), out()); - const bass = synth(osc('sawtooth8')).chain(vol(.5),out()); - const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', slowcat('Db major','Db mixolydian')]).slow(4); - const t = x => x.scale(scales); - return stack( - " ".tone(drums.chain(out())), - "]>".apply(t).struct("x*8").apply(scaleTranspose("0 [-5,-2] -7 [-9,-2]")).legato(.2).slow(2).tone(rhodes), - //"]>".slow(2).voicings().struct("~ x").legato(.25).tone(rhodes), - "".legato("<1@3 [.3 1]>").slow(2).tone(bass), - ).fast(3/2) -})`; +export const blippyRhodes = `const delay = new FeedbackDelay(1/12, .4).chain(vol(0.3), out()); -export const wavyKalimba = `sampler({ +const drums = await players({ + bd: 'samples/tidal/bd/BT0A0D0.wav', + sn: 'samples/tidal/sn/ST0T0S3.wav', + hh: 'samples/tidal/hh/000_hh3closedhh.wav' +}, 'https://loophole-letters.vercel.app/') + +const rhodes = await sampler({ + E1: 'samples/rhodes/MK2Md2000.mp3', + E2: 'samples/rhodes/MK2Md2012.mp3', + E3: 'samples/rhodes/MK2Md2024.mp3', + E4: 'samples/rhodes/MK2Md2036.mp3', + E5: 'samples/rhodes/MK2Md2048.mp3', + E6: 'samples/rhodes/MK2Md2060.mp3', + E7: 'samples/rhodes/MK2Md2072.mp3' +}, 'https://loophole-letters.vercel.app/') + +const bass = synth(osc('sawtooth8')).chain(vol(.5),out()) +const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', slowcat('Db major','Db mixolydian')]).slow(4) + +stack( + " " + .tone(drums.chain(out())), + "]>" + .scale(scales) + .struct("x*8") + .scaleTranspose("0 [-5,-2] -7 [-9,-2]") + .legato(.3) + .slow(2) + .tone(rhodes.chain(vol(0.5).connect(delay), out())), + //"]>".slow(2).voicings().struct("~ x").legato(.25).tone(rhodes), + "" + .legato("<1@3 [.3 1]>") + .slow(2) + .tone(bass), +).fast(3/2)`; + +export const wavyKalimba = `const delay = new FeedbackDelay(1/3, .5).chain(vol(.2), out()); +let kalimba = await sampler({ C5: 'https://freesound.org/data/previews/536/536549_11935698-lq.mp3' -}).then((kalimba)=>{ - const delay = new FeedbackDelay(1/3, .5).chain(vol(.2), out()); - kalimba = kalimba.chain(vol(0.6).connect(delay),out()); - const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major']).slow(4); - return stack( - "[0 2 4 6 9 2 0 -2]*3" - .add("<0 2>/4") - .scale(scales) - .struct("x*8") - .velocity("<.8 .3 .6>*8") - .slow(2) - .tone(kalimba), - "" - .scale(scales) - .scaleTranspose("[0 <2 4>]*2") - .struct("x*4") - .velocity("<.8 .5>*4") - .velocity(0.8) - .slow(2) - .tone(kalimba) - ) - .legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8") - .fast(1) -})`; +}) +kalimba = kalimba.chain(vol(0.6).connect(delay),out()); +const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major']).slow(4); + +stack( + "[0 2 4 6 9 2 0 -2]*3" + .add("<0 2>/4") + .scale(scales) + .struct("x*8") + .velocity("<.8 .3 .6>*8") + .slow(2) + .tone(kalimba), + "" + .scale(scales) + .scaleTranspose("[0 <2 4>]*2") + .struct("x*4") + .velocity("<.8 .5>*4") + .velocity(0.8) + .slow(2) + .tone(kalimba) +) + .legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8") + .fast(1)`; export const jemblung = `const delay = new FeedbackDelay(1/8, .6).chain(vol(0.15), out()); const snare = noise({type:'white',...adsr(0,0.2,0)}).chain(lowpass(5000),vol(1.8),out()); @@ -530,8 +554,7 @@ stack( "c2*8".tone(noise().chain(highpass(6000),vol(0.5).connect(delay),out())), ).slow(3)`; -export const risingEnemy = `piano().then((p) => -stack( +export const risingEnemy = `stack( "2,6" .scale('F3 dorian') .transpose(sine2.struct("x*64").slow(4).mul(2).round()) @@ -543,11 +566,10 @@ stack( .transpose("<0 1 2 1>/2".early(0.5)) .transpose(5) .fast(2 / 3) - .tone(p.toDestination()) -)`; + .tone((await piano()).toDestination())`; export const festivalOfFingers = `const chords = ""; -piano().then(p=>stack( +stack( chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), chords.rootNotes(2).struct("x(4,8,-2)"), chords.rootNotes(4) @@ -555,22 +577,19 @@ piano().then(p=>stack( .struct("x(3,8,-2)".fast(2)) .scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4")) ).slow(2) - //.pianoroll() .velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8)) - .tone(p.chain(out())))`; + .tone((await piano()).chain(out()))`; export const festivalOfFingers2 = `const chords = ""; - const scales = slowcat('C minor','F dorian','G dorian','F# mixolydian') - piano().then(p=>stack( - chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), - chords.rootNotes(2).struct("x(4,8)"), - chords.rootNotes(4) - .scale(scales) - .struct("x(3,8,-2)".fast(2)) - .scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/3")) - ).slow(2).transpose(-1) - .legato(cosine.struct("x*8").add(4/5).mul(4/5).fast(8)) - .velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8)) - // .pianoroll() - .tone(p.chain(out())).fast(3/4) - )`; +const scales = slowcat('C minor','F dorian','G dorian','F# mixolydian') +stack( + chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), + chords.rootNotes(2).struct("x(4,8)"), + chords.rootNotes(4) + .scale(scales) + .struct("x(3,8,-2)".fast(2)) + .scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/3")) +).slow(2).transpose(-1) + .legato(cosine.struct("x*8").add(4/5).mul(4/5).fast(8)) + .velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8)) + .tone((await piano()).chain(out())).fast(3/4)`; diff --git a/repl/src/ui.mjs b/repl/src/ui.mjs index c9c5a9cd..34eb5d30 100644 --- a/repl/src/ui.mjs +++ b/repl/src/ui.mjs @@ -37,6 +37,12 @@ export const backgroundImage = function (src, animateOptions = {}) { frame((_, t) => funcOptions.forEach(([option, value]) => { handleOption(option, value(t)); - }) + }), ); }; + +export const cleanup = () => { + const container = document.getElementById('code'); + container.style = ''; + container.className = 'grow relative'; // has to match App.tsx +}; diff --git a/strudel.mjs b/strudel.mjs index 8efc8c69..aba06811 100644 --- a/strudel.mjs +++ b/strudel.mjs @@ -267,13 +267,42 @@ class Pattern { return this._withEvent(event => event.setContext({})) } - withLocation(location) { + withLocation(start, end) { + const location = { + start: { line: start[0], column: start[1], offset: start[2] }, + end: { line: end[0], column: end[1], offset: end[2] }, + }; return this._withContext((context) => { const locations = (context.locations || []).concat([location]) return { ...context, locations } }); } + withMiniLocation(start, end) { + const offset = { + start: { line: start[0], column: start[1], offset: start[2] }, + end: { line: end[0], column: end[1], offset: end[2] }, + }; + return this._withContext((context) => { + let locations = (context.locations || []); + locations = locations.map(({ start, end }) => { + const colOffset = start.line === 1 ? 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 {...context, locations } + }); + } + withValue(func) { // Returns a new pattern, with the function applied to the value of // each event. It has the alias 'fmap'. @@ -990,33 +1019,10 @@ Pattern.prototype.define = (name, func, options = {}) => { Pattern.prototype.define('hush', (pat) => pat.hush(), { patternified: false, composable: true }); Pattern.prototype.define('bypass', (pat) => pat.bypass(on), { patternified: true, composable: true }); -// this is wrapped around mini patterns to offset krill parser location into the global js code space -function withLocationOffset(pat, offset) { - return pat._withContext((context) => { - let locations = (context.locations || []); - locations = locations.map(({ start, end }) => { - const colOffset = start.line === 1 ? 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 {...context, locations } - }); -} - export {Fraction, TimeSpan, Hap, Pattern, pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr, reify, silence, fast, slow, early, late, rev, add, sub, mul, div, union, every, when, off, jux, append, superimpose, struct, mask, invert, inv, - withLocationOffset }