allow await + refactor locations

This commit is contained in:
Felix Roos 2022-03-21 19:47:58 +01:00
parent a02a48ac82
commit 98f91607cd
6 changed files with 243 additions and 293 deletions

View File

@ -13,7 +13,7 @@ import * as uiHelpers from './ui.mjs';
import * as drawHelpers from './draw.mjs'; import * as drawHelpers from './draw.mjs';
import gist from './gist.js'; import gist from './gist.js';
import shapeshifter from './shapeshifter'; import shapeshifter from './shapeshifter';
import { minify } from './parse'; import { mini } from './parse';
import * as Tone from 'tone'; import * as Tone from 'tone';
import * as toneHelpers from './tone'; import * as toneHelpers from './tone';
import * as voicingHelpers from './voicings'; 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 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 // 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) => { export const evaluate: any = async (code: string) => {
const shapeshifted = shapeshifter(code); // transform syntactically correct js code to semantically usable code const shapeshifted = shapeshifter(code); // transform syntactically correct js code to semantically usable code
// console.log('shapeshifted', shapeshifted);
drawHelpers.cleanup(); drawHelpers.cleanup();
uiHelpers.cleanup();
let evaluated = await eval(shapeshifted); 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') { if (evaluated?.constructor?.name !== 'Pattern') {
const message = `got "${typeof evaluated}" instead of pattern`; const message = `got "${typeof evaluated}" instead of pattern`;
throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.')); throw new Error(message + (typeof evaluated === 'function' ? ', did you forget to call a function?' : '.'));

View File

@ -28,7 +28,7 @@ const applyOptions = (parent: any) => (pat: any, i: number) => {
const unimplemented = Object.keys(options || {}).filter((key) => key !== 'operator'); const unimplemented = Object.keys(options || {}).filter((key) => key !== 'operator');
if (unimplemented.length) { if (unimplemented.length) {
console.warn( 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; return pat;
@ -103,12 +103,10 @@ export function patternifyAST(ast: any): any {
return ast.source_; return ast.source_;
} }
const { start, end } = ast.location_; 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_; const value = !isNaN(Number(ast.source_)) ? Number(ast.source_) : ast.source_;
// return ast.source_; // the following line expects the shapeshifter append .withMiniLocation
// 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 // 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_); return patternifyAST(ast.source_);
case 'stretch': case 'stretch':

View File

@ -6,7 +6,9 @@ import {
IdentifierExpression, IdentifierExpression,
CallExpression, CallExpression,
StaticMemberExpression, StaticMemberExpression,
Script, ReturnStatement,
ArrayExpression,
LiteralNumericExpression,
} from 'shift-ast'; } from 'shift-ast';
import codegen from 'shift-codegen'; import codegen from 'shift-codegen';
import * as strudel from '../../strudel.mjs'; import * as strudel from '../../strudel.mjs';
@ -18,14 +20,8 @@ const isNote = (name) => /^[a-gC-G][bs]?[0-9]$/.test(name);
const addLocations = true; const addLocations = true;
export const addMiniLocations = true; export const addMiniLocations = true;
/* export default (_code) => {
not supported for highlighting: const { code, addReturn } = wrapAsync(_code);
- 'b3'.p
- mini('b3') / m('b3')
- 'b3'.m / 'b3'.mini
*/
export default (code) => {
const ast = parseScriptWithLocation(code); const ast = parseScriptWithLocation(code);
const artificialNodes = []; const artificialNodes = [];
const parents = []; const parents = [];
@ -37,23 +33,20 @@ export default (code) => {
return node; return node;
} }
// replace template string `xxx` with 'xxx'.m // replace template string `xxx` with mini(`xxx`)
if (isBackTickString(node)) { if (isBackTickString(node)) {
const minified = getMinified(node.elements[0].rawValue); return minifyWithLocation(node, node, ast.locations, artificialNodes);
return wrapLocationOffset(minified, node, ast.locations, artificialNodes);
} }
// allows to use top level strings, which are normally directives... but we don't need directives // 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) { if (node.directives?.length === 1 && !node.statements?.length) {
const minified = getMinified(node.directives[0].rawValue); const str = new LiteralStringExpression({ value: node.directives[0].rawValue });
const wrapped = wrapLocationOffset(minified, node.directives[0], ast.locations, artificialNodes); const wrapped = minifyWithLocation(str, node.directives[0], ast.locations, artificialNodes);
return new Script({ directives: [], statements: [wrapped] }); 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)) { if (isStringWithDoubleQuotes(node, ast.locations, code)) {
const minified = getMinified(node.value); return minifyWithLocation(node, node, ast.locations, artificialNodes);
return wrapLocationOffset(minified, node, ast.locations, artificialNodes);
} }
// operator overloading => still not done // operator overloading => still not done
@ -87,7 +80,6 @@ export default (code) => {
if (node.type === 'CallExpression' && node.callee.name === 'pure') { if (node.type === 'CallExpression' && node.callee.name === 'pure') {
const literal = node.arguments[0]; const literal = node.arguments[0];
// const value = literal[{ LiteralNumericExpression: 'value', LiteralStringExpression: 'name' }[literal.type]]; // const value = literal[{ LiteralNumericExpression: 'value', LiteralStringExpression: 'name' }[literal.type]];
// console.log('value',value);
return reifyWithLocation(literal, node.arguments[0], ast.locations, artificialNodes); return reifyWithLocation(literal, node.arguments[0], ast.locations, artificialNodes);
} }
// replace pseudo note variables // replace pseudo note variables
@ -103,28 +95,16 @@ export default (code) => {
return new IdentifierExpression({ name: 'silence' }); return new IdentifierExpression({ name: 'silence' });
} }
} }
if (addLocations && node.type === 'LiteralStringExpression' && isMarkable) { if (
// console.log('add', node); 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); return reifyWithLocation(node, node, ast.locations, artificialNodes);
} }
if (!addMiniLocations) { if (addMiniLocations) {
return wrapFunction('reify', node); return addMiniNotationLocations(node, ast.locations, artificialNodes);
}
// 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);
} }
return node; return node;
}, },
@ -132,9 +112,50 @@ export default (code) => {
parents.pop(); 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) { function wrapFunction(name, ...args) {
return new CallExpression({ return new CallExpression({
callee: new IdentifierExpression({ name }), 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) { function isBackTickString(node) {
return node.type === 'TemplateExpression' && node.elements.length === 1; return node.type === 'TemplateExpression' && node.elements.length === 1;
} }
@ -196,143 +210,52 @@ function canBeOverloaded(node) {
// TODO: support sequence(c3).transpose(3).x.y.z // 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 // 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 // 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) { function reifyWithLocation(literalNode, node, locations, artificialNodes) {
const args = getLocationArguments(node, locations);
const withLocation = new CallExpression({ const withLocation = new CallExpression({
callee: new StaticMemberExpression({ callee: new StaticMemberExpression({
object: wrapFunction('reify', literalNode), object: wrapFunction('reify', literalNode),
property: 'withLocation', property: 'withLocation',
}), }),
arguments: [getLocationObject(node, locations)], arguments: args,
}); });
artificialNodes.push(withLocation); artificialNodes.push(withLocation);
return withLocation; return withLocation;
} }
// returns ast for source location object // turns node in reify(value).withLocation(location), where location is the node's location in the source code
function getLocationObject(node, locations) { // with this, the reified pattern can pass its location to the event, to know where to highlight when it's active
/*const locationAST = parseScript( function minifyWithLocation(literalNode, node, locations, artificialNodes) {
"x=" + JSON.stringify(ast.locations.get(node)) const args = getLocationArguments(node, locations);
).statements[0].expression.expression; const withLocation = new CallExpression({
callee: new StaticMemberExpression({
console.log("locationAST", locationAST);*/ object: wrapFunction('mini', literalNode),
property: 'withMiniLocation',
/*const callAST = parseScript( }),
`reify(${node.name}).withLocation(${JSON.stringify( arguments: args,
ast.locations.get(node) });
)})` artificialNodes.push(withLocation);
).statements[0].expression;*/ return withLocation;
const loc = locations.get(node); }
return {
type: 'ObjectExpression', function getLocationArguments(node, locations) {
properties: [ const loc = locations.get(node);
{ return [
type: 'DataProperty', new ArrayExpression({
name: { elements: [
type: 'StaticPropertyName', new LiteralNumericExpression({ value: loc.start.line - 1 }), // the minus 1 assumes the code has been wrapped in async iife
value: 'start', new LiteralNumericExpression({ value: loc.start.column }),
}, new LiteralNumericExpression({ value: loc.start.offset }),
expression: { ],
type: 'ObjectExpression', }),
properties: [ new ArrayExpression({
{ elements: [
type: 'DataProperty', new LiteralNumericExpression({ value: loc.end.line - 1 }), // the minus 1 assumes the code has been wrapped in async iife
name: { new LiteralNumericExpression({ value: loc.end.column }),
type: 'StaticPropertyName', new LiteralNumericExpression({ value: loc.end.offset }),
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,
},
},
],
},
},
],
};
} }

View File

@ -358,13 +358,26 @@ stack(
"c1*2".tone(membrane().chain(vol(0.8),out())) "c1*2".tone(membrane().chain(vol(0.8),out()))
).slow(1)`; ).slow(1)`;
export const drums = `stack( export const synthDrums = `stack(
"c1*2".tone(membrane().chain(vol(0.8),out())), "c1*2".tone(membrane().chain(vol(0.8),out())),
"~ c3".tone(noise().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())) "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(
"<bd!3 bd(3,4,2)>",
"hh*4",
"~ <sn!3 sn(3,4,1)>"
).tone(drums.chain(out()))
`;
export const xylophoneCalling = `const t = x => x.scaleTranspose("<0 2 4 3>/4").transpose(-2) 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 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()); 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("<x x x ~>/8") "[2,4]/4".scale('D dorian').apply(t).tone(instr('pad')).mask("<x x x ~>/8")
).fast(6/8)`; ).fast(6/8)`;
export const barryHarris = `piano() export const barryHarris = `backgroundImage(
.then(p => "0,2,[7 6]" '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>") .add("<0 1 2 3 4 5 7 8>")
.scale('C bebop major') .scale('C bebop major')
.transpose("<0 1 2 1>/8") .transpose("<0 1 2 1>/8")
.slow(2) .slow(2)
.tone(p.toDestination())) .tone((await piano()).toDestination())
`; `;
export const blippyRhodes = `Promise.all([ export const blippyRhodes = `const delay = new FeedbackDelay(1/12, .4).chain(vol(0.3), out());
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(
"<bd sn> <hh hh*2 hh*3>".tone(drums.chain(out())),
"<g4 c5 a4 [ab4 <eb5 f5>]>".apply(t).struct("x*8").apply(scaleTranspose("0 [-5,-2] -7 [-9,-2]")).legato(.2).slow(2).tone(rhodes),
//"<C^7 C7 F^7 [Fm7 <Db^7 Db7>]>".slow(2).voicings().struct("~ x").legato(.25).tone(rhodes),
"<c2 c3 f2 [[F2 C2] db2]>".legato("<1@3 [.3 1]>").slow(2).tone(bass),
).fast(3/2)
})`;
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(
"<bd sn> <hh hh*2 hh*3>"
.tone(drums.chain(out())),
"<g4 c5 a4 [ab4 <eb5 f5>]>"
.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())),
//"<C^7 C7 F^7 [Fm7 <Db^7 Db7>]>".slow(2).voicings().struct("~ x").legato(.25).tone(rhodes),
"<c2 c3 f2 [[F2 C2] db2]>"
.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' 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());
kalimba = kalimba.chain(vol(0.6).connect(delay),out()); const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major']).slow(4);
const scales = sequence('C major', 'C mixolydian', 'F lydian', ['F minor', 'Db major']).slow(4);
return stack( stack(
"[0 2 4 6 9 2 0 -2]*3" "[0 2 4 6 9 2 0 -2]*3"
.add("<0 2>/4") .add("<0 2>/4")
.scale(scales) .scale(scales)
.struct("x*8") .struct("x*8")
.velocity("<.8 .3 .6>*8") .velocity("<.8 .3 .6>*8")
.slow(2) .slow(2)
.tone(kalimba), .tone(kalimba),
"<c2 c2 f2 [[F2 C2] db2]>" "<c2 c2 f2 [[F2 C2] db2]>"
.scale(scales) .scale(scales)
.scaleTranspose("[0 <2 4>]*2") .scaleTranspose("[0 <2 4>]*2")
.struct("x*4") .struct("x*4")
.velocity("<.8 .5>*4") .velocity("<.8 .5>*4")
.velocity(0.8) .velocity(0.8)
.slow(2) .slow(2)
.tone(kalimba) .tone(kalimba)
) )
.legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8") .legato("<.4 .8 1 1.2 1.4 1.6 1.8 2>/8")
.fast(1) .fast(1)`;
})`;
export const jemblung = `const delay = new FeedbackDelay(1/8, .6).chain(vol(0.15), out()); 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()); 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())), "c2*8".tone(noise().chain(highpass(6000),vol(0.5).connect(delay),out())),
).slow(3)`; ).slow(3)`;
export const risingEnemy = `piano().then((p) => export const risingEnemy = `stack(
stack(
"2,6" "2,6"
.scale('F3 dorian') .scale('F3 dorian')
.transpose(sine2.struct("x*64").slow(4).mul(2).round()) .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("<0 1 2 1>/2".early(0.5))
.transpose(5) .transpose(5)
.fast(2 / 3) .fast(2 / 3)
.tone(p.toDestination()) .tone((await piano()).toDestination())`;
)`;
export const festivalOfFingers = `const chords = "<Cm7 Fm7 G7 F#7>"; export const festivalOfFingers = `const chords = "<Cm7 Fm7 G7 F#7>";
piano().then(p=>stack( stack(
chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), 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(2).struct("x(4,8,-2)"),
chords.rootNotes(4) chords.rootNotes(4)
@ -555,22 +577,19 @@ piano().then(p=>stack(
.struct("x(3,8,-2)".fast(2)) .struct("x(3,8,-2)".fast(2))
.scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4")) .scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4"))
).slow(2) ).slow(2)
//.pianoroll()
.velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8)) .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 = "<Cm7 Fm7 G7 F#7 >"; export const festivalOfFingers2 = `const chords = "<Cm7 Fm7 G7 F#7 >";
const scales = slowcat('C minor','F dorian','G dorian','F# mixolydian') const scales = slowcat('C minor','F dorian','G dorian','F# mixolydian')
piano().then(p=>stack( stack(
chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), 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(2).struct("x(4,8)"),
chords.rootNotes(4) chords.rootNotes(4)
.scale(scales) .scale(scales)
.struct("x(3,8,-2)".fast(2)) .struct("x(3,8,-2)".fast(2))
.scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/3")) .scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/3"))
).slow(2).transpose(-1) ).slow(2).transpose(-1)
.legato(cosine.struct("x*8").add(4/5).mul(4/5).fast(8)) .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)) .velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8))
// .pianoroll() .tone((await piano()).chain(out())).fast(3/4)`;
.tone(p.chain(out())).fast(3/4)
)`;

View File

@ -37,6 +37,12 @@ export const backgroundImage = function (src, animateOptions = {}) {
frame((_, t) => frame((_, t) =>
funcOptions.forEach(([option, value]) => { funcOptions.forEach(([option, value]) => {
handleOption(option, value(t)); handleOption(option, value(t));
}) }),
); );
}; };
export const cleanup = () => {
const container = document.getElementById('code');
container.style = '';
container.className = 'grow relative'; // has to match App.tsx
};

View File

@ -267,13 +267,42 @@ class Pattern {
return this._withEvent(event => event.setContext({})) 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) => { return this._withContext((context) => {
const locations = (context.locations || []).concat([location]) const locations = (context.locations || []).concat([location])
return { ...context, locations } 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) { withValue(func) {
// Returns a new pattern, with the function applied to the value of // Returns a new pattern, with the function applied to the value of
// each event. It has the alias 'fmap'. // 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('hush', (pat) => pat.hush(), { patternified: false, composable: true });
Pattern.prototype.define('bypass', (pat) => pat.bypass(on), { patternified: true, 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, export {Fraction, TimeSpan, Hap, Pattern,
pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr, reify, silence, pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr, reify, silence,
fast, slow, early, late, rev, fast, slow, early, late, rev,
add, sub, mul, div, union, every, when, off, jux, append, superimpose, add, sub, mul, div, union, every, when, off, jux, append, superimpose,
struct, mask, invert, inv, struct, mask, invert, inv,
withLocationOffset
} }