From 2062e4233e842f765b1d4c005025f72f9329f3b8 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 12:41:20 +0100 Subject: [PATCH 01/21] add poc for krill parsing --- repl/krill-parser.js | 1916 ++++++++++++++++++++++++++++++++++++++++ repl/krill.pegjs | 207 +++++ repl/package-lock.json | 38 + repl/package.json | 4 +- repl/src/App.tsx | 12 +- repl/src/mini.ts | 34 + 6 files changed, 2202 insertions(+), 9 deletions(-) create mode 100644 repl/krill-parser.js create mode 100644 repl/krill.pegjs create mode 100644 repl/src/mini.ts diff --git a/repl/krill-parser.js b/repl/krill-parser.js new file mode 100644 index 00000000..db17b13f --- /dev/null +++ b/repl/krill-parser.js @@ -0,0 +1,1916 @@ +// Generated by Peggy 1.2.0. +// +// https://peggyjs.org/ + +function peg$subclass(child, parent) { + function C() { this.constructor = child; } + C.prototype = parent.prototype; + child.prototype = new C(); +} + +function peg$SyntaxError(message, expected, found, location) { + var self = Error.call(this, message); + if (Object.setPrototypeOf) { + Object.setPrototypeOf(self, peg$SyntaxError.prototype); + } + self.expected = expected; + self.found = found; + self.location = location; + self.name = "SyntaxError"; + return self; +} + +peg$subclass(peg$SyntaxError, Error); + +function peg$padEnd(str, targetLength, padString) { + padString = padString || " "; + if (str.length > targetLength) { return str; } + targetLength -= str.length; + padString += padString.repeat(targetLength); + return str + padString.slice(0, targetLength); +} + +peg$SyntaxError.prototype.format = function(sources) { + var str = "Error: " + this.message; + if (this.location) { + var src = null; + var k; + for (k = 0; k < sources.length; k++) { + if (sources[k].source === this.location.source) { + src = sources[k].text.split(/\r\n|\n|\r/g); + break; + } + } + var s = this.location.start; + var loc = this.location.source + ":" + s.line + ":" + s.column; + if (src) { + var e = this.location.end; + var filler = peg$padEnd("", s.line.toString().length); + var line = src[s.line - 1]; + var last = s.line === e.line ? e.column : line.length + 1; + str += "\n --> " + loc + "\n" + + filler + " |\n" + + s.line + " | " + line + "\n" + + filler + " | " + peg$padEnd("", s.column - 1) + + peg$padEnd("", last - s.column, "^"); + } else { + str += "\n at " + loc; + } + } + return str; +}; + +peg$SyntaxError.buildMessage = function(expected, found) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + class: function(expectation) { + var escapedParts = expectation.parts.map(function(part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + }, + + any: function() { + return "any character"; + }, + + end: function() { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = expected.map(describeExpectation); + var i, j; + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; +}; + +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + + var peg$FAILED = {}; + var peg$source = options.grammarSource; + + var peg$startRuleFunctions = { start: peg$parsestart }; + var peg$startRuleFunction = peg$parsestart; + + var peg$c0 = "."; + var peg$c1 = "-"; + var peg$c2 = "+"; + var peg$c3 = "0"; + var peg$c4 = ","; + var peg$c5 = "\""; + var peg$c6 = "'"; + var peg$c7 = "#"; + var peg$c8 = "["; + var peg$c9 = "]"; + var peg$c10 = "<"; + var peg$c11 = ">"; + var peg$c12 = "@"; + var peg$c13 = "("; + var peg$c14 = ")"; + var peg$c15 = "/"; + var peg$c16 = "*"; + var peg$c17 = "%"; + var peg$c18 = "struct"; + var peg$c19 = "target"; + var peg$c20 = "euclid"; + var peg$c21 = "slow"; + var peg$c22 = "rotL"; + var peg$c23 = "rotR"; + var peg$c24 = "fast"; + var peg$c25 = "scale"; + var peg$c26 = "//"; + var peg$c27 = "cat"; + var peg$c28 = "$"; + var peg$c29 = "setcps"; + var peg$c30 = "setbpm"; + var peg$c31 = "hush"; + + var peg$r0 = /^[1-9]/; + var peg$r1 = /^[eE]/; + var peg$r2 = /^[0-9]/; + var peg$r3 = /^[ \n\r\t]/; + var peg$r4 = /^[0-9a-zA-Z~]/; + var peg$r5 = /^[^\n]/; + + var peg$e0 = peg$otherExpectation("number"); + var peg$e1 = peg$literalExpectation(".", false); + var peg$e2 = peg$classExpectation([["1", "9"]], false, false); + var peg$e3 = peg$classExpectation(["e", "E"], false, false); + var peg$e4 = peg$literalExpectation("-", false); + var peg$e5 = peg$literalExpectation("+", false); + var peg$e6 = peg$literalExpectation("0", false); + var peg$e7 = peg$classExpectation([["0", "9"]], false, false); + var peg$e8 = peg$otherExpectation("whitespace"); + var peg$e9 = peg$classExpectation([" ", "\n", "\r", "\t"], false, false); + var peg$e10 = peg$literalExpectation(",", false); + var peg$e11 = peg$literalExpectation("\"", false); + var peg$e12 = peg$literalExpectation("'", false); + var peg$e13 = peg$classExpectation([["0", "9"], ["a", "z"], ["A", "Z"], "~"], false, false); + var peg$e14 = peg$literalExpectation("#", false); + var peg$e15 = peg$literalExpectation("[", false); + var peg$e16 = peg$literalExpectation("]", false); + var peg$e17 = peg$literalExpectation("<", false); + var peg$e18 = peg$literalExpectation(">", false); + var peg$e19 = peg$literalExpectation("@", false); + var peg$e20 = peg$literalExpectation("(", false); + var peg$e21 = peg$literalExpectation(")", false); + var peg$e22 = peg$literalExpectation("/", false); + var peg$e23 = peg$literalExpectation("*", false); + var peg$e24 = peg$literalExpectation("%", false); + var peg$e25 = peg$literalExpectation("struct", false); + var peg$e26 = peg$literalExpectation("target", false); + var peg$e27 = peg$literalExpectation("euclid", false); + var peg$e28 = peg$literalExpectation("slow", false); + var peg$e29 = peg$literalExpectation("rotL", false); + var peg$e30 = peg$literalExpectation("rotR", false); + var peg$e31 = peg$literalExpectation("fast", false); + var peg$e32 = peg$literalExpectation("scale", false); + var peg$e33 = peg$literalExpectation("//", false); + var peg$e34 = peg$classExpectation(["\n"], true, false); + var peg$e35 = peg$literalExpectation("cat", false); + var peg$e36 = peg$literalExpectation("$", false); + var peg$e37 = peg$literalExpectation("setcps", false); + var peg$e38 = peg$literalExpectation("setbpm", false); + var peg$e39 = peg$literalExpectation("hush", false); + + var peg$f0 = function() { return parseFloat(text()); }; + var peg$f1 = function(chars) { return chars.join("") }; + var peg$f2 = function(s) { return s}; + var peg$f3 = function(sc) { sc.arguments_.alignment = "t"; return sc;}; + var peg$f4 = function(a) { return { weight: a} }; + var peg$f5 = function(p, s) { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s } } } }; + var peg$f6 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a } } } }; + var peg$f7 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+a } } } }; + var peg$f8 = function(a) { return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } }; + var peg$f9 = function(s, o) { return new ElementStub(s, o);}; + var peg$f10 = function(s) { return new PatternStub(s,"h"); }; + var peg$f11 = function(c, v) { return v}; + var peg$f12 = function(c, cs) { if (cs.length == 0 && c instanceof Object) { return c;} else { cs.unshift(c); return new PatternStub(cs,"v");} }; + var peg$f13 = function(s) { return s; }; + var peg$f14 = function(s) { return { name: "struct", args: { sequence:s }}}; + var peg$f15 = function(s) { return { name: "target", args : { name:s}}}; + var peg$f16 = function(p, s) { return { name: "bjorklund", args :{ pulse: parseInt(p), step:parseInt(s) }}}; + var peg$f17 = function(a) { return { name: "stretch", args :{ amount: a}}}; + var peg$f18 = function(a) { return { name: "shift", args :{ amount: "-"+a}}}; + var peg$f19 = function(a) { return { name: "shift", args :{ amount: a}}}; + var peg$f20 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}}; + var peg$f21 = function(s) { return { name: "scale", args :{ scale: s.join("")}}}; + var peg$f22 = function(s, v) { return v}; + var peg$f23 = function(s, ss) { ss.unshift(s); return new PatternStub(ss,"t"); }; + var peg$f24 = function(sg) {return sg}; + var peg$f25 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)}; + var peg$f26 = function(sc) { return sc }; + var peg$f27 = function(c) { return c }; + var peg$f28 = function(v) { return new CommandStub("setcps", { value: v})}; + var peg$f29 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})}; + var peg$f30 = function() { return new CommandStub("hush")}; + + var peg$currPos = 0; + var peg$savedPos = 0; + var peg$posDetailsCache = [{ line: 1, column: 1 }]; + var peg$maxFailPos = 0; + var peg$maxFailExpected = []; + var peg$silentFails = 0; + + var peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function offset() { + return peg$savedPos; + } + + function range() { + return { + source: peg$source, + start: peg$savedPos, + end: peg$currPos + }; + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos]; + var p; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + + return details; + } + } + + function peg$computeLocation(startPos, endPos) { + var startPosDetails = peg$computePosDetails(startPos); + var endPosDetails = peg$computePosDetails(endPos); + + return { + source: peg$source, + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parsestart() { + var s0; + + s0 = peg$parsestatement(); + + return s0; + } + + function peg$parsenumber() { + var s0, s1, s2, s3, s4; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parseminus(); + if (s1 === peg$FAILED) { + s1 = null; + } + s2 = peg$parseint(); + if (s2 !== peg$FAILED) { + s3 = peg$parsefrac(); + if (s3 === peg$FAILED) { + s3 = null; + } + s4 = peg$parseexp(); + if (s4 === peg$FAILED) { + s4 = null; + } + peg$savedPos = s0; + s0 = peg$f0(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + + return s0; + } + + function peg$parsedecimal_point() { + var s0; + + if (input.charCodeAt(peg$currPos) === 46) { + s0 = peg$c0; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + + return s0; + } + + function peg$parsedigit1_9() { + var s0; + + if (peg$r0.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + + return s0; + } + + function peg$parsee() { + var s0; + + if (peg$r1.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e3); } + } + + return s0; + } + + function peg$parseexp() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parsee(); + if (s1 !== peg$FAILED) { + s2 = peg$parseminus(); + if (s2 === peg$FAILED) { + s2 = peg$parseplus(); + } + if (s2 === peg$FAILED) { + s2 = null; + } + s3 = []; + s4 = peg$parseDIGIT(); + if (s4 !== peg$FAILED) { + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseDIGIT(); + } + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefrac() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsedecimal_point(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseDIGIT(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseDIGIT(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseint() { + var s0, s1, s2, s3; + + s0 = peg$parsezero(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsedigit1_9(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseDIGIT(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseDIGIT(); + } + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseminus() { + var s0; + + if (input.charCodeAt(peg$currPos) === 45) { + s0 = peg$c1; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + + return s0; + } + + function peg$parseplus() { + var s0; + + if (input.charCodeAt(peg$currPos) === 43) { + s0 = peg$c2; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + + return s0; + } + + function peg$parsezero() { + var s0; + + if (input.charCodeAt(peg$currPos) === 48) { + s0 = peg$c3; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + + return s0; + } + + function peg$parseDIGIT() { + var s0; + + if (peg$r2.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e7); } + } + + return s0; + } + + function peg$parsews() { + var s0, s1; + + peg$silentFails++; + s0 = []; + if (peg$r3.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + while (s1 !== peg$FAILED) { + s0.push(s1); + if (peg$r3.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + } + peg$silentFails--; + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e8); } + + return s0; + } + + function peg$parsecomma() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 44) { + s2 = peg$c4; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsequote() { + var s0; + + if (input.charCodeAt(peg$currPos) === 34) { + s0 = peg$c5; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e11); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 39) { + s0 = peg$c6; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e12); } + } + } + + return s0; + } + + function peg$parsestep_char() { + var s0; + + if (peg$r4.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e13); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s0 = peg$c1; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 35) { + s0 = peg$c7; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 46) { + s0 = peg$c0; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + } + } + } + + return s0; + } + + function peg$parsestep() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsews(); + s2 = []; + s3 = peg$parsestep_char(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsestep_char(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + peg$savedPos = s0; + s0 = peg$f1(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesub_cycle() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 91) { + s2 = peg$c8; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + s4 = peg$parsestack(); + if (s4 !== peg$FAILED) { + s5 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 93) { + s6 = peg$c9; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + if (s6 !== peg$FAILED) { + s7 = peg$parsews(); + peg$savedPos = s0; + s0 = peg$f2(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsetimeline() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 60) { + s2 = peg$c10; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + s4 = peg$parsesingle_cycle(); + if (s4 !== peg$FAILED) { + s5 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 62) { + s6 = peg$c11; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e18); } + } + if (s6 !== peg$FAILED) { + s7 = peg$parsews(); + peg$savedPos = s0; + s0 = peg$f3(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice() { + var s0; + + s0 = peg$parsestep(); + if (s0 === peg$FAILED) { + s0 = peg$parsesub_cycle(); + if (s0 === peg$FAILED) { + s0 = peg$parsetimeline(); + } + } + + return s0; + } + + function peg$parseslice_modifier() { + var s0; + + s0 = peg$parseslice_weight(); + if (s0 === peg$FAILED) { + s0 = peg$parseslice_bjorklund(); + if (s0 === peg$FAILED) { + s0 = peg$parseslice_slow(); + if (s0 === peg$FAILED) { + s0 = peg$parseslice_fast(); + if (s0 === peg$FAILED) { + s0 = peg$parseslice_fixed_step(); + } + } + } + } + + return s0; + } + + function peg$parseslice_weight() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 64) { + s1 = peg$c12; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e19); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsenumber(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f4(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_bjorklund() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 40) { + s1 = peg$c13; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e20); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + s4 = peg$parsews(); + s5 = peg$parsecomma(); + if (s5 !== peg$FAILED) { + s6 = peg$parsews(); + s7 = peg$parsenumber(); + if (s7 !== peg$FAILED) { + s8 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 41) { + s9 = peg$c14; + peg$currPos++; + } else { + s9 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e21); } + } + if (s9 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f5(s3, s7); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_slow() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 47) { + s1 = peg$c15; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e22); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsenumber(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f6(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_fast() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 42) { + s1 = peg$c16; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e23); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsenumber(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f7(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_fixed_step() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 37) { + s1 = peg$c17; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e24); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsenumber(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f8(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_with_modifier() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = peg$parseslice(); + if (s1 !== peg$FAILED) { + s2 = peg$parseslice_modifier(); + if (s2 === peg$FAILED) { + s2 = null; + } + peg$savedPos = s0; + s0 = peg$f9(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesingle_cycle() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseslice_with_modifier(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseslice_with_modifier(); + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f10(s1); + } + s0 = s1; + + return s0; + } + + function peg$parsestack() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parsesingle_cycle(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parsecomma(); + if (s4 !== peg$FAILED) { + s5 = peg$parsesingle_cycle(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f11(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parsecomma(); + if (s4 !== peg$FAILED) { + s5 = peg$parsesingle_cycle(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f11(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + peg$savedPos = s0; + s0 = peg$f12(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesequence() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parsews(); + s2 = peg$parsequote(); + if (s2 !== peg$FAILED) { + s3 = peg$parsestack(); + if (s3 !== peg$FAILED) { + s4 = peg$parsequote(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f13(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseoperator() { + var s0; + + s0 = peg$parsescale(); + if (s0 === peg$FAILED) { + s0 = peg$parseslow(); + if (s0 === peg$FAILED) { + s0 = peg$parsefast(); + if (s0 === peg$FAILED) { + s0 = peg$parsetarget(); + if (s0 === peg$FAILED) { + s0 = peg$parsebjorklund(); + if (s0 === peg$FAILED) { + s0 = peg$parsestruct(); + if (s0 === peg$FAILED) { + s0 = peg$parserotR(); + if (s0 === peg$FAILED) { + s0 = peg$parserotL(); + } + } + } + } + } + } + } + + return s0; + } + + function peg$parsestruct() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c18) { + s1 = peg$c18; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e25); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsesequence_or_operator(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f14(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsetarget() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c19) { + s1 = peg$c19; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsequote(); + if (s3 !== peg$FAILED) { + s4 = peg$parsestep(); + if (s4 !== peg$FAILED) { + s5 = peg$parsequote(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f15(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsebjorklund() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c20) { + s1 = peg$c20; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e27); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parseint(); + if (s3 !== peg$FAILED) { + s4 = peg$parsews(); + s5 = peg$parseint(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f16(s3, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslow() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c21) { + s1 = peg$c21; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e28); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f17(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parserotL() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c22) { + s1 = peg$c22; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e29); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f18(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parserotR() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c23) { + s1 = peg$c23; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e30); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f19(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefast() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c24) { + s1 = peg$c24; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e31); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f20(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsescale() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 5) === peg$c25) { + s1 = peg$c25; + peg$currPos += 5; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e32); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsequote(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parsestep_char(); + if (s5 !== peg$FAILED) { + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parsestep_char(); + } + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s5 = peg$parsequote(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f21(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsecomment() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c26) { + s1 = peg$c26; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e33); } + } + if (s1 !== peg$FAILED) { + s2 = []; + if (peg$r5.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e34); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$r5.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e34); } + } + } + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsecat() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 3) === peg$c27) { + s1 = peg$c27; + peg$currPos += 3; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e35); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 91) { + s3 = peg$c8; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parsews(); + s5 = peg$parsesequence_or_operator(); + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$currPos; + s8 = peg$parsecomma(); + if (s8 !== peg$FAILED) { + s9 = peg$parsesequence_or_operator(); + if (s9 !== peg$FAILED) { + peg$savedPos = s7; + s7 = peg$f22(s5, s9); + } else { + peg$currPos = s7; + s7 = peg$FAILED; + } + } else { + peg$currPos = s7; + s7 = peg$FAILED; + } + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$currPos; + s8 = peg$parsecomma(); + if (s8 !== peg$FAILED) { + s9 = peg$parsesequence_or_operator(); + if (s9 !== peg$FAILED) { + peg$savedPos = s7; + s7 = peg$f22(s5, s9); + } else { + peg$currPos = s7; + s7 = peg$FAILED; + } + } else { + peg$currPos = s7; + s7 = peg$FAILED; + } + } + s7 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 93) { + s8 = peg$c9; + peg$currPos++; + } else { + s8 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + if (s8 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f23(s5, s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesequence_or_group() { + var s0; + + s0 = peg$parsecat(); + if (s0 === peg$FAILED) { + s0 = peg$parsesequence(); + } + + return s0; + } + + function peg$parsesequence_or_operator() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parsesequence_or_group(); + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = []; + s4 = peg$parsecomment(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parsecomment(); + } + peg$savedPos = s0; + s0 = peg$f24(s1); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseoperator(); + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 36) { + s3 = peg$c28; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e36); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parsews(); + s5 = peg$parsesequence_or_operator(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f25(s1, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parsesequ_or_operator_or_comment() { + var s0, s1; + + s0 = peg$currPos; + s1 = peg$parsesequence_or_operator(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f26(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$parsecomment(); + } + + return s0; + } + + function peg$parsesequence_definition() { + var s0; + + s0 = peg$parsesequ_or_operator_or_comment(); + + return s0; + } + + function peg$parsecommand() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsews(); + s2 = peg$parsesetcps(); + if (s2 === peg$FAILED) { + s2 = peg$parsesetbpm(); + if (s2 === peg$FAILED) { + s2 = peg$parsehush(); + } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + peg$savedPos = s0; + s0 = peg$f27(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesetcps() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c29) { + s1 = peg$c29; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e37); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f28(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesetbpm() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c30) { + s1 = peg$c30; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e38); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f29(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsehush() { + var s0, s1; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c31) { + s1 = peg$c31; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e39); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f30(); + } + s0 = s1; + + return s0; + } + + function peg$parsestatement() { + var s0; + + s0 = peg$parsesequence_definition(); + if (s0 === peg$FAILED) { + s0 = peg$parsecommand(); + } + + return s0; + } + + + var PatternStub = function(source, alignment) + { + this.type_ = "pattern"; + this.arguments_ = { alignment : alignment}; + this.source_ = source; + } + + var OperatorStub = function(name, args, source) + { + this.type_ = name; + this.arguments_ = args; + this.source_ = source; + } + + var ElementStub = function(source, options) + { + this.type_ = "element"; + this.source_ = source; + this.options_ = options; + } + + var CommandStub = function(name, options) + { + this.type_ = "command"; + this.name_ = name; + this.options_ = options; + } + + + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } +} + +export { + peg$SyntaxError as SyntaxError, + peg$parse as parse +}; diff --git a/repl/krill.pegjs b/repl/krill.pegjs new file mode 100644 index 00000000..e32a9763 --- /dev/null +++ b/repl/krill.pegjs @@ -0,0 +1,207 @@ +// Some terminology: +// a sequence = a serie of elements placed between quotes +// a stack = a serie of vertically aligned slices sharing the same overall length +// a slice = a serie of horizontally aligned elements + + +{ + var PatternStub = function(source, alignment) + { + this.type_ = "pattern"; + this.arguments_ = { alignment : alignment}; + this.source_ = source; + } + + var OperatorStub = function(name, args, source) + { + this.type_ = name; + this.arguments_ = args; + this.source_ = source; + } + + var ElementStub = function(source, options) + { + this.type_ = "element"; + this.source_ = source; + this.options_ = options; + } + + var CommandStub = function(name, options) + { + this.type_ = "command"; + this.name_ = name; + this.options_ = options; + } + +} + +start = statement + +// ----- Numbers ----- + +number "number" + = minus? int frac? exp? { return parseFloat(text()); } + +decimal_point + = "." + +digit1_9 + = [1-9] + +e + = [eE] + +exp + = e (minus / plus)? DIGIT+ + +frac + = decimal_point DIGIT+ + +int + = zero / (digit1_9 DIGIT*) + +minus + = "-" + +plus + = "+" + +zero + = "0" + +DIGIT = [0-9] + +// ------------------ delimiters --------------------------- + +ws "whitespace" = [ \n\r\t]* +comma = ws "," ws; +quote = '"' / "'" + +// ------------------ steps and cycles --------------------------- + +// single step definition (e.g bd) +step_char = [0-9a-zA-Z~] / "-" / "#" / "." +step = ws chars:step_char+ ws { return chars.join("") } + +// define a sub cycle e.g. [1 2, 3 [4]] +sub_cycle = ws "[" ws s:stack ws "]" ws { return s} + +// define a timeline e.g <1 3 [3 5]>. We simply defer to a stack and change the alignement +timeline = ws "<" ws sc:single_cycle ws ">" ws + { sc.arguments_.alignment = "t"; return sc;} + +// a slice is either a single step or a sub cycle +slice = step / sub_cycle / timeline + +// slice modifier affects the timing/size of a slice (e.g. [a b c]@3) +// at this point, we assume we can represent them as regular sequence operators +slice_modifier = slice_weight / slice_bjorklund / slice_slow / slice_fast / slice_fixed_step + +slice_weight = "@" a:number + { return { weight: a} } + +slice_bjorklund = "(" ws p:number ws comma ws s:number ws")" + { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s } } } } + +slice_slow = "/"a:number + { return { operator : { type_: "stretch", arguments_ :{ amount:a } } } } + +slice_fast = "*"a:number + { return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+a } } } } + +slice_fixed_step = "%"a:number + { return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } } + +// a slice with an modifier applied i.e [bd@4 sd@3]@2 hh] +slice_with_modifier = s:slice o:slice_modifier? + { return new ElementStub(s, o);} + +// a single cycle is a combination of one or more successive slices (as an array). If we +// have only one element, we skip the array and return the element itself +single_cycle = s:(slice_with_modifier)+ + { return new PatternStub(s,"h"); } + +// a stack is a serie of vertically aligned single cycles, separated by a comma +// if the stack contains only one element, we don't create a stack but return the +// underlying element +stack = c:single_cycle cs:(comma v:single_cycle { return v})* + { if (cs.length == 0 && c instanceof Object) { return c;} else { cs.unshift(c); return new PatternStub(cs,"v");} } + +// a sequence is a quoted stack +sequence = ws quote s:stack quote + { return s; } + +// ------------------ operators --------------------------- + +operator = scale / slow / fast / target / bjorklund / struct / rotR / rotL + +struct = "struct" ws s:sequence_or_operator + { return { name: "struct", args: { sequence:s }}} + +target = "target" ws quote s:step quote + { return { name: "target", args : { name:s}}} + +bjorklund = "euclid" ws p:int ws s:int + { return { name: "bjorklund", args :{ pulse: parseInt(p), step:parseInt(s) }}} + +slow = "slow" ws a:number + { return { name: "stretch", args :{ amount: a}}} + +rotL = "rotL" ws a:number + { return { name: "shift", args :{ amount: "-"+a}}} + +rotR = "rotR" ws a:number + { return { name: "shift", args :{ amount: a}}} + +fast = "fast" ws a:number + { return { name: "stretch", args :{ amount: "1/"+a}}} + +scale = "scale" ws quote s:(step_char)+ quote +{ return { name: "scale", args :{ scale: s.join("")}}} + +comment = '//' p:([^\n]*) + +// ---------------- grouping -------------------------------- + +group_operator = cat + +// cat is another form of timeline +cat = "cat" ws "[" ws s:sequence_or_operator ss:(comma v:sequence_or_operator { return v})* ws "]" + { ss.unshift(s); return new PatternStub(ss,"t"); } + +// ------------------ high level sequence --------------------------- + +sequence_or_group = + group_operator / + sequence + +sequence_or_operator = + sg:sequence_or_group ws (comment)* + {return sg} + / o:operator ws "$" ws soc:sequence_or_operator + { return new OperatorStub(o.name,o.args,soc)} + +sequ_or_operator_or_comment = + sc: sequence_or_operator + { return sc } + / comment + +sequence_definition = s:sequ_or_operator_or_comment + +// ---------------------- statements ---------------------------- + +command = ws c:(setcps / setbpm / hush) ws + { return c } + +setcps = "setcps" ws v:number + { return new CommandStub("setcps", { value: v})} + +setbpm = "setbpm" ws v:number + { return new CommandStub("setcps", { value: (v/120/2)})} + +hush = "hush" + { return new CommandStub("hush")} + +// ---------------------- statements ---------------------------- + +statement = sequence_definition / command diff --git a/repl/package-lock.json b/repl/package-lock.json index 6ffdd90a..a08c11c7 100644 --- a/repl/package-lock.json +++ b/repl/package-lock.json @@ -25,6 +25,8 @@ "@web/test-runner": "^0.13.3", "autoprefixer": "^10.4.2", "chai": "^4.3.4", + "peggy": "^1.2.0", + "pegjs": "^0.10.0", "postcss": "^8.4.6", "prettier": "^2.2.1", "snowpack": "^3.3.7", @@ -6067,6 +6069,30 @@ "node": "*" } }, + "node_modules/peggy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/peggy/-/peggy-1.2.0.tgz", + "integrity": "sha512-PQ+NKpAobImfMprYQtc4Egmyi29bidRGEX0kKjCU5uuW09s0Cthwqhfy7mLkwcB4VcgacE5L/ZjruD/kOPCUUw==", + "dev": true, + "bin": { + "peggy": "bin/peggy" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pegjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", + "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", + "dev": true, + "bin": { + "pegjs": "bin/pegjs" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -13113,6 +13139,18 @@ "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true }, + "peggy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/peggy/-/peggy-1.2.0.tgz", + "integrity": "sha512-PQ+NKpAobImfMprYQtc4Egmyi29bidRGEX0kKjCU5uuW09s0Cthwqhfy7mLkwcB4VcgacE5L/ZjruD/kOPCUUw==", + "dev": true + }, + "pegjs": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", + "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", + "dev": true + }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", diff --git a/repl/package.json b/repl/package.json index eabaec9d..e6c8dc39 100644 --- a/repl/package.json +++ b/repl/package.json @@ -4,7 +4,8 @@ "build": "snowpack build && cp ./public/.nojekyll ../docs", "test": "web-test-runner \"src/**/*.test.tsx\"", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"", - "lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"" + "lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"", + "peggy": "peggy -o krill-parser.js --format es ./krill.pegjs" }, "dependencies": { "react": "^17.0.2", @@ -27,6 +28,7 @@ "@web/test-runner": "^0.13.3", "autoprefixer": "^10.4.2", "chai": "^4.3.4", + "peggy": "^1.2.0", "postcss": "^8.4.6", "prettier": "^2.2.1", "snowpack": "^3.3.7", diff --git a/repl/src/App.tsx b/repl/src/App.tsx index c2770173..18d9e797 100644 --- a/repl/src/App.tsx +++ b/repl/src/App.tsx @@ -6,15 +6,11 @@ import * as Tone from 'tone'; import useCycle from './useCycle'; import type { Hap, Pattern } from './types'; import { tetris } from './tunes'; +import _mini from './mini'; -const { Fraction, TimeSpan } = strudel; - -const fr = (v: number) => new Fraction(v); -const ts = (start: number, end: number) => new TimeSpan(fr(start), fr(end)); -const parse = (code: string): Pattern => { - const { sequence, pure, reify, slowcat, fastcat, cat, stack, silence } = strudel; // make available to eval - return eval(code); -}; +const { sequence, pure, reify, slowcat, fastcat, cat, stack, silence } = strudel; // make available to eval +const mini = _mini; // for eval (direct import wont work somehow) +const parse = (code: string): Pattern => eval(code); const synth = new Tone.PolySynth().toDestination(); synth.set({ diff --git a/repl/src/mini.ts b/repl/src/mini.ts new file mode 100644 index 00000000..b56baf8c --- /dev/null +++ b/repl/src/mini.ts @@ -0,0 +1,34 @@ +import * as krill from '../krill-parser'; +import * as strudel from '../../strudel.mjs'; + +export function patternifyAST(ast: any): any { + switch (ast.type_) { + case 'pattern': + return strudel.sequence(...ast.source_.map(patternifyAST)); + case 'element': + if (typeof ast.source_ !== 'object') { + return ast.source_; + } + return patternifyAST(ast.source_); + } +} +export default (str: string) => patternifyAST(krill.parse(`"${str}"`)); + +/* +TODO: +export interface Arguments { + alignment: string; +} + +export interface ElementStub { + type_: string; + source_: string; + options_?: any; +} + +export interface PatternStub { + type_: string; // pattern + arguments_: Arguments; + source_: ElementStub[]; +} + */ From 06ebcfff89210e0ed987ba3bda9bb1822e3c4b10 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 13:01:18 +0100 Subject: [PATCH 02/21] tetris in mini notation --- repl/src/mini.ts | 13 +++++++++++-- repl/src/tunes.ts | 29 ++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/repl/src/mini.ts b/repl/src/mini.ts index b56baf8c..1dbeacd6 100644 --- a/repl/src/mini.ts +++ b/repl/src/mini.ts @@ -1,18 +1,27 @@ import * as krill from '../krill-parser'; import * as strudel from '../../strudel.mjs'; +const { sequence, silence } = strudel; + export function patternifyAST(ast: any): any { switch (ast.type_) { case 'pattern': - return strudel.sequence(...ast.source_.map(patternifyAST)); + return sequence(...ast.source_.map(patternifyAST)); case 'element': + if (ast.source_ === '~') { + return silence; + } if (typeof ast.source_ !== 'object') { return ast.source_; } return patternifyAST(ast.source_); } } -export default (str: string) => patternifyAST(krill.parse(`"${str}"`)); +/* export default (str: string) => patternifyAST(krill.parse(`"${str}"`)); */ + +export default (...strings: string[]) => { + return sequence(...strings.map((str) => patternifyAST(krill.parse(`"${str}"`)))); +}; /* TODO: diff --git a/repl/src/tunes.ts b/repl/src/tunes.ts index bda5f2d4..f2b75380 100644 --- a/repl/src/tunes.ts +++ b/repl/src/tunes.ts @@ -1,4 +1,4 @@ -export const tetris = `stack(sequence( +export const tetrisWithFunctions = `stack(sequence( 'e5', sequence('b4', 'c5'), 'd5', sequence('c5', 'b4'), 'a4', sequence('a4', 'c5'), 'e5', sequence('d5', 'c5'), 'b4', sequence(silence, 'c5'), 'd5', 'e5', @@ -19,6 +19,33 @@ export const tetris = `stack(sequence( ) )._slow(16)`; +export const tetris = `stack( + sequence( + mini( + 'e5 [b4 c5] d5 [c5 b4]', + 'a4 [a4 c5] e5 [d5 c5]', + 'b4 [~ c5] d5 e5', + 'c5 a4 a4 ~', + '[~ d5] [~ f5] a5 [g5 f5]', + 'e5 [~ c5] e5 [d5 c5]', + 'b4 [b4 c5] d5 e5', + 'c5 a4 a4 ~' + ) + ), + sequence( + mini( + 'e2 e3 e2 e3 e2 e3 e2 e3', + 'a2 a3 a2 a3 a2 a3 a2 a3', + 'g#2 g#3 g#2 g#3 e2 e3 e2 e3', + 'a2 a3 a2 a3 a2 a3 b1 c2', + 'd2 d3 d2 d3 d2 d3 d2 d3', + 'c2 c3 c2 c3 c2 c3 c2 c3', + 'b1 b2 b1 b2 e2 e3 e2 e3', + 'a1 a2 a1 a2 a1 a2 a1 a2' + ) + ) +)._slow(16);`; + // "sequence('c3', 'eb3', sequence('g3', 'f3'))" // /* `sequence( stack('c4','eb4','g4'), From 174c0135ac1e7ab8187be31178bd951e17b03d2f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 13:01:43 +0100 Subject: [PATCH 03/21] build --- docs/_snowpack/link/repl/krill-parser.js | 1916 ++++++++++++++++++++++ docs/_snowpack/link/strudel.js | 20 + docs/dist/App.js | 11 +- docs/dist/mini.js | 20 + docs/dist/tunes.js | 28 +- 5 files changed, 1987 insertions(+), 8 deletions(-) create mode 100644 docs/_snowpack/link/repl/krill-parser.js create mode 100644 docs/dist/mini.js diff --git a/docs/_snowpack/link/repl/krill-parser.js b/docs/_snowpack/link/repl/krill-parser.js new file mode 100644 index 00000000..db17b13f --- /dev/null +++ b/docs/_snowpack/link/repl/krill-parser.js @@ -0,0 +1,1916 @@ +// Generated by Peggy 1.2.0. +// +// https://peggyjs.org/ + +function peg$subclass(child, parent) { + function C() { this.constructor = child; } + C.prototype = parent.prototype; + child.prototype = new C(); +} + +function peg$SyntaxError(message, expected, found, location) { + var self = Error.call(this, message); + if (Object.setPrototypeOf) { + Object.setPrototypeOf(self, peg$SyntaxError.prototype); + } + self.expected = expected; + self.found = found; + self.location = location; + self.name = "SyntaxError"; + return self; +} + +peg$subclass(peg$SyntaxError, Error); + +function peg$padEnd(str, targetLength, padString) { + padString = padString || " "; + if (str.length > targetLength) { return str; } + targetLength -= str.length; + padString += padString.repeat(targetLength); + return str + padString.slice(0, targetLength); +} + +peg$SyntaxError.prototype.format = function(sources) { + var str = "Error: " + this.message; + if (this.location) { + var src = null; + var k; + for (k = 0; k < sources.length; k++) { + if (sources[k].source === this.location.source) { + src = sources[k].text.split(/\r\n|\n|\r/g); + break; + } + } + var s = this.location.start; + var loc = this.location.source + ":" + s.line + ":" + s.column; + if (src) { + var e = this.location.end; + var filler = peg$padEnd("", s.line.toString().length); + var line = src[s.line - 1]; + var last = s.line === e.line ? e.column : line.length + 1; + str += "\n --> " + loc + "\n" + + filler + " |\n" + + s.line + " | " + line + "\n" + + filler + " | " + peg$padEnd("", s.column - 1) + + peg$padEnd("", last - s.column, "^"); + } else { + str += "\n at " + loc; + } + } + return str; +}; + +peg$SyntaxError.buildMessage = function(expected, found) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + class: function(expectation) { + var escapedParts = expectation.parts.map(function(part) { + return Array.isArray(part) + ? classEscape(part[0]) + "-" + classEscape(part[1]) + : classEscape(part); + }); + + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + }, + + any: function() { + return "any character"; + }, + + end: function() { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/"/g, "\\\"") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, "\\\\") + .replace(/\]/g, "\\]") + .replace(/\^/g, "\\^") + .replace(/-/g, "\\-") + .replace(/\0/g, "\\0") + .replace(/\t/g, "\\t") + .replace(/\n/g, "\\n") + .replace(/\r/g, "\\r") + .replace(/[\x00-\x0F]/g, function(ch) { return "\\x0" + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return "\\x" + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = expected.map(describeExpectation); + var i, j; + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; +}; + +function peg$parse(input, options) { + options = options !== undefined ? options : {}; + + var peg$FAILED = {}; + var peg$source = options.grammarSource; + + var peg$startRuleFunctions = { start: peg$parsestart }; + var peg$startRuleFunction = peg$parsestart; + + var peg$c0 = "."; + var peg$c1 = "-"; + var peg$c2 = "+"; + var peg$c3 = "0"; + var peg$c4 = ","; + var peg$c5 = "\""; + var peg$c6 = "'"; + var peg$c7 = "#"; + var peg$c8 = "["; + var peg$c9 = "]"; + var peg$c10 = "<"; + var peg$c11 = ">"; + var peg$c12 = "@"; + var peg$c13 = "("; + var peg$c14 = ")"; + var peg$c15 = "/"; + var peg$c16 = "*"; + var peg$c17 = "%"; + var peg$c18 = "struct"; + var peg$c19 = "target"; + var peg$c20 = "euclid"; + var peg$c21 = "slow"; + var peg$c22 = "rotL"; + var peg$c23 = "rotR"; + var peg$c24 = "fast"; + var peg$c25 = "scale"; + var peg$c26 = "//"; + var peg$c27 = "cat"; + var peg$c28 = "$"; + var peg$c29 = "setcps"; + var peg$c30 = "setbpm"; + var peg$c31 = "hush"; + + var peg$r0 = /^[1-9]/; + var peg$r1 = /^[eE]/; + var peg$r2 = /^[0-9]/; + var peg$r3 = /^[ \n\r\t]/; + var peg$r4 = /^[0-9a-zA-Z~]/; + var peg$r5 = /^[^\n]/; + + var peg$e0 = peg$otherExpectation("number"); + var peg$e1 = peg$literalExpectation(".", false); + var peg$e2 = peg$classExpectation([["1", "9"]], false, false); + var peg$e3 = peg$classExpectation(["e", "E"], false, false); + var peg$e4 = peg$literalExpectation("-", false); + var peg$e5 = peg$literalExpectation("+", false); + var peg$e6 = peg$literalExpectation("0", false); + var peg$e7 = peg$classExpectation([["0", "9"]], false, false); + var peg$e8 = peg$otherExpectation("whitespace"); + var peg$e9 = peg$classExpectation([" ", "\n", "\r", "\t"], false, false); + var peg$e10 = peg$literalExpectation(",", false); + var peg$e11 = peg$literalExpectation("\"", false); + var peg$e12 = peg$literalExpectation("'", false); + var peg$e13 = peg$classExpectation([["0", "9"], ["a", "z"], ["A", "Z"], "~"], false, false); + var peg$e14 = peg$literalExpectation("#", false); + var peg$e15 = peg$literalExpectation("[", false); + var peg$e16 = peg$literalExpectation("]", false); + var peg$e17 = peg$literalExpectation("<", false); + var peg$e18 = peg$literalExpectation(">", false); + var peg$e19 = peg$literalExpectation("@", false); + var peg$e20 = peg$literalExpectation("(", false); + var peg$e21 = peg$literalExpectation(")", false); + var peg$e22 = peg$literalExpectation("/", false); + var peg$e23 = peg$literalExpectation("*", false); + var peg$e24 = peg$literalExpectation("%", false); + var peg$e25 = peg$literalExpectation("struct", false); + var peg$e26 = peg$literalExpectation("target", false); + var peg$e27 = peg$literalExpectation("euclid", false); + var peg$e28 = peg$literalExpectation("slow", false); + var peg$e29 = peg$literalExpectation("rotL", false); + var peg$e30 = peg$literalExpectation("rotR", false); + var peg$e31 = peg$literalExpectation("fast", false); + var peg$e32 = peg$literalExpectation("scale", false); + var peg$e33 = peg$literalExpectation("//", false); + var peg$e34 = peg$classExpectation(["\n"], true, false); + var peg$e35 = peg$literalExpectation("cat", false); + var peg$e36 = peg$literalExpectation("$", false); + var peg$e37 = peg$literalExpectation("setcps", false); + var peg$e38 = peg$literalExpectation("setbpm", false); + var peg$e39 = peg$literalExpectation("hush", false); + + var peg$f0 = function() { return parseFloat(text()); }; + var peg$f1 = function(chars) { return chars.join("") }; + var peg$f2 = function(s) { return s}; + var peg$f3 = function(sc) { sc.arguments_.alignment = "t"; return sc;}; + var peg$f4 = function(a) { return { weight: a} }; + var peg$f5 = function(p, s) { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s } } } }; + var peg$f6 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a } } } }; + var peg$f7 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+a } } } }; + var peg$f8 = function(a) { return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } }; + var peg$f9 = function(s, o) { return new ElementStub(s, o);}; + var peg$f10 = function(s) { return new PatternStub(s,"h"); }; + var peg$f11 = function(c, v) { return v}; + var peg$f12 = function(c, cs) { if (cs.length == 0 && c instanceof Object) { return c;} else { cs.unshift(c); return new PatternStub(cs,"v");} }; + var peg$f13 = function(s) { return s; }; + var peg$f14 = function(s) { return { name: "struct", args: { sequence:s }}}; + var peg$f15 = function(s) { return { name: "target", args : { name:s}}}; + var peg$f16 = function(p, s) { return { name: "bjorklund", args :{ pulse: parseInt(p), step:parseInt(s) }}}; + var peg$f17 = function(a) { return { name: "stretch", args :{ amount: a}}}; + var peg$f18 = function(a) { return { name: "shift", args :{ amount: "-"+a}}}; + var peg$f19 = function(a) { return { name: "shift", args :{ amount: a}}}; + var peg$f20 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}}; + var peg$f21 = function(s) { return { name: "scale", args :{ scale: s.join("")}}}; + var peg$f22 = function(s, v) { return v}; + var peg$f23 = function(s, ss) { ss.unshift(s); return new PatternStub(ss,"t"); }; + var peg$f24 = function(sg) {return sg}; + var peg$f25 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)}; + var peg$f26 = function(sc) { return sc }; + var peg$f27 = function(c) { return c }; + var peg$f28 = function(v) { return new CommandStub("setcps", { value: v})}; + var peg$f29 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})}; + var peg$f30 = function() { return new CommandStub("hush")}; + + var peg$currPos = 0; + var peg$savedPos = 0; + var peg$posDetailsCache = [{ line: 1, column: 1 }]; + var peg$maxFailPos = 0; + var peg$maxFailExpected = []; + var peg$silentFails = 0; + + var peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function offset() { + return peg$savedPos; + } + + function range() { + return { + source: peg$source, + start: peg$savedPos, + end: peg$currPos + }; + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== undefined + ? location + : peg$computeLocation(peg$savedPos, peg$currPos); + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos]; + var p; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + + return details; + } + } + + function peg$computeLocation(startPos, endPos) { + var startPosDetails = peg$computePosDetails(startPos); + var endPosDetails = peg$computePosDetails(endPos); + + return { + source: peg$source, + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parsestart() { + var s0; + + s0 = peg$parsestatement(); + + return s0; + } + + function peg$parsenumber() { + var s0, s1, s2, s3, s4; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parseminus(); + if (s1 === peg$FAILED) { + s1 = null; + } + s2 = peg$parseint(); + if (s2 !== peg$FAILED) { + s3 = peg$parsefrac(); + if (s3 === peg$FAILED) { + s3 = null; + } + s4 = peg$parseexp(); + if (s4 === peg$FAILED) { + s4 = null; + } + peg$savedPos = s0; + s0 = peg$f0(); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e0); } + } + + return s0; + } + + function peg$parsedecimal_point() { + var s0; + + if (input.charCodeAt(peg$currPos) === 46) { + s0 = peg$c0; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + + return s0; + } + + function peg$parsedigit1_9() { + var s0; + + if (peg$r0.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e2); } + } + + return s0; + } + + function peg$parsee() { + var s0; + + if (peg$r1.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e3); } + } + + return s0; + } + + function peg$parseexp() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parsee(); + if (s1 !== peg$FAILED) { + s2 = peg$parseminus(); + if (s2 === peg$FAILED) { + s2 = peg$parseplus(); + } + if (s2 === peg$FAILED) { + s2 = null; + } + s3 = []; + s4 = peg$parseDIGIT(); + if (s4 !== peg$FAILED) { + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseDIGIT(); + } + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefrac() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsedecimal_point(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseDIGIT(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseDIGIT(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseint() { + var s0, s1, s2, s3; + + s0 = peg$parsezero(); + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parsedigit1_9(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseDIGIT(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseDIGIT(); + } + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseminus() { + var s0; + + if (input.charCodeAt(peg$currPos) === 45) { + s0 = peg$c1; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + + return s0; + } + + function peg$parseplus() { + var s0; + + if (input.charCodeAt(peg$currPos) === 43) { + s0 = peg$c2; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e5); } + } + + return s0; + } + + function peg$parsezero() { + var s0; + + if (input.charCodeAt(peg$currPos) === 48) { + s0 = peg$c3; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e6); } + } + + return s0; + } + + function peg$parseDIGIT() { + var s0; + + if (peg$r2.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e7); } + } + + return s0; + } + + function peg$parsews() { + var s0, s1; + + peg$silentFails++; + s0 = []; + if (peg$r3.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + while (s1 !== peg$FAILED) { + s0.push(s1); + if (peg$r3.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e9); } + } + } + peg$silentFails--; + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e8); } + + return s0; + } + + function peg$parsecomma() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 44) { + s2 = peg$c4; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e10); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsequote() { + var s0; + + if (input.charCodeAt(peg$currPos) === 34) { + s0 = peg$c5; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e11); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 39) { + s0 = peg$c6; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e12); } + } + } + + return s0; + } + + function peg$parsestep_char() { + var s0; + + if (peg$r4.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e13); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s0 = peg$c1; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e4); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 35) { + s0 = peg$c7; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e14); } + } + if (s0 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 46) { + s0 = peg$c0; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e1); } + } + } + } + } + + return s0; + } + + function peg$parsestep() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsews(); + s2 = []; + s3 = peg$parsestep_char(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parsestep_char(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + peg$savedPos = s0; + s0 = peg$f1(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesub_cycle() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 91) { + s2 = peg$c8; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + s4 = peg$parsestack(); + if (s4 !== peg$FAILED) { + s5 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 93) { + s6 = peg$c9; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + if (s6 !== peg$FAILED) { + s7 = peg$parsews(); + peg$savedPos = s0; + s0 = peg$f2(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsetimeline() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 60) { + s2 = peg$c10; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e17); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + s4 = peg$parsesingle_cycle(); + if (s4 !== peg$FAILED) { + s5 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 62) { + s6 = peg$c11; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e18); } + } + if (s6 !== peg$FAILED) { + s7 = peg$parsews(); + peg$savedPos = s0; + s0 = peg$f3(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice() { + var s0; + + s0 = peg$parsestep(); + if (s0 === peg$FAILED) { + s0 = peg$parsesub_cycle(); + if (s0 === peg$FAILED) { + s0 = peg$parsetimeline(); + } + } + + return s0; + } + + function peg$parseslice_modifier() { + var s0; + + s0 = peg$parseslice_weight(); + if (s0 === peg$FAILED) { + s0 = peg$parseslice_bjorklund(); + if (s0 === peg$FAILED) { + s0 = peg$parseslice_slow(); + if (s0 === peg$FAILED) { + s0 = peg$parseslice_fast(); + if (s0 === peg$FAILED) { + s0 = peg$parseslice_fixed_step(); + } + } + } + } + + return s0; + } + + function peg$parseslice_weight() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 64) { + s1 = peg$c12; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e19); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsenumber(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f4(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_bjorklund() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 40) { + s1 = peg$c13; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e20); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + s4 = peg$parsews(); + s5 = peg$parsecomma(); + if (s5 !== peg$FAILED) { + s6 = peg$parsews(); + s7 = peg$parsenumber(); + if (s7 !== peg$FAILED) { + s8 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 41) { + s9 = peg$c14; + peg$currPos++; + } else { + s9 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e21); } + } + if (s9 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f5(s3, s7); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_slow() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 47) { + s1 = peg$c15; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e22); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsenumber(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f6(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_fast() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 42) { + s1 = peg$c16; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e23); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsenumber(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f7(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_fixed_step() { + var s0, s1, s2; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 37) { + s1 = peg$c17; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e24); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsenumber(); + if (s2 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f8(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslice_with_modifier() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = peg$parseslice(); + if (s1 !== peg$FAILED) { + s2 = peg$parseslice_modifier(); + if (s2 === peg$FAILED) { + s2 = null; + } + peg$savedPos = s0; + s0 = peg$f9(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesingle_cycle() { + var s0, s1, s2; + + s0 = peg$currPos; + s1 = []; + s2 = peg$parseslice_with_modifier(); + if (s2 !== peg$FAILED) { + while (s2 !== peg$FAILED) { + s1.push(s2); + s2 = peg$parseslice_with_modifier(); + } + } else { + s1 = peg$FAILED; + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f10(s1); + } + s0 = s1; + + return s0; + } + + function peg$parsestack() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parsesingle_cycle(); + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$currPos; + s4 = peg$parsecomma(); + if (s4 !== peg$FAILED) { + s5 = peg$parsesingle_cycle(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f11(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$currPos; + s4 = peg$parsecomma(); + if (s4 !== peg$FAILED) { + s5 = peg$parsesingle_cycle(); + if (s5 !== peg$FAILED) { + peg$savedPos = s3; + s3 = peg$f11(s1, s5); + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } else { + peg$currPos = s3; + s3 = peg$FAILED; + } + } + peg$savedPos = s0; + s0 = peg$f12(s1, s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesequence() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parsews(); + s2 = peg$parsequote(); + if (s2 !== peg$FAILED) { + s3 = peg$parsestack(); + if (s3 !== peg$FAILED) { + s4 = peg$parsequote(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f13(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseoperator() { + var s0; + + s0 = peg$parsescale(); + if (s0 === peg$FAILED) { + s0 = peg$parseslow(); + if (s0 === peg$FAILED) { + s0 = peg$parsefast(); + if (s0 === peg$FAILED) { + s0 = peg$parsetarget(); + if (s0 === peg$FAILED) { + s0 = peg$parsebjorklund(); + if (s0 === peg$FAILED) { + s0 = peg$parsestruct(); + if (s0 === peg$FAILED) { + s0 = peg$parserotR(); + if (s0 === peg$FAILED) { + s0 = peg$parserotL(); + } + } + } + } + } + } + } + + return s0; + } + + function peg$parsestruct() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c18) { + s1 = peg$c18; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e25); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsesequence_or_operator(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f14(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsetarget() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c19) { + s1 = peg$c19; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e26); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsequote(); + if (s3 !== peg$FAILED) { + s4 = peg$parsestep(); + if (s4 !== peg$FAILED) { + s5 = peg$parsequote(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f15(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsebjorklund() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c20) { + s1 = peg$c20; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e27); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parseint(); + if (s3 !== peg$FAILED) { + s4 = peg$parsews(); + s5 = peg$parseint(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f16(s3, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseslow() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c21) { + s1 = peg$c21; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e28); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f17(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parserotL() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c22) { + s1 = peg$c22; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e29); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f18(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parserotR() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c23) { + s1 = peg$c23; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e30); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f19(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsefast() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c24) { + s1 = peg$c24; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e31); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f20(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsescale() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 5) === peg$c25) { + s1 = peg$c25; + peg$currPos += 5; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e32); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsequote(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$parsestep_char(); + if (s5 !== peg$FAILED) { + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$parsestep_char(); + } + } else { + s4 = peg$FAILED; + } + if (s4 !== peg$FAILED) { + s5 = peg$parsequote(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f21(s4); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsecomment() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 2) === peg$c26) { + s1 = peg$c26; + peg$currPos += 2; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e33); } + } + if (s1 !== peg$FAILED) { + s2 = []; + if (peg$r5.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e34); } + } + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$r5.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e34); } + } + } + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsecat() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 3) === peg$c27) { + s1 = peg$c27; + peg$currPos += 3; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e35); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 91) { + s3 = peg$c8; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e15); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parsews(); + s5 = peg$parsesequence_or_operator(); + if (s5 !== peg$FAILED) { + s6 = []; + s7 = peg$currPos; + s8 = peg$parsecomma(); + if (s8 !== peg$FAILED) { + s9 = peg$parsesequence_or_operator(); + if (s9 !== peg$FAILED) { + peg$savedPos = s7; + s7 = peg$f22(s5, s9); + } else { + peg$currPos = s7; + s7 = peg$FAILED; + } + } else { + peg$currPos = s7; + s7 = peg$FAILED; + } + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$currPos; + s8 = peg$parsecomma(); + if (s8 !== peg$FAILED) { + s9 = peg$parsesequence_or_operator(); + if (s9 !== peg$FAILED) { + peg$savedPos = s7; + s7 = peg$f22(s5, s9); + } else { + peg$currPos = s7; + s7 = peg$FAILED; + } + } else { + peg$currPos = s7; + s7 = peg$FAILED; + } + } + s7 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 93) { + s8 = peg$c9; + peg$currPos++; + } else { + s8 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e16); } + } + if (s8 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f23(s5, s6); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesequence_or_group() { + var s0; + + s0 = peg$parsecat(); + if (s0 === peg$FAILED) { + s0 = peg$parsesequence(); + } + + return s0; + } + + function peg$parsesequence_or_operator() { + var s0, s1, s2, s3, s4, s5; + + s0 = peg$currPos; + s1 = peg$parsesequence_or_group(); + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = []; + s4 = peg$parsecomment(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parsecomment(); + } + peg$savedPos = s0; + s0 = peg$f24(s1); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + s1 = peg$parseoperator(); + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + if (input.charCodeAt(peg$currPos) === 36) { + s3 = peg$c28; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e36); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parsews(); + s5 = peg$parsesequence_or_operator(); + if (s5 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f25(s1, s5); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parsesequ_or_operator_or_comment() { + var s0, s1; + + s0 = peg$currPos; + s1 = peg$parsesequence_or_operator(); + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f26(s1); + } + s0 = s1; + if (s0 === peg$FAILED) { + s0 = peg$parsecomment(); + } + + return s0; + } + + function peg$parsesequence_definition() { + var s0; + + s0 = peg$parsesequ_or_operator_or_comment(); + + return s0; + } + + function peg$parsecommand() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + s1 = peg$parsews(); + s2 = peg$parsesetcps(); + if (s2 === peg$FAILED) { + s2 = peg$parsesetbpm(); + if (s2 === peg$FAILED) { + s2 = peg$parsehush(); + } + } + if (s2 !== peg$FAILED) { + s3 = peg$parsews(); + peg$savedPos = s0; + s0 = peg$f27(s2); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesetcps() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c29) { + s1 = peg$c29; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e37); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f28(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsesetbpm() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 6) === peg$c30) { + s1 = peg$c30; + peg$currPos += 6; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e38); } + } + if (s1 !== peg$FAILED) { + s2 = peg$parsews(); + s3 = peg$parsenumber(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s0 = peg$f29(s3); + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parsehush() { + var s0, s1; + + s0 = peg$currPos; + if (input.substr(peg$currPos, 4) === peg$c31) { + s1 = peg$c31; + peg$currPos += 4; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$e39); } + } + if (s1 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$f30(); + } + s0 = s1; + + return s0; + } + + function peg$parsestatement() { + var s0; + + s0 = peg$parsesequence_definition(); + if (s0 === peg$FAILED) { + s0 = peg$parsecommand(); + } + + return s0; + } + + + var PatternStub = function(source, alignment) + { + this.type_ = "pattern"; + this.arguments_ = { alignment : alignment}; + this.source_ = source; + } + + var OperatorStub = function(name, args, source) + { + this.type_ = name; + this.arguments_ = args; + this.source_ = source; + } + + var ElementStub = function(source, options) + { + this.type_ = "element"; + this.source_ = source; + this.options_ = options; + } + + var CommandStub = function(name, options) + { + this.type_ = "command"; + this.name_ = name; + this.options_ = options; + } + + + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } +} + +export { + peg$SyntaxError as SyntaxError, + peg$parse as parse +}; diff --git a/docs/_snowpack/link/strudel.js b/docs/_snowpack/link/strudel.js index 3934b8e4..0d6867c8 100644 --- a/docs/_snowpack/link/strudel.js +++ b/docs/_snowpack/link/strudel.js @@ -292,20 +292,40 @@ class Pattern { outerJoin() { return this.outerBind(id); } + _patternify(func) { + const pat = this; + const patterned = function(...args) { + const pat_arg = sequence(...args); + return pat_arg.fmap((arg) => func.call(pat, arg)).outerJoin(); + }; + return patterned; + } _fast(factor) { var fastQuery = this.withQueryTime((t) => t.mul(factor)); return fastQuery.withEventTime((t) => t.div(factor)); } + fast(factor) { + return this._patternify(Pattern.prototype._fast)(factor); + } _slow(factor) { return this._fast(1 / factor); } + slow(factor) { + return this._patternify(Pattern.prototype._slow)(factor); + } _early(offset) { offset = Fraction(offset); return this.withQueryTime((t) => t.add(offset)).withEventTime((t) => t.sub(offset)); } + early(factor) { + return this._patternify(Pattern.prototype._early)(factor); + } _late(offset) { return this._early(0 - offset); } + late(factor) { + return this._patternify(Pattern.prototype._late)(factor); + } when(binary_pat, func) { var true_pat = binary_pat._filterValues(id); var false_pat = binary_pat._filterValues((val) => !val); diff --git a/docs/dist/App.js b/docs/dist/App.js index 4284e687..92ad6e4a 100644 --- a/docs/dist/App.js +++ b/docs/dist/App.js @@ -5,13 +5,10 @@ import cx from "./cx.js"; import * as Tone from "../_snowpack/pkg/tone.js"; import useCycle from "./useCycle.js"; import {tetris} from "./tunes.js"; -const {Fraction, TimeSpan} = strudel; -const fr = (v) => new Fraction(v); -const ts = (start, end) => new TimeSpan(fr(start), fr(end)); -const parse = (code) => { - const {sequence, pure, reify, slowcat, fastcat, cat, stack, silence} = strudel; - return eval(code); -}; +import _mini from "./mini.js"; +const {sequence, pure, reify, slowcat, fastcat, cat, stack, silence} = strudel; +const mini = _mini; +const parse = (code) => eval(code); const synth = new Tone.PolySynth().toDestination(); synth.set({ oscillator: {type: "triangle"}, diff --git a/docs/dist/mini.js b/docs/dist/mini.js new file mode 100644 index 00000000..15b82788 --- /dev/null +++ b/docs/dist/mini.js @@ -0,0 +1,20 @@ +import * as krill from "../_snowpack/link/repl/krill-parser.js"; +import * as strudel from "../_snowpack/link/strudel.js"; +const {sequence, silence} = strudel; +export function patternifyAST(ast) { + switch (ast.type_) { + case "pattern": + return sequence(...ast.source_.map(patternifyAST)); + case "element": + if (ast.source_ === "~") { + return silence; + } + if (typeof ast.source_ !== "object") { + return ast.source_; + } + return patternifyAST(ast.source_); + } +} +export default (...strings) => { + return sequence(...strings.map((str) => patternifyAST(krill.parse(`"${str}"`)))); +}; diff --git a/docs/dist/tunes.js b/docs/dist/tunes.js index 3e9dd75d..4d71f41f 100644 --- a/docs/dist/tunes.js +++ b/docs/dist/tunes.js @@ -1,4 +1,4 @@ -export const tetris = `stack(sequence( +export const tetrisWithFunctions = `stack(sequence( 'e5', sequence('b4', 'c5'), 'd5', sequence('c5', 'b4'), 'a4', sequence('a4', 'c5'), 'e5', sequence('d5', 'c5'), 'b4', sequence(silence, 'c5'), 'd5', 'e5', @@ -18,6 +18,32 @@ export const tetris = `stack(sequence( 'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2', ) )._slow(16)`; +export const tetris = `stack( + sequence( + mini( + 'e5 [b4 c5] d5 [c5 b4]', + 'a4 [a4 c5] e5 [d5 c5]', + 'b4 [~ c5] d5 e5', + 'c5 a4 a4 ~', + '[~ d5] [~ f5] a5 [g5 f5]', + 'e5 [~ c5] e5 [d5 c5]', + 'b4 [b4 c5] d5 e5', + 'c5 a4 a4 ~' + ) + ), + sequence( + mini( + 'e2 e3 e2 e3 e2 e3 e2 e3', + 'a2 a3 a2 a3 a2 a3 a2 a3', + 'g#2 g#3 g#2 g#3 e2 e3 e2 e3', + 'a2 a3 a2 a3 a2 a3 b1 c2', + 'd2 d3 d2 d3 d2 d3 d2 d3', + 'c2 c3 c2 c3 c2 c3 c2 c3', + 'b1 b2 b1 b2 e2 e3 e2 e3', + 'a1 a2 a1 a2 a1 a2 a1 a2' + ) + ) +)._slow(16);`; export const spanish = `slowcat( stack('c4','eb4','g4'), stack('bb3','d4','f4'), From f56d40ac8d78be8999168424c6cf546d7f4844ac Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 13:03:23 +0100 Subject: [PATCH 04/21] build with /strudel --- docs/dist/logo.svg.proxy.js | 2 +- docs/index.html | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/dist/logo.svg.proxy.js b/docs/dist/logo.svg.proxy.js index aed9da04..73f6fe7f 100644 --- a/docs/dist/logo.svg.proxy.js +++ b/docs/dist/logo.svg.proxy.js @@ -1 +1 @@ -export default "/dist/logo.svg"; \ No newline at end of file +export default "/strudel/dist/logo.svg"; \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 0e7db17b..507724ce 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,8 +2,8 @@ - - + + Strudel REPL @@ -11,6 +11,6 @@
- + From 3919a2165034a536be87d76b64667c847144eb28 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 14:19:15 +0100 Subject: [PATCH 05/21] mini: support stack with "," --- repl/src/App.tsx | 6 ++++-- repl/src/mini.ts | 16 ++++++++++++++-- repl/src/tunes.ts | 2 ++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/repl/src/App.tsx b/repl/src/App.tsx index 18d9e797..0ad4653e 100644 --- a/repl/src/App.tsx +++ b/repl/src/App.tsx @@ -5,9 +5,11 @@ import cx from './cx'; import * as Tone from 'tone'; import useCycle from './useCycle'; import type { Hap, Pattern } from './types'; -import { tetris } from './tunes'; +import * as tunes from './tunes'; import _mini from './mini'; +const { tetris, tetrisMini } = tunes; + const { sequence, pure, reify, slowcat, fastcat, cat, stack, silence } = strudel; // make available to eval const mini = _mini; // for eval (direct import wont work somehow) const parse = (code: string): Pattern => eval(code); @@ -21,7 +23,7 @@ synth.set({ }); function App() { - const [code, setCode] = useState(tetris); + const [code, setCode] = useState(tetrisMini); const [log, setLog] = useState(''); const logBox = useRef(); const [error, setError] = useState(); diff --git a/repl/src/mini.ts b/repl/src/mini.ts index 1dbeacd6..40e31b8a 100644 --- a/repl/src/mini.ts +++ b/repl/src/mini.ts @@ -1,11 +1,14 @@ import * as krill from '../krill-parser'; import * as strudel from '../../strudel.mjs'; -const { sequence, silence } = strudel; +const { sequence, stack, silence } = strudel; export function patternifyAST(ast: any): any { switch (ast.type_) { case 'pattern': + if (ast.arguments_.alignment === 'v') { + return stack(...ast.source_.map(patternifyAST)); + } return sequence(...ast.source_.map(patternifyAST)); case 'element': if (ast.source_ === '~') { @@ -16,11 +19,20 @@ export function patternifyAST(ast: any): any { } return patternifyAST(ast.source_); } + // *3 => options_.operator.arguments_.amount = 1/3 } /* export default (str: string) => patternifyAST(krill.parse(`"${str}"`)); */ export default (...strings: string[]) => { - return sequence(...strings.map((str) => patternifyAST(krill.parse(`"${str}"`)))); + const pattern = sequence( + ...strings.map((str) => { + const ast = krill.parse(`"${str}"`); + // console.log('ast', ast); + return patternifyAST(ast); + }) + ); + // console.log('mini pattern', pattern); + return pattern; }; /* diff --git a/repl/src/tunes.ts b/repl/src/tunes.ts index f2b75380..5685d190 100644 --- a/repl/src/tunes.ts +++ b/repl/src/tunes.ts @@ -46,6 +46,8 @@ export const tetris = `stack( ) )._slow(16);`; +export const tetrisMini = `mini('[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]],[[e2 e3 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]')._slow(16);`; + // "sequence('c3', 'eb3', sequence('g3', 'f3'))" // /* `sequence( stack('c4','eb4','g4'), From bc4893b4ba1a6eaa06d942d4721a300f34ae8d37 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 14:19:49 +0100 Subject: [PATCH 06/21] build --- docs/dist/App.js | 5 +++-- docs/dist/mini.js | 11 +++++++++-- docs/dist/tunes.js | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/docs/dist/App.js b/docs/dist/App.js index 92ad6e4a..2565580f 100644 --- a/docs/dist/App.js +++ b/docs/dist/App.js @@ -4,8 +4,9 @@ import * as strudel from "../_snowpack/link/strudel.js"; import cx from "./cx.js"; import * as Tone from "../_snowpack/pkg/tone.js"; import useCycle from "./useCycle.js"; -import {tetris} from "./tunes.js"; +import * as tunes from "./tunes.js"; import _mini from "./mini.js"; +const {tetris, tetrisMini} = tunes; const {sequence, pure, reify, slowcat, fastcat, cat, stack, silence} = strudel; const mini = _mini; const parse = (code) => eval(code); @@ -17,7 +18,7 @@ synth.set({ } }); function App() { - const [code, setCode] = useState(tetris); + const [code, setCode] = useState(tetrisMini); const [log, setLog] = useState(""); const logBox = useRef(); const [error, setError] = useState(); diff --git a/docs/dist/mini.js b/docs/dist/mini.js index 15b82788..c491801b 100644 --- a/docs/dist/mini.js +++ b/docs/dist/mini.js @@ -1,9 +1,12 @@ import * as krill from "../_snowpack/link/repl/krill-parser.js"; import * as strudel from "../_snowpack/link/strudel.js"; -const {sequence, silence} = strudel; +const {sequence, stack, silence} = strudel; export function patternifyAST(ast) { switch (ast.type_) { case "pattern": + if (ast.arguments_.alignment === "v") { + return stack(...ast.source_.map(patternifyAST)); + } return sequence(...ast.source_.map(patternifyAST)); case "element": if (ast.source_ === "~") { @@ -16,5 +19,9 @@ export function patternifyAST(ast) { } } export default (...strings) => { - return sequence(...strings.map((str) => patternifyAST(krill.parse(`"${str}"`)))); + const pattern = sequence(...strings.map((str) => { + const ast = krill.parse(`"${str}"`); + return patternifyAST(ast); + })); + return pattern; }; diff --git a/docs/dist/tunes.js b/docs/dist/tunes.js index 4d71f41f..95eb653e 100644 --- a/docs/dist/tunes.js +++ b/docs/dist/tunes.js @@ -44,6 +44,7 @@ export const tetris = `stack( ) ) )._slow(16);`; +export const tetrisMini = `mini('[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]],[[e2 e3 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]')._slow(16);`; export const spanish = `slowcat( stack('c4','eb4','g4'), stack('bb3','d4','f4'), From 5b7054b0be5e0b63023cd5b8e1bdc91520de54d7 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 14:24:06 +0100 Subject: [PATCH 07/21] add linebreaks --- repl/src/tunes.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/repl/src/tunes.ts b/repl/src/tunes.ts index 5685d190..5c5df672 100644 --- a/repl/src/tunes.ts +++ b/repl/src/tunes.ts @@ -46,7 +46,24 @@ export const tetris = `stack( ) )._slow(16);`; -export const tetrisMini = `mini('[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]],[[e2 e3 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]')._slow(16);`; +export const tetrisMini1 = `mini('[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]],[[e2 e3 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]')._slow(16);`; +export const tetrisMini = `mini(\`[[e5 [b4 c5] d5 [c5 b4]] +[a4 [a4 c5] e5 [d5 c5]] +[b4 [~ c5] d5 e5] +[c5 a4 a4 ~] +[[~ d5] [~ f5] a5 [g5 f5]] +[e5 [~ c5] e5 [d5 c5]] +[b4 [b4 c5] d5 e5] +[c5 a4 a4 ~]], +[[e2 e3 e2 e3 e2 e3 e2 e3] +[a2 a3 a2 a3 a2 a3 a2 a3] +[g#2 g#3 g#2 g#3 e2 e3 e2 e3] +[a2 a3 a2 a3 a2 a3 b1 c2] +[d2 d3 d2 d3 d2 d3 d2 d3] +[c2 c3 c2 c3 c2 c3 c2 c3] +[b1 b2 b1 b2 e2 e3 e2 e3] +[a1 a2 a1 a2 a1 a2 a1 a2]]\`)._slow(16); +`; // "sequence('c3', 'eb3', sequence('g3', 'f3'))" // /* `sequence( From c8bf4331c817d7a293b1883a1c4585e6b0c8fe8f Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 14:24:30 +0100 Subject: [PATCH 08/21] build --- docs/dist/tunes.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/dist/tunes.js b/docs/dist/tunes.js index 95eb653e..372061c2 100644 --- a/docs/dist/tunes.js +++ b/docs/dist/tunes.js @@ -44,7 +44,24 @@ export const tetris = `stack( ) ) )._slow(16);`; -export const tetrisMini = `mini('[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]],[[e2 e3 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]')._slow(16);`; +export const tetrisMini1 = `mini('[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]],[[e2 e3 e2 e3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 a2 a3] [g#2 g#3 g#2 g#3 e2 e3 e2 e3] [a2 a3 a2 a3 a2 a3 b1 c2] [d2 d3 d2 d3 d2 d3 d2 d3] [c2 c3 c2 c3 c2 c3 c2 c3] [b1 b2 b1 b2 e2 e3 e2 e3] [a1 a2 a1 a2 a1 a2 a1 a2]]')._slow(16);`; +export const tetrisMini = `mini(\`[[e5 [b4 c5] d5 [c5 b4]] +[a4 [a4 c5] e5 [d5 c5]] +[b4 [~ c5] d5 e5] +[c5 a4 a4 ~] +[[~ d5] [~ f5] a5 [g5 f5]] +[e5 [~ c5] e5 [d5 c5]] +[b4 [b4 c5] d5 e5] +[c5 a4 a4 ~]], +[[e2 e3 e2 e3 e2 e3 e2 e3] +[a2 a3 a2 a3 a2 a3 a2 a3] +[g#2 g#3 g#2 g#3 e2 e3 e2 e3] +[a2 a3 a2 a3 a2 a3 b1 c2] +[d2 d3 d2 d3 d2 d3 d2 d3] +[c2 c3 c2 c3 c2 c3 c2 c3] +[b1 b2 b1 b2 e2 e3 e2 e3] +[a1 a2 a1 a2 a1 a2 a1 a2]]\`)._slow(16); +`; export const spanish = `slowcat( stack('c4','eb4','g4'), stack('bb3','d4','f4'), From 27be90358ffdac5dd7cd75152082dcebbbbc411c Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 20:59:41 +0100 Subject: [PATCH 09/21] add many more krill operators --- repl/package-lock.json | 623 +++++++++++++++++++++++++++++++++++++++-- repl/package.json | 1 + repl/src/App.tsx | 8 +- repl/src/mini.ts | 55 ---- repl/src/parse.d.ts | 18 ++ repl/src/parse.ts | 101 +++++++ repl/src/tunes.ts | 36 ++- repl/src/types.d.ts | 1 + 8 files changed, 758 insertions(+), 85 deletions(-) delete mode 100644 repl/src/mini.ts create mode 100644 repl/src/parse.d.ts create mode 100644 repl/src/parse.ts diff --git a/repl/package-lock.json b/repl/package-lock.json index a08c11c7..2003efbc 100644 --- a/repl/package-lock.json +++ b/repl/package-lock.json @@ -5,8 +5,10 @@ "packages": { "": { "dependencies": { + "@tonaljs/tonal": "^4.6.5", "react": "^17.0.2", "react-dom": "^17.0.2", + "tonal": "^2.2.2", "tone": "^14.7.77" }, "devDependencies": { @@ -26,7 +28,6 @@ "autoprefixer": "^10.4.2", "chai": "^4.3.4", "peggy": "^1.2.0", - "pegjs": "^0.10.0", "postcss": "^8.4.6", "prettier": "^2.2.1", "snowpack": "^3.3.7", @@ -1084,6 +1085,205 @@ "react-dom": "*" } }, + "node_modules/@tonaljs/abc-notation": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/abc-notation/-/abc-notation-4.6.5.tgz", + "integrity": "sha512-1S0Jnx0NfDLgyhkQOMEHqOacELL6RUdPcWWUP+nAnsOsb9owvB9RKYLSzp5odd16FVUR7U8c/JLc2yxIRvSeJw==", + "dependencies": { + "@tonaljs/core": "^4.6.5" + } + }, + "node_modules/@tonaljs/array": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/array/-/array-4.6.5.tgz", + "integrity": "sha512-7A3DbBQ+qIQ134FqE518b4tJ8V2a15Sn303JjHzgnqZqKrNh/s3wqwkL60F7LKcd09tcp+vIKQP/MYt4xMcRAA==", + "dependencies": { + "@tonaljs/core": "^4.6.5" + } + }, + "node_modules/@tonaljs/chord": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/chord/-/chord-4.6.5.tgz", + "integrity": "sha512-Pjdel4aDVv4kcx9PW6Qozt5BB9nAt13AOExfzKztpgPmlBSy0SKHse7Jp1cA4MGAuLHU8dzVssTFYpCskEFw3w==", + "dependencies": { + "@tonaljs/chord-detect": "^4.6.5", + "@tonaljs/chord-type": "^4.6.5", + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5", + "@tonaljs/pcset": "^4.6.5", + "@tonaljs/scale-type": "^4.6.5" + } + }, + "node_modules/@tonaljs/chord-detect": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/chord-detect/-/chord-detect-4.6.5.tgz", + "integrity": "sha512-4xu53UP4kNTfdTNpAAVijhXcQ+ypJqmeMnsST08ZXSjoYfJUhmf5rWDWfz36KOTtNdCA6AbYgdtTYV/Xw0nd/w==", + "dependencies": { + "@tonaljs/chord-type": "^4.6.5", + "@tonaljs/core": "^4.6.5", + "@tonaljs/pcset": "^4.6.5" + } + }, + "node_modules/@tonaljs/chord-type": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/chord-type/-/chord-type-4.6.5.tgz", + "integrity": "sha512-Ol4DDopqpZCF9odosO2i8I+plud3Ul7VWJGNvL+PPCf4Qnwuz87q3aJQDLNoRUz4VyW0u66mq3LyVh6A8kb6Ug==", + "dependencies": { + "@tonaljs/core": "^4.6.5", + "@tonaljs/pcset": "^4.6.5" + } + }, + "node_modules/@tonaljs/collection": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@tonaljs/collection/-/collection-4.6.2.tgz", + "integrity": "sha512-bfPCotLJNB/tG1NrdbsQPLDKZB5jlMs7uPQ6RYKiNkaena3345ZKkbCGl5pj6YTXeDm/oblXiSbFAn7SlLRZdQ==" + }, + "node_modules/@tonaljs/core": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/core/-/core-4.6.5.tgz", + "integrity": "sha512-t7Vx0+L3j/ubQj2AhI1H45D/K745np4DwJjJjXNi5FlGD+TL2wyw50dCwkHKGHsrLDqup1qqP6yN7LBpC6UwNg==" + }, + "node_modules/@tonaljs/duration-value": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@tonaljs/duration-value/-/duration-value-4.6.2.tgz", + "integrity": "sha512-zrXT0L/qsDQ6251Mlqz54vcUbYUB9xb6uJhlxUzc6VauXOt8UOfrdTULubRTXTaBwWt1h8J5n9pXTQmNGzNI9A==" + }, + "node_modules/@tonaljs/interval": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/interval/-/interval-4.6.5.tgz", + "integrity": "sha512-7EDWhqZ7Nnh9oD4ahRYJHLc799ACGxYL4hDHwMKD16B2MgXqPvDeDvwQ31qUuO0ruGz8tMb3FDlgg0Hplowcbw==", + "dependencies": { + "@tonaljs/core": "^4.6.5" + } + }, + "node_modules/@tonaljs/key": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/key/-/key-4.6.5.tgz", + "integrity": "sha512-ZdZWb5IStx6CLRmdEjawR66CqNpoW3EVUua2nVZBMdgnNebWxt4nvgH/ZNvGlCQGFZkUZzRhCfTwqsS6e3OmSA==", + "dependencies": { + "@tonaljs/core": "^4.6.5", + "@tonaljs/note": "^4.6.5", + "@tonaljs/roman-numeral": "^4.6.5" + } + }, + "node_modules/@tonaljs/midi": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/midi/-/midi-4.6.5.tgz", + "integrity": "sha512-fJEZtNvV3M6yW1w+Tep60Rbv5PvuKszQcQzaJS1Loq5mHOKAzdmRfuJSpEpZBiaKEZ1WAMh1QKXYyOd+imyGQg==", + "dependencies": { + "@tonaljs/core": "^4.6.5" + } + }, + "node_modules/@tonaljs/mode": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/mode/-/mode-4.6.5.tgz", + "integrity": "sha512-54iaON1rJ6q8fV5iuei8RGDxYhKBGGxZz3rjAxGSqdTUwBRVOdPqtzOkofThf9gRGYOMhzPp1BMbxbV+UCAPsA==", + "dependencies": { + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5", + "@tonaljs/interval": "^4.6.5", + "@tonaljs/pcset": "^4.6.5", + "@tonaljs/scale-type": "^4.6.5" + } + }, + "node_modules/@tonaljs/note": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/note/-/note-4.6.5.tgz", + "integrity": "sha512-Y0/eTzcReXzfcSLLG4k/dLLayqbvh/XYIkybG/QMDyR0BREuJq0Sw+NavbzhTtO0dadIQb/qfe0GFq4k2xS+NQ==", + "dependencies": { + "@tonaljs/core": "^4.6.5", + "@tonaljs/midi": "^4.6.5" + } + }, + "node_modules/@tonaljs/pcset": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/pcset/-/pcset-4.6.5.tgz", + "integrity": "sha512-oWAKflP3cREnUfScqsBzg2LLKNevxSnpDtrq8CPtwOAsrAa8PjQG07NQfhqIiFMjPUdgkDiER3qVA1n8dDwAJA==", + "dependencies": { + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5" + } + }, + "node_modules/@tonaljs/progression": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/progression/-/progression-4.6.5.tgz", + "integrity": "sha512-ijYEgMFQG4izHYUw5cRtBRNBuoYzmpGvb/tRiykhJNI6XIjekZEMiMsOMfb1u5q+EGvnVNXRmrluMRDIz2rmRw==", + "dependencies": { + "@tonaljs/chord": "^4.6.5", + "@tonaljs/core": "^4.6.5", + "@tonaljs/roman-numeral": "^4.6.5" + } + }, + "node_modules/@tonaljs/range": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/range/-/range-4.6.5.tgz", + "integrity": "sha512-99cOvVJ3l4X0UJuTSa6qE87JriREnnWIsi3xo1/n7RoqFxnfi8YPh4SfJJyysvHcT18X4EfcTNde9ancMBVu6A==", + "dependencies": { + "@tonaljs/collection": "^4.6.2", + "@tonaljs/midi": "^4.6.5" + } + }, + "node_modules/@tonaljs/roman-numeral": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/roman-numeral/-/roman-numeral-4.6.5.tgz", + "integrity": "sha512-bWYQNZWKmYDDcmbQQNwcWAHfTWanpzmvI0wplrMnGd4x0op5etwUEv+Yzjg0B1ef+E+zcU02Sl0WwRJhaDK3hg==", + "dependencies": { + "@tonaljs/core": "^4.6.5" + } + }, + "node_modules/@tonaljs/scale": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/scale/-/scale-4.6.5.tgz", + "integrity": "sha512-isYDamelOBtcd5bEnJ8QV0Js7jKRwZ0FlFVE/+bUN3wsyo9u6KLL5gMyfH9RKdx74m8lE13JXYTXgKqe+AOa4A==", + "dependencies": { + "@tonaljs/chord-type": "^4.6.5", + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5", + "@tonaljs/note": "^4.6.5", + "@tonaljs/pcset": "^4.6.5", + "@tonaljs/scale-type": "^4.6.5" + } + }, + "node_modules/@tonaljs/scale-type": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/scale-type/-/scale-type-4.6.5.tgz", + "integrity": "sha512-rwcDOYf2UifjLJhmuQ8f8bJSeOCMDQJ1lB7lzlqdFxes03OeQhdOEfrT0nPtW8BhBEvq4GMM2NA6CLxX8MTwOQ==", + "dependencies": { + "@tonaljs/core": "^4.6.5", + "@tonaljs/pcset": "^4.6.5" + } + }, + "node_modules/@tonaljs/time-signature": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@tonaljs/time-signature/-/time-signature-4.6.2.tgz", + "integrity": "sha512-OlZY4gdLd21WpMeAI1nS9E9zWcYU6oAzh6ptAUndqmVnFIrIWIWKCkWapdFx8dWdqrX8jqya3m4T33wmeo7w5Q==" + }, + "node_modules/@tonaljs/tonal": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/tonal/-/tonal-4.6.5.tgz", + "integrity": "sha512-lmsWinI9dy7nQyzCEgDVeVAwJtsk4ey05cJZd6oa4QVuSFD+CR8ebaEiwT4/Na+W0kHrKicT3h0uYc2PJIvx5Q==", + "dependencies": { + "@tonaljs/abc-notation": "^4.6.5", + "@tonaljs/array": "^4.6.5", + "@tonaljs/chord": "^4.6.5", + "@tonaljs/chord-type": "^4.6.5", + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5", + "@tonaljs/duration-value": "^4.6.2", + "@tonaljs/interval": "^4.6.5", + "@tonaljs/key": "^4.6.5", + "@tonaljs/midi": "^4.6.5", + "@tonaljs/mode": "^4.6.5", + "@tonaljs/note": "^4.6.5", + "@tonaljs/pcset": "^4.6.5", + "@tonaljs/progression": "^4.6.5", + "@tonaljs/range": "^4.6.5", + "@tonaljs/roman-numeral": "^4.6.5", + "@tonaljs/scale": "^4.6.5", + "@tonaljs/scale-type": "^4.6.5", + "@tonaljs/time-signature": "^4.6.2" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -6081,18 +6281,6 @@ "node": ">=10" } }, - "node_modules/pegjs": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", - "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", - "dev": true, - "bin": { - "pegjs": "bin/pegjs" - }, - "engines": { - "node": ">=0.10" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -7878,6 +8066,108 @@ "node": ">=0.6" } }, + "node_modules/tonal": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal/-/tonal-2.2.2.tgz", + "integrity": "sha512-Ze2bQc6KhAf3FKM9HzEsQ4z8hZh4WYCOsCrryONqf/THGOrOpL9Cc8Uc0dq0OA2yK2JbD5FhZckEXNYyD9946A==", + "dependencies": { + "tonal-array": "^2.2.2", + "tonal-chord": "^2.2.2", + "tonal-dictionary": "^2.2.2", + "tonal-distance": "^2.2.2", + "tonal-interval": "^2.2.2", + "tonal-key": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-pcset": "^2.2.2", + "tonal-scale": "^2.2.2" + } + }, + "node_modules/tonal-array": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-array/-/tonal-array-2.2.2.tgz", + "integrity": "sha512-h6YIq20L0EEU4EsDoKHAjl5kD2EQn467VfV79QHAuybvNCJpqqRNsQ3QNvoQyir1BgDXaDUIN9FEmQJNiaaCKA==", + "dependencies": { + "tonal-note": "^2.2.2" + } + }, + "node_modules/tonal-chord": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-chord/-/tonal-chord-2.2.2.tgz", + "integrity": "sha512-gOIXapi6Gx3ISRKdEJKEQjhDBiwjhaalyWSrN5rijGrSyyFFNZ+EVOfzcqLtnVAF9BgeO9Ca0eXCor3XpHdEJg==", + "dependencies": { + "tonal-dictionary": "^2.2.2", + "tonal-distance": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-pcset": "^2.2.2" + } + }, + "node_modules/tonal-dictionary": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-dictionary/-/tonal-dictionary-2.2.2.tgz", + "integrity": "sha512-283ppJl/0lohhlVPMI6t5C6XwaP5Wx0egu9qfG9TLCT2tn4pRwYpXkzGufd9icvkJTgOylOum3+RxWmywUIPIg==", + "dependencies": { + "tonal-array": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-pcset": "^2.2.2" + } + }, + "node_modules/tonal-distance": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-2.2.2.tgz", + "integrity": "sha512-ktA6OapCxaetXJb/JuXD5QwfyB7/G3y3ONby7Kkbezyffc57cnNfjdhlTR9XBR7eSFIY/J1KuhLwMx/qrffT4g==", + "dependencies": { + "tonal-interval": "^2.2.2", + "tonal-note": "^2.2.2" + } + }, + "node_modules/tonal-interval": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-2.2.2.tgz", + "integrity": "sha512-lrtDU8lH5IAX7YE63OhGGDRpVb4OoGxaN0wDu5XC3sUhXBwjSgNYpHY2D9JI2aWQ/Er9jhQbnw9b0ffkLy34+Q==" + }, + "node_modules/tonal-key": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-key/-/tonal-key-2.2.2.tgz", + "integrity": "sha512-KIc0b8yPl2ATDxF/65P52tIIempNsAQrug0idpD0zFvs5F5cb1hp7Rh7JJ4gECwC/6a3Hgdd1jomI+TnJ7K98w==", + "dependencies": { + "tonal-array": "^2.2.2", + "tonal-distance": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-roman-numeral": "^2.2.2" + } + }, + "node_modules/tonal-note": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-note/-/tonal-note-2.2.2.tgz", + "integrity": "sha512-RNK3Nb8PxBEW9yYGStcoczgE8bCYFZ5zfLvYJjvuzLWiwTQmqWOhTzONVobVCGFZ/jgDNwpBEKe/bngL3g3Xfw==" + }, + "node_modules/tonal-pcset": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-pcset/-/tonal-pcset-2.2.2.tgz", + "integrity": "sha512-PSqhkxzckO6J27W0GxawHYln4wvfDJ7puDmccksyFOBo97UhLnpxiyvBekhiYpkuaMtoZLQC/KALAkEj7lcb+A==", + "dependencies": { + "tonal-array": "^2.2.2", + "tonal-interval": "^2.2.2", + "tonal-note": "^2.2.2" + } + }, + "node_modules/tonal-roman-numeral": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-roman-numeral/-/tonal-roman-numeral-2.2.2.tgz", + "integrity": "sha512-+auQNObpW3OvsSqlo+Cc+0otrlEhtbEgpzkPoKbTtkCva0P9oSkSz0OZ9fI73KQM5MsBs1XbB+olxppWkzYTFw==" + }, + "node_modules/tonal-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-scale/-/tonal-scale-2.2.2.tgz", + "integrity": "sha512-tDb3YCoTF50XOXq9kNhGB1JkInk7qAGN6GQnP/3xkGxkreFFRZyI58jfHlmWf/AH4+IKb/exsOmL6G8Ok/PCRw==", + "dependencies": { + "tonal-array": "^2.2.2", + "tonal-dictionary": "^2.2.2", + "tonal-distance": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-pcset": "^2.2.2" + } + }, "node_modules/tone": { "version": "14.7.77", "resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz", @@ -9250,6 +9540,205 @@ "@testing-library/dom": "^7.28.1" } }, + "@tonaljs/abc-notation": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/abc-notation/-/abc-notation-4.6.5.tgz", + "integrity": "sha512-1S0Jnx0NfDLgyhkQOMEHqOacELL6RUdPcWWUP+nAnsOsb9owvB9RKYLSzp5odd16FVUR7U8c/JLc2yxIRvSeJw==", + "requires": { + "@tonaljs/core": "^4.6.5" + } + }, + "@tonaljs/array": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/array/-/array-4.6.5.tgz", + "integrity": "sha512-7A3DbBQ+qIQ134FqE518b4tJ8V2a15Sn303JjHzgnqZqKrNh/s3wqwkL60F7LKcd09tcp+vIKQP/MYt4xMcRAA==", + "requires": { + "@tonaljs/core": "^4.6.5" + } + }, + "@tonaljs/chord": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/chord/-/chord-4.6.5.tgz", + "integrity": "sha512-Pjdel4aDVv4kcx9PW6Qozt5BB9nAt13AOExfzKztpgPmlBSy0SKHse7Jp1cA4MGAuLHU8dzVssTFYpCskEFw3w==", + "requires": { + "@tonaljs/chord-detect": "^4.6.5", + "@tonaljs/chord-type": "^4.6.5", + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5", + "@tonaljs/pcset": "^4.6.5", + "@tonaljs/scale-type": "^4.6.5" + } + }, + "@tonaljs/chord-detect": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/chord-detect/-/chord-detect-4.6.5.tgz", + "integrity": "sha512-4xu53UP4kNTfdTNpAAVijhXcQ+ypJqmeMnsST08ZXSjoYfJUhmf5rWDWfz36KOTtNdCA6AbYgdtTYV/Xw0nd/w==", + "requires": { + "@tonaljs/chord-type": "^4.6.5", + "@tonaljs/core": "^4.6.5", + "@tonaljs/pcset": "^4.6.5" + } + }, + "@tonaljs/chord-type": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/chord-type/-/chord-type-4.6.5.tgz", + "integrity": "sha512-Ol4DDopqpZCF9odosO2i8I+plud3Ul7VWJGNvL+PPCf4Qnwuz87q3aJQDLNoRUz4VyW0u66mq3LyVh6A8kb6Ug==", + "requires": { + "@tonaljs/core": "^4.6.5", + "@tonaljs/pcset": "^4.6.5" + } + }, + "@tonaljs/collection": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@tonaljs/collection/-/collection-4.6.2.tgz", + "integrity": "sha512-bfPCotLJNB/tG1NrdbsQPLDKZB5jlMs7uPQ6RYKiNkaena3345ZKkbCGl5pj6YTXeDm/oblXiSbFAn7SlLRZdQ==" + }, + "@tonaljs/core": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/core/-/core-4.6.5.tgz", + "integrity": "sha512-t7Vx0+L3j/ubQj2AhI1H45D/K745np4DwJjJjXNi5FlGD+TL2wyw50dCwkHKGHsrLDqup1qqP6yN7LBpC6UwNg==" + }, + "@tonaljs/duration-value": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@tonaljs/duration-value/-/duration-value-4.6.2.tgz", + "integrity": "sha512-zrXT0L/qsDQ6251Mlqz54vcUbYUB9xb6uJhlxUzc6VauXOt8UOfrdTULubRTXTaBwWt1h8J5n9pXTQmNGzNI9A==" + }, + "@tonaljs/interval": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/interval/-/interval-4.6.5.tgz", + "integrity": "sha512-7EDWhqZ7Nnh9oD4ahRYJHLc799ACGxYL4hDHwMKD16B2MgXqPvDeDvwQ31qUuO0ruGz8tMb3FDlgg0Hplowcbw==", + "requires": { + "@tonaljs/core": "^4.6.5" + } + }, + "@tonaljs/key": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/key/-/key-4.6.5.tgz", + "integrity": "sha512-ZdZWb5IStx6CLRmdEjawR66CqNpoW3EVUua2nVZBMdgnNebWxt4nvgH/ZNvGlCQGFZkUZzRhCfTwqsS6e3OmSA==", + "requires": { + "@tonaljs/core": "^4.6.5", + "@tonaljs/note": "^4.6.5", + "@tonaljs/roman-numeral": "^4.6.5" + } + }, + "@tonaljs/midi": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/midi/-/midi-4.6.5.tgz", + "integrity": "sha512-fJEZtNvV3M6yW1w+Tep60Rbv5PvuKszQcQzaJS1Loq5mHOKAzdmRfuJSpEpZBiaKEZ1WAMh1QKXYyOd+imyGQg==", + "requires": { + "@tonaljs/core": "^4.6.5" + } + }, + "@tonaljs/mode": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/mode/-/mode-4.6.5.tgz", + "integrity": "sha512-54iaON1rJ6q8fV5iuei8RGDxYhKBGGxZz3rjAxGSqdTUwBRVOdPqtzOkofThf9gRGYOMhzPp1BMbxbV+UCAPsA==", + "requires": { + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5", + "@tonaljs/interval": "^4.6.5", + "@tonaljs/pcset": "^4.6.5", + "@tonaljs/scale-type": "^4.6.5" + } + }, + "@tonaljs/note": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/note/-/note-4.6.5.tgz", + "integrity": "sha512-Y0/eTzcReXzfcSLLG4k/dLLayqbvh/XYIkybG/QMDyR0BREuJq0Sw+NavbzhTtO0dadIQb/qfe0GFq4k2xS+NQ==", + "requires": { + "@tonaljs/core": "^4.6.5", + "@tonaljs/midi": "^4.6.5" + } + }, + "@tonaljs/pcset": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/pcset/-/pcset-4.6.5.tgz", + "integrity": "sha512-oWAKflP3cREnUfScqsBzg2LLKNevxSnpDtrq8CPtwOAsrAa8PjQG07NQfhqIiFMjPUdgkDiER3qVA1n8dDwAJA==", + "requires": { + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5" + } + }, + "@tonaljs/progression": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/progression/-/progression-4.6.5.tgz", + "integrity": "sha512-ijYEgMFQG4izHYUw5cRtBRNBuoYzmpGvb/tRiykhJNI6XIjekZEMiMsOMfb1u5q+EGvnVNXRmrluMRDIz2rmRw==", + "requires": { + "@tonaljs/chord": "^4.6.5", + "@tonaljs/core": "^4.6.5", + "@tonaljs/roman-numeral": "^4.6.5" + } + }, + "@tonaljs/range": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/range/-/range-4.6.5.tgz", + "integrity": "sha512-99cOvVJ3l4X0UJuTSa6qE87JriREnnWIsi3xo1/n7RoqFxnfi8YPh4SfJJyysvHcT18X4EfcTNde9ancMBVu6A==", + "requires": { + "@tonaljs/collection": "^4.6.2", + "@tonaljs/midi": "^4.6.5" + } + }, + "@tonaljs/roman-numeral": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/roman-numeral/-/roman-numeral-4.6.5.tgz", + "integrity": "sha512-bWYQNZWKmYDDcmbQQNwcWAHfTWanpzmvI0wplrMnGd4x0op5etwUEv+Yzjg0B1ef+E+zcU02Sl0WwRJhaDK3hg==", + "requires": { + "@tonaljs/core": "^4.6.5" + } + }, + "@tonaljs/scale": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/scale/-/scale-4.6.5.tgz", + "integrity": "sha512-isYDamelOBtcd5bEnJ8QV0Js7jKRwZ0FlFVE/+bUN3wsyo9u6KLL5gMyfH9RKdx74m8lE13JXYTXgKqe+AOa4A==", + "requires": { + "@tonaljs/chord-type": "^4.6.5", + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5", + "@tonaljs/note": "^4.6.5", + "@tonaljs/pcset": "^4.6.5", + "@tonaljs/scale-type": "^4.6.5" + } + }, + "@tonaljs/scale-type": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/scale-type/-/scale-type-4.6.5.tgz", + "integrity": "sha512-rwcDOYf2UifjLJhmuQ8f8bJSeOCMDQJ1lB7lzlqdFxes03OeQhdOEfrT0nPtW8BhBEvq4GMM2NA6CLxX8MTwOQ==", + "requires": { + "@tonaljs/core": "^4.6.5", + "@tonaljs/pcset": "^4.6.5" + } + }, + "@tonaljs/time-signature": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/@tonaljs/time-signature/-/time-signature-4.6.2.tgz", + "integrity": "sha512-OlZY4gdLd21WpMeAI1nS9E9zWcYU6oAzh6ptAUndqmVnFIrIWIWKCkWapdFx8dWdqrX8jqya3m4T33wmeo7w5Q==" + }, + "@tonaljs/tonal": { + "version": "4.6.5", + "resolved": "https://registry.npmjs.org/@tonaljs/tonal/-/tonal-4.6.5.tgz", + "integrity": "sha512-lmsWinI9dy7nQyzCEgDVeVAwJtsk4ey05cJZd6oa4QVuSFD+CR8ebaEiwT4/Na+W0kHrKicT3h0uYc2PJIvx5Q==", + "requires": { + "@tonaljs/abc-notation": "^4.6.5", + "@tonaljs/array": "^4.6.5", + "@tonaljs/chord": "^4.6.5", + "@tonaljs/chord-type": "^4.6.5", + "@tonaljs/collection": "^4.6.2", + "@tonaljs/core": "^4.6.5", + "@tonaljs/duration-value": "^4.6.2", + "@tonaljs/interval": "^4.6.5", + "@tonaljs/key": "^4.6.5", + "@tonaljs/midi": "^4.6.5", + "@tonaljs/mode": "^4.6.5", + "@tonaljs/note": "^4.6.5", + "@tonaljs/pcset": "^4.6.5", + "@tonaljs/progression": "^4.6.5", + "@tonaljs/range": "^4.6.5", + "@tonaljs/roman-numeral": "^4.6.5", + "@tonaljs/scale": "^4.6.5", + "@tonaljs/scale-type": "^4.6.5", + "@tonaljs/time-signature": "^4.6.2" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -13145,12 +13634,6 @@ "integrity": "sha512-PQ+NKpAobImfMprYQtc4Egmyi29bidRGEX0kKjCU5uuW09s0Cthwqhfy7mLkwcB4VcgacE5L/ZjruD/kOPCUUw==", "dev": true }, - "pegjs": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", - "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=", - "dev": true - }, "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -14481,6 +14964,108 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, + "tonal": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal/-/tonal-2.2.2.tgz", + "integrity": "sha512-Ze2bQc6KhAf3FKM9HzEsQ4z8hZh4WYCOsCrryONqf/THGOrOpL9Cc8Uc0dq0OA2yK2JbD5FhZckEXNYyD9946A==", + "requires": { + "tonal-array": "^2.2.2", + "tonal-chord": "^2.2.2", + "tonal-dictionary": "^2.2.2", + "tonal-distance": "^2.2.2", + "tonal-interval": "^2.2.2", + "tonal-key": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-pcset": "^2.2.2", + "tonal-scale": "^2.2.2" + } + }, + "tonal-array": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-array/-/tonal-array-2.2.2.tgz", + "integrity": "sha512-h6YIq20L0EEU4EsDoKHAjl5kD2EQn467VfV79QHAuybvNCJpqqRNsQ3QNvoQyir1BgDXaDUIN9FEmQJNiaaCKA==", + "requires": { + "tonal-note": "^2.2.2" + } + }, + "tonal-chord": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-chord/-/tonal-chord-2.2.2.tgz", + "integrity": "sha512-gOIXapi6Gx3ISRKdEJKEQjhDBiwjhaalyWSrN5rijGrSyyFFNZ+EVOfzcqLtnVAF9BgeO9Ca0eXCor3XpHdEJg==", + "requires": { + "tonal-dictionary": "^2.2.2", + "tonal-distance": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-pcset": "^2.2.2" + } + }, + "tonal-dictionary": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-dictionary/-/tonal-dictionary-2.2.2.tgz", + "integrity": "sha512-283ppJl/0lohhlVPMI6t5C6XwaP5Wx0egu9qfG9TLCT2tn4pRwYpXkzGufd9icvkJTgOylOum3+RxWmywUIPIg==", + "requires": { + "tonal-array": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-pcset": "^2.2.2" + } + }, + "tonal-distance": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-2.2.2.tgz", + "integrity": "sha512-ktA6OapCxaetXJb/JuXD5QwfyB7/G3y3ONby7Kkbezyffc57cnNfjdhlTR9XBR7eSFIY/J1KuhLwMx/qrffT4g==", + "requires": { + "tonal-interval": "^2.2.2", + "tonal-note": "^2.2.2" + } + }, + "tonal-interval": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-2.2.2.tgz", + "integrity": "sha512-lrtDU8lH5IAX7YE63OhGGDRpVb4OoGxaN0wDu5XC3sUhXBwjSgNYpHY2D9JI2aWQ/Er9jhQbnw9b0ffkLy34+Q==" + }, + "tonal-key": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-key/-/tonal-key-2.2.2.tgz", + "integrity": "sha512-KIc0b8yPl2ATDxF/65P52tIIempNsAQrug0idpD0zFvs5F5cb1hp7Rh7JJ4gECwC/6a3Hgdd1jomI+TnJ7K98w==", + "requires": { + "tonal-array": "^2.2.2", + "tonal-distance": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-roman-numeral": "^2.2.2" + } + }, + "tonal-note": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-note/-/tonal-note-2.2.2.tgz", + "integrity": "sha512-RNK3Nb8PxBEW9yYGStcoczgE8bCYFZ5zfLvYJjvuzLWiwTQmqWOhTzONVobVCGFZ/jgDNwpBEKe/bngL3g3Xfw==" + }, + "tonal-pcset": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-pcset/-/tonal-pcset-2.2.2.tgz", + "integrity": "sha512-PSqhkxzckO6J27W0GxawHYln4wvfDJ7puDmccksyFOBo97UhLnpxiyvBekhiYpkuaMtoZLQC/KALAkEj7lcb+A==", + "requires": { + "tonal-array": "^2.2.2", + "tonal-interval": "^2.2.2", + "tonal-note": "^2.2.2" + } + }, + "tonal-roman-numeral": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-roman-numeral/-/tonal-roman-numeral-2.2.2.tgz", + "integrity": "sha512-+auQNObpW3OvsSqlo+Cc+0otrlEhtbEgpzkPoKbTtkCva0P9oSkSz0OZ9fI73KQM5MsBs1XbB+olxppWkzYTFw==" + }, + "tonal-scale": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/tonal-scale/-/tonal-scale-2.2.2.tgz", + "integrity": "sha512-tDb3YCoTF50XOXq9kNhGB1JkInk7qAGN6GQnP/3xkGxkreFFRZyI58jfHlmWf/AH4+IKb/exsOmL6G8Ok/PCRw==", + "requires": { + "tonal-array": "^2.2.2", + "tonal-dictionary": "^2.2.2", + "tonal-distance": "^2.2.2", + "tonal-note": "^2.2.2", + "tonal-pcset": "^2.2.2" + } + }, "tone": { "version": "14.7.77", "resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz", diff --git a/repl/package.json b/repl/package.json index e6c8dc39..e5d71892 100644 --- a/repl/package.json +++ b/repl/package.json @@ -8,6 +8,7 @@ "peggy": "peggy -o krill-parser.js --format es ./krill.pegjs" }, "dependencies": { + "@tonaljs/tonal": "^4.6.5", "react": "^17.0.2", "react-dom": "^17.0.2", "tone": "^14.7.77" diff --git a/repl/src/App.tsx b/repl/src/App.tsx index 0ad4653e..ee032037 100644 --- a/repl/src/App.tsx +++ b/repl/src/App.tsx @@ -6,12 +6,12 @@ import * as Tone from 'tone'; import useCycle from './useCycle'; import type { Hap, Pattern } from './types'; import * as tunes from './tunes'; -import _mini from './mini'; +import * as krill from './parse'; -const { tetris, tetrisMini } = tunes; +const { tetris, tetrisMini, tetrisHaskell } = tunes; const { sequence, pure, reify, slowcat, fastcat, cat, stack, silence } = strudel; // make available to eval -const mini = _mini; // for eval (direct import wont work somehow) +const { mini, h } = krill; // for eval (direct import wont work somehow) const parse = (code: string): Pattern => eval(code); const synth = new Tone.PolySynth().toDestination(); @@ -23,7 +23,7 @@ synth.set({ }); function App() { - const [code, setCode] = useState(tetrisMini); + const [code, setCode] = useState(tetrisHaskell); const [log, setLog] = useState(''); const logBox = useRef(); const [error, setError] = useState(); diff --git a/repl/src/mini.ts b/repl/src/mini.ts deleted file mode 100644 index 40e31b8a..00000000 --- a/repl/src/mini.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as krill from '../krill-parser'; -import * as strudel from '../../strudel.mjs'; - -const { sequence, stack, silence } = strudel; - -export function patternifyAST(ast: any): any { - switch (ast.type_) { - case 'pattern': - if (ast.arguments_.alignment === 'v') { - return stack(...ast.source_.map(patternifyAST)); - } - return sequence(...ast.source_.map(patternifyAST)); - case 'element': - if (ast.source_ === '~') { - return silence; - } - if (typeof ast.source_ !== 'object') { - return ast.source_; - } - return patternifyAST(ast.source_); - } - // *3 => options_.operator.arguments_.amount = 1/3 -} -/* export default (str: string) => patternifyAST(krill.parse(`"${str}"`)); */ - -export default (...strings: string[]) => { - const pattern = sequence( - ...strings.map((str) => { - const ast = krill.parse(`"${str}"`); - // console.log('ast', ast); - return patternifyAST(ast); - }) - ); - // console.log('mini pattern', pattern); - return pattern; -}; - -/* -TODO: -export interface Arguments { - alignment: string; -} - -export interface ElementStub { - type_: string; - source_: string; - options_?: any; -} - -export interface PatternStub { - type_: string; // pattern - arguments_: Arguments; - source_: ElementStub[]; -} - */ diff --git a/repl/src/parse.d.ts b/repl/src/parse.d.ts new file mode 100644 index 00000000..be24374b --- /dev/null +++ b/repl/src/parse.d.ts @@ -0,0 +1,18 @@ +/* +TODO: +export interface Arguments { + alignment: string; +} + +export interface ElementStub { + type_: string; + source_: string; + options_?: any; +} + +export interface PatternStub { + type_: string; // pattern + arguments_: Arguments; + source_: ElementStub[]; +} + */ diff --git a/repl/src/parse.ts b/repl/src/parse.ts new file mode 100644 index 00000000..64228006 --- /dev/null +++ b/repl/src/parse.ts @@ -0,0 +1,101 @@ +import * as krill from '../krill-parser'; +import * as strudel from '../../strudel.mjs'; +import { Scale, Note, Interval } from '@tonaljs/tonal'; + +const { sequence, stack, silence, Fraction, pure } = strudel; + +function reify(thing: any) { + if (thing?.constructor?.name === 'Pattern') { + return thing; + } + return pure(thing); +} + +const applyOptions = (parent: any) => (pat: any, i: number) => { + const ast = parent.source_[i]; + const options = ast.options_; + const operator = options?.operator; + if (operator) { + switch (operator.type_) { + case 'stretch': + const speed = new Fraction(operator.arguments_.amount).inverse().valueOf(); + return reify(pat).fast(speed); + // case 'fixed-step': "%" // https://github.com/Mdashdotdashn/krill#patterns---a-more-traditional-approach + } + console.warn(`operator "${operator.type_}" not implemented`); + } + // TODO: options.weight = "@" + // TODO: bjorklund e.g. "c3(5,8)" + 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` + ); + } + return pat; +}; + +export function patternifyAST(ast: any): any { + switch (ast.type_) { + case 'pattern': + const children = ast.source_.map(patternifyAST).map(applyOptions(ast)); + if (ast.arguments_.alignment === 'v') { + return stack(...children); + } + return sequence(...children); + case 'element': + if (ast.source_ === '~') { + return silence; + } + if (typeof ast.source_ !== 'object') { + return ast.source_; + } + return patternifyAST(ast.source_); + case 'stretch': + return patternifyAST(ast.source_).slow(ast.arguments_.amount); + case 'scale': + let [tonic, scale] = Scale.tokenize(ast.arguments_.scale); + const intervals = Scale.get(scale).intervals; + const pattern = patternifyAST(ast.source_); + tonic = tonic || 'C4'; + // console.log('scale', ast, pattern, tonic, scale); + console.log('tonic', tonic); + return pattern.fmap((step: any) => { + step = Number(step); + if (isNaN(step)) { + console.warn(`scale step "${step}" not a number`); + return step; + } + const octaves = Math.floor(step / intervals.length); + const mod = (n: number, m: number): number => (n < 0 ? mod(n + m, m) : n % m); + const index = mod(step, intervals.length); // % with negative numbers. e.g. -1 % 3 = 2 + const interval = Interval.add(intervals[index], Interval.fromSemitones(octaves * 12)); + return Note.transpose(tonic, interval || '1P'); + }); + /* case 'struct': + // TODO: + return silence; */ + default: + console.warn(`node type "${ast.type_}" not implemented -> returning silence`); + return silence; + } +} + +// mini notation only (wraps in "") +export const mini = (...strings: string[]) => { + const pattern = sequence( + ...strings.map((str) => { + const ast = krill.parse(`"${str}"`); + // console.log('ast', ast); + return patternifyAST(ast); + }) + ); + return pattern; +}; + +// includes haskell style (raw krill parsing) +export const h = (string: string) => { + const ast = krill.parse(string); + console.log('ast', ast); + return patternifyAST(ast); +}; diff --git a/repl/src/tunes.ts b/repl/src/tunes.ts index 5c5df672..2f195d54 100644 --- a/repl/src/tunes.ts +++ b/repl/src/tunes.ts @@ -55,16 +55,38 @@ export const tetrisMini = `mini(\`[[e5 [b4 c5] d5 [c5 b4]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]], -[[e2 e3 e2 e3 e2 e3 e2 e3] -[a2 a3 a2 a3 a2 a3 a2 a3] -[g#2 g#3 g#2 g#3 e2 e3 e2 e3] +[[e2 e3]*4] +[[a2 a3]*4] +[[g#2 g#3]*2 [e2 e3]*2] [a2 a3 a2 a3 a2 a3 b1 c2] -[d2 d3 d2 d3 d2 d3 d2 d3] -[c2 c3 c2 c3 c2 c3 c2 c3] -[b1 b2 b1 b2 e2 e3 e2 e3] -[a1 a2 a1 a2 a1 a2 a1 a2]]\`)._slow(16); +[[d2 d3]*4] +[[c2 c3]*4] +[[b1 b2]*2 [e2 e3]*2] +[[a1 a2]*4]\`)._slow(16); `; +export const tetrisHaskell = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]] +[a4 [a4 c5] e5 [d5 c5]] +[b4 [~ c5] d5 e5] +[c5 a4 a4 ~] +[[~ d5] [~ f5] a5 [g5 f5]] +[e5 [~ c5] e5 [d5 c5]] +[b4 [b4 c5] d5 e5] +[c5 a4 a4 ~]], +[[e2 e3]*4] +[[a2 a3]*4] +[[g#2 g#3]*2 [e2 e3]*2] +[a2 a3 a2 a3 a2 a3 b1 c2] +[[d2 d3]*4] +[[c2 c3]*4] +[[b1 b2]*2 [e2 e3]*2] +[[a1 a2]*4]"\`) +`; + +/* +export const tetrisHaskell = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]] [a4 [a4 c5] e5 [d5 c5]] [b4 [~ c5] d5 e5] [c5 a4 a4 ~] [[~ d5] [~ f5] a5 [g5 f5]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]], [[e2 e3]*4] [[a2 a3]*4] [[g#2 g#3]*2 [e2 e3]*2] [a2 a3 a2 a3 a2 a3 b1 c2] [[d2 d3]*4] [[c2 c3]*4] [[b1 b2]*2 [e2 e3]*2] [[a1 a2]*4]"\`)`; +*/ + // "sequence('c3', 'eb3', sequence('g3', 'f3'))" // /* `sequence( stack('c4','eb4','g4'), diff --git a/repl/src/types.d.ts b/repl/src/types.d.ts index 6854f74e..8252d201 100644 --- a/repl/src/types.d.ts +++ b/repl/src/types.d.ts @@ -19,4 +19,5 @@ export declare interface Hap { } export declare interface Pattern { query: (span: TimeSpan) => Hap[]; + fmap: (v: T) => T; } From b91eeb72f0d8670b296818911a566333788a9173 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 21:00:29 +0100 Subject: [PATCH 10/21] build --- docs/_snowpack/pkg/@tonaljs/tonal.js | 1722 ++++++++++++++++++++++++++ docs/_snowpack/pkg/import-map.json | 1 + docs/dist/App.js | 8 +- docs/dist/mini.js | 27 - docs/dist/parse.js | 81 ++ docs/dist/tunes.js | 31 +- 6 files changed, 1832 insertions(+), 38 deletions(-) create mode 100644 docs/_snowpack/pkg/@tonaljs/tonal.js delete mode 100644 docs/dist/mini.js create mode 100644 docs/dist/parse.js diff --git a/docs/_snowpack/pkg/@tonaljs/tonal.js b/docs/_snowpack/pkg/@tonaljs/tonal.js new file mode 100644 index 00000000..c8186d9b --- /dev/null +++ b/docs/_snowpack/pkg/@tonaljs/tonal.js @@ -0,0 +1,1722 @@ +/** + * Fill a string with a repeated character + * + * @param character + * @param repetition + */ +const fillStr = (s, n) => Array(Math.abs(n) + 1).join(s); +function deprecate(original, alternative, fn) { + return function (...args) { + // tslint:disable-next-line + console.warn(`${original} is deprecated. Use ${alternative}.`); + return fn.apply(this, args); + }; +} + +function isNamed(src) { + return src !== null && typeof src === "object" && typeof src.name === "string" + ? true + : false; +} + +function isPitch(pitch) { + return pitch !== null && + typeof pitch === "object" && + typeof pitch.step === "number" && + typeof pitch.alt === "number" + ? true + : false; +} +// The number of fifths of [C, D, E, F, G, A, B] +const FIFTHS = [0, 2, 4, -1, 1, 3, 5]; +// The number of octaves it span each step +const STEPS_TO_OCTS = FIFTHS.map((fifths) => Math.floor((fifths * 7) / 12)); +function encode(pitch) { + const { step, alt, oct, dir = 1 } = pitch; + const f = FIFTHS[step] + 7 * alt; + if (oct === undefined) { + return [dir * f]; + } + const o = oct - STEPS_TO_OCTS[step] - 4 * alt; + return [dir * f, dir * o]; +} +// We need to get the steps from fifths +// Fifths for CDEFGAB are [ 0, 2, 4, -1, 1, 3, 5 ] +// We add 1 to fifths to avoid negative numbers, so: +// for ["F", "C", "G", "D", "A", "E", "B"] we have: +const FIFTHS_TO_STEPS = [3, 0, 4, 1, 5, 2, 6]; +function decode(coord) { + const [f, o, dir] = coord; + const step = FIFTHS_TO_STEPS[unaltered(f)]; + const alt = Math.floor((f + 1) / 7); + if (o === undefined) { + return { step, alt, dir }; + } + const oct = o + 4 * alt + STEPS_TO_OCTS[step]; + return { step, alt, oct, dir }; +} +// Return the number of fifths as if it were unaltered +function unaltered(f) { + const i = (f + 1) % 7; + return i < 0 ? 7 + i : i; +} + +const NoNote = { empty: true, name: "", pc: "", acc: "" }; +const cache$1 = new Map(); +const stepToLetter = (step) => "CDEFGAB".charAt(step); +const altToAcc = (alt) => alt < 0 ? fillStr("b", -alt) : fillStr("#", alt); +const accToAlt = (acc) => acc[0] === "b" ? -acc.length : acc.length; +/** + * Given a note literal (a note name or a note object), returns the Note object + * @example + * note('Bb4') // => { name: "Bb4", midi: 70, chroma: 10, ... } + */ +function note(src) { + const cached = cache$1.get(src); + if (cached) { + return cached; + } + const value = typeof src === "string" + ? parse$1(src) + : isPitch(src) + ? note(pitchName$1(src)) + : isNamed(src) + ? note(src.name) + : NoNote; + cache$1.set(src, value); + return value; +} +const REGEX$1 = /^([a-gA-G]?)(#{1,}|b{1,}|x{1,}|)(-?\d*)\s*(.*)$/; +/** + * @private + */ +function tokenizeNote(str) { + const m = REGEX$1.exec(str); + return [m[1].toUpperCase(), m[2].replace(/x/g, "##"), m[3], m[4]]; +} +/** + * @private + */ +function coordToNote(noteCoord) { + return note(decode(noteCoord)); +} +const mod = (n, m) => ((n % m) + m) % m; +const SEMI = [0, 2, 4, 5, 7, 9, 11]; +function parse$1(noteName) { + const tokens = tokenizeNote(noteName); + if (tokens[0] === "" || tokens[3] !== "") { + return NoNote; + } + const letter = tokens[0]; + const acc = tokens[1]; + const octStr = tokens[2]; + const step = (letter.charCodeAt(0) + 3) % 7; + const alt = accToAlt(acc); + const oct = octStr.length ? +octStr : undefined; + const coord = encode({ step, alt, oct }); + const name = letter + acc + octStr; + const pc = letter + acc; + const chroma = (SEMI[step] + alt + 120) % 12; + const height = oct === undefined + ? mod(SEMI[step] + alt, 12) - 12 * 99 + : SEMI[step] + alt + 12 * (oct + 1); + const midi = height >= 0 && height <= 127 ? height : null; + const freq = oct === undefined ? null : Math.pow(2, (height - 69) / 12) * 440; + return { + empty: false, + acc, + alt, + chroma, + coord, + freq, + height, + letter, + midi, + name, + oct, + pc, + step, + }; +} +function pitchName$1(props) { + const { step, alt, oct } = props; + const letter = stepToLetter(step); + if (!letter) { + return ""; + } + const pc = letter + altToAcc(alt); + return oct || oct === 0 ? pc + oct : pc; +} + +const NoInterval = { empty: true, name: "", acc: "" }; +// shorthand tonal notation (with quality after number) +const INTERVAL_TONAL_REGEX = "([-+]?\\d+)(d{1,4}|m|M|P|A{1,4})"; +// standard shorthand notation (with quality before number) +const INTERVAL_SHORTHAND_REGEX = "(AA|A|P|M|m|d|dd)([-+]?\\d+)"; +const REGEX = new RegExp("^" + INTERVAL_TONAL_REGEX + "|" + INTERVAL_SHORTHAND_REGEX + "$"); +/** + * @private + */ +function tokenizeInterval(str) { + const m = REGEX.exec(`${str}`); + if (m === null) { + return ["", ""]; + } + return m[1] ? [m[1], m[2]] : [m[4], m[3]]; +} +const cache = {}; +/** + * Get interval properties. It returns an object with: + * + * - name: the interval name + * - num: the interval number + * - type: 'perfectable' or 'majorable' + * - q: the interval quality (d, m, M, A) + * - dir: interval direction (1 ascending, -1 descending) + * - simple: the simplified number + * - semitones: the size in semitones + * - chroma: the interval chroma + * + * @param {string} interval - the interval name + * @return {Object} the interval properties + * + * @example + * import { interval } from '@tonaljs/core' + * interval('P5').semitones // => 7 + * interval('m3').type // => 'majorable' + */ +function interval(src) { + return typeof src === "string" + ? cache[src] || (cache[src] = parse(src)) + : isPitch(src) + ? interval(pitchName(src)) + : isNamed(src) + ? interval(src.name) + : NoInterval; +} +const SIZES = [0, 2, 4, 5, 7, 9, 11]; +const TYPES = "PMMPPMM"; +function parse(str) { + const tokens = tokenizeInterval(str); + if (tokens[0] === "") { + return NoInterval; + } + const num = +tokens[0]; + const q = tokens[1]; + const step = (Math.abs(num) - 1) % 7; + const t = TYPES[step]; + if (t === "M" && q === "P") { + return NoInterval; + } + const type = t === "M" ? "majorable" : "perfectable"; + const name = "" + num + q; + const dir = num < 0 ? -1 : 1; + const simple = num === 8 || num === -8 ? num : dir * (step + 1); + const alt = qToAlt(type, q); + const oct = Math.floor((Math.abs(num) - 1) / 7); + const semitones = dir * (SIZES[step] + alt + 12 * oct); + const chroma = (((dir * (SIZES[step] + alt)) % 12) + 12) % 12; + const coord = encode({ step, alt, oct, dir }); + return { + empty: false, + name, + num, + q, + step, + alt, + dir, + type, + simple, + semitones, + chroma, + coord, + oct, + }; +} +/** + * @private + * + * forceDescending is used in the case of unison (#243) + */ +function coordToInterval(coord, forceDescending) { + const [f, o = 0] = coord; + const isDescending = f * 7 + o * 12 < 0; + const ivl = forceDescending || isDescending ? [-f, -o, -1] : [f, o, 1]; + return interval(decode(ivl)); +} +function qToAlt(type, q) { + return (q === "M" && type === "majorable") || + (q === "P" && type === "perfectable") + ? 0 + : q === "m" && type === "majorable" + ? -1 + : /^A+$/.test(q) + ? q.length + : /^d+$/.test(q) + ? -1 * (type === "perfectable" ? q.length : q.length + 1) + : 0; +} +// return the interval name of a pitch +function pitchName(props) { + const { step, alt, oct = 0, dir } = props; + if (!dir) { + return ""; + } + const calcNum = step + 1 + 7 * oct; + // this is an edge case: descending pitch class unison (see #243) + const num = calcNum === 0 ? step + 1 : calcNum; + const d = dir < 0 ? "-" : ""; + const type = TYPES[step] === "M" ? "majorable" : "perfectable"; + const name = d + num + altToQ(type, alt); + return name; +} +function altToQ(type, alt) { + if (alt === 0) { + return type === "majorable" ? "M" : "P"; + } + else if (alt === -1 && type === "majorable") { + return "m"; + } + else if (alt > 0) { + return fillStr("A", alt); + } + else { + return fillStr("d", type === "perfectable" ? alt : alt + 1); + } +} + +/** + * Transpose a note by an interval. + * + * @param {string} note - the note or note name + * @param {string} interval - the interval or interval name + * @return {string} the transposed note name or empty string if not valid notes + * @example + * import { tranpose } from "@tonaljs/core" + * transpose("d3", "3M") // => "F#3" + * transpose("D", "3M") // => "F#" + * ["C", "D", "E", "F", "G"].map(pc => transpose(pc, "M3)) // => ["E", "F#", "G#", "A", "B"] + */ +function transpose(noteName, intervalName) { + const note$1 = note(noteName); + const interval$1 = interval(intervalName); + if (note$1.empty || interval$1.empty) { + return ""; + } + const noteCoord = note$1.coord; + const intervalCoord = interval$1.coord; + const tr = noteCoord.length === 1 + ? [noteCoord[0] + intervalCoord[0]] + : [noteCoord[0] + intervalCoord[0], noteCoord[1] + intervalCoord[1]]; + return coordToNote(tr).name; +} +/** + * Find the interval distance between two notes or coord classes. + * + * To find distance between coord classes, both notes must be coord classes and + * the interval is always ascending + * + * @param {Note|string} from - the note or note name to calculate distance from + * @param {Note|string} to - the note or note name to calculate distance to + * @return {string} the interval name or empty string if not valid notes + * + */ +function distance(fromNote, toNote) { + const from = note(fromNote); + const to = note(toNote); + if (from.empty || to.empty) { + return ""; + } + const fcoord = from.coord; + const tcoord = to.coord; + const fifths = tcoord[0] - fcoord[0]; + const octs = fcoord.length === 2 && tcoord.length === 2 + ? tcoord[1] - fcoord[1] + : -Math.floor((fifths * 7) / 12); + // If it's unison and not pitch class, it can be descending interval (#243) + const forceDescending = to.height === from.height && + to.midi !== null && + from.midi !== null && + from.step > to.step; + return coordToInterval([fifths, octs], forceDescending).name; +} + +// ascending range +function ascR(b, n) { + const a = []; + // tslint:disable-next-line:curly + for (; n--; a[n] = n + b) + ; + return a; +} +// descending range +function descR(b, n) { + const a = []; + // tslint:disable-next-line:curly + for (; n--; a[n] = b - n) + ; + return a; +} +/** + * Creates a numeric range + * + * @param {number} from + * @param {number} to + * @return {Array} + * + * @example + * range(-2, 2) // => [-2, -1, 0, 1, 2] + * range(2, -2) // => [2, 1, 0, -1, -2] + */ +function range(from, to) { + return from < to ? ascR(from, to - from + 1) : descR(from, from - to + 1); +} +/** + * Rotates a list a number of times. It"s completly agnostic about the + * contents of the list. + * + * @param {Integer} times - the number of rotations + * @param {Array} collection + * @return {Array} the rotated collection + * + * @example + * rotate(1, [1, 2, 3]) // => [2, 3, 1] + */ +function rotate(times, arr) { + const len = arr.length; + const n = ((times % len) + len) % len; + return arr.slice(n, len).concat(arr.slice(0, n)); +} +/** + * Return a copy of the collection with the null values removed + * @function + * @param {Array} collection + * @return {Array} + * + * @example + * compact(["a", "b", null, "c"]) // => ["a", "b", "c"] + */ +function compact(arr) { + return arr.filter((n) => n === 0 || n); +} + +const EmptyPcset = { + empty: true, + name: "", + setNum: 0, + chroma: "000000000000", + normalized: "000000000000", + intervals: [], +}; +// UTILITIES +const setNumToChroma = (num) => Number(num).toString(2); +const chromaToNumber = (chroma) => parseInt(chroma, 2); +const REGEX$2 = /^[01]{12}$/; +function isChroma(set) { + return REGEX$2.test(set); +} +const isPcsetNum = (set) => typeof set === "number" && set >= 0 && set <= 4095; +const isPcset = (set) => set && isChroma(set.chroma); +const cache$2 = { [EmptyPcset.chroma]: EmptyPcset }; +/** + * Get the pitch class set of a collection of notes or set number or chroma + */ +function get(src) { + const chroma = isChroma(src) + ? src + : isPcsetNum(src) + ? setNumToChroma(src) + : Array.isArray(src) + ? listToChroma(src) + : isPcset(src) + ? src.chroma + : EmptyPcset.chroma; + return (cache$2[chroma] = cache$2[chroma] || chromaToPcset(chroma)); +} +const IVLS = [ + "1P", + "2m", + "2M", + "3m", + "3M", + "4P", + "5d", + "5P", + "6m", + "6M", + "7m", + "7M", +]; +/** + * @private + * Get the intervals of a pcset *starting from C* + * @param {Set} set - the pitch class set + * @return {IntervalName[]} an array of interval names or an empty array + * if not a valid pitch class set + */ +function chromaToIntervals(chroma) { + const intervals = []; + for (let i = 0; i < 12; i++) { + // tslint:disable-next-line:curly + if (chroma.charAt(i) === "1") + intervals.push(IVLS[i]); + } + return intervals; +} +/** + * Given a a list of notes or a pcset chroma, produce the rotations + * of the chroma discarding the ones that starts with "0" + * + * This is used, for example, to get all the modes of a scale. + * + * @param {Array|string} set - the list of notes or pitchChr of the set + * @param {boolean} normalize - (Optional, true by default) remove all + * the rotations that starts with "0" + * @return {Array} an array with all the modes of the chroma + * + * @example + * Pcset.modes(["C", "D", "E"]).map(Pcset.intervals) + */ +function modes(set, normalize = true) { + const pcs = get(set); + const binary = pcs.chroma.split(""); + return compact(binary.map((_, i) => { + const r = rotate(i, binary); + return normalize && r[0] === "0" ? null : r.join(""); + })); +} +/** + * Create a function that test if a collection of notes is a + * subset of a given set + * + * The function is curryfied. + * + * @param {PcsetChroma|NoteName[]} set - the superset to test against (chroma or + * list of notes) + * @return{function(PcsetChroma|NoteNames[]): boolean} a function accepting a set + * to test against (chroma or list of notes) + * @example + * const inCMajor = Pcset.isSubsetOf(["C", "E", "G"]) + * inCMajor(["e6", "c4"]) // => true + * inCMajor(["e6", "c4", "d3"]) // => false + */ +function isSubsetOf(set) { + const s = get(set).setNum; + return (notes) => { + const o = get(notes).setNum; + // tslint:disable-next-line: no-bitwise + return s && s !== o && (o & s) === o; + }; +} +/** + * Create a function that test if a collection of notes is a + * superset of a given set (it contains all notes and at least one more) + * + * @param {Set} set - an array of notes or a chroma set string to test against + * @return {(subset: Set): boolean} a function that given a set + * returns true if is a subset of the first one + * @example + * const extendsCMajor = Pcset.isSupersetOf(["C", "E", "G"]) + * extendsCMajor(["e6", "a", "c4", "g2"]) // => true + * extendsCMajor(["c6", "e4", "g3"]) // => false + */ +function isSupersetOf(set) { + const s = get(set).setNum; + return (notes) => { + const o = get(notes).setNum; + // tslint:disable-next-line: no-bitwise + return s && s !== o && (o | s) === o; + }; +} +//// PRIVATE //// +function chromaRotations(chroma) { + const binary = chroma.split(""); + return binary.map((_, i) => rotate(i, binary).join("")); +} +function chromaToPcset(chroma) { + const setNum = chromaToNumber(chroma); + const normalizedNum = chromaRotations(chroma) + .map(chromaToNumber) + .filter((n) => n >= 2048) + .sort()[0]; + const normalized = setNumToChroma(normalizedNum); + const intervals = chromaToIntervals(chroma); + return { + empty: false, + name: "", + setNum, + chroma, + normalized, + intervals, + }; +} +function listToChroma(set) { + if (set.length === 0) { + return EmptyPcset.chroma; + } + let pitch; + const binary = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + // tslint:disable-next-line:prefer-for-of + for (let i = 0; i < set.length; i++) { + pitch = note(set[i]); + // tslint:disable-next-line: curly + if (pitch.empty) + pitch = interval(set[i]); + // tslint:disable-next-line: curly + if (!pitch.empty) + binary[pitch.chroma] = 1; + } + return binary.join(""); +} + +/** + * @private + * Chord List + * Source: https://en.wikibooks.org/wiki/Music_Theory/Complete_List_of_Chord_Patterns + * Format: ["intervals", "full name", "abrv1 abrv2"] + */ +const CHORDS = [ + // ==Major== + ["1P 3M 5P", "major", "M ^ "], + ["1P 3M 5P 7M", "major seventh", "maj7 Δ ma7 M7 Maj7 ^7"], + ["1P 3M 5P 7M 9M", "major ninth", "maj9 Δ9 ^9"], + ["1P 3M 5P 7M 9M 13M", "major thirteenth", "maj13 Maj13 ^13"], + ["1P 3M 5P 6M", "sixth", "6 add6 add13 M6"], + ["1P 3M 5P 6M 9M", "sixth/ninth", "6/9 69 M69"], + ["1P 3M 6m 7M", "major seventh flat sixth", "M7b6 ^7b6"], + [ + "1P 3M 5P 7M 11A", + "major seventh sharp eleventh", + "maj#4 Δ#4 Δ#11 M7#11 ^7#11 maj7#11", + ], + // ==Minor== + // '''Normal''' + ["1P 3m 5P", "minor", "m min -"], + ["1P 3m 5P 7m", "minor seventh", "m7 min7 mi7 -7"], + [ + "1P 3m 5P 7M", + "minor/major seventh", + "m/ma7 m/maj7 mM7 mMaj7 m/M7 -Δ7 mΔ -^7", + ], + ["1P 3m 5P 6M", "minor sixth", "m6 -6"], + ["1P 3m 5P 7m 9M", "minor ninth", "m9 -9"], + ["1P 3m 5P 7M 9M", "minor/major ninth", "mM9 mMaj9 -^9"], + ["1P 3m 5P 7m 9M 11P", "minor eleventh", "m11 -11"], + ["1P 3m 5P 7m 9M 13M", "minor thirteenth", "m13 -13"], + // '''Diminished''' + ["1P 3m 5d", "diminished", "dim ° o"], + ["1P 3m 5d 7d", "diminished seventh", "dim7 °7 o7"], + ["1P 3m 5d 7m", "half-diminished", "m7b5 ø -7b5 h7 h"], + // ==Dominant/Seventh== + // '''Normal''' + ["1P 3M 5P 7m", "dominant seventh", "7 dom"], + ["1P 3M 5P 7m 9M", "dominant ninth", "9"], + ["1P 3M 5P 7m 9M 13M", "dominant thirteenth", "13"], + ["1P 3M 5P 7m 11A", "lydian dominant seventh", "7#11 7#4"], + // '''Altered''' + ["1P 3M 5P 7m 9m", "dominant flat ninth", "7b9"], + ["1P 3M 5P 7m 9A", "dominant sharp ninth", "7#9"], + ["1P 3M 7m 9m", "altered", "alt7"], + // '''Suspended''' + ["1P 4P 5P", "suspended fourth", "sus4 sus"], + ["1P 2M 5P", "suspended second", "sus2"], + ["1P 4P 5P 7m", "suspended fourth seventh", "7sus4 7sus"], + ["1P 5P 7m 9M 11P", "eleventh", "11"], + [ + "1P 4P 5P 7m 9m", + "suspended fourth flat ninth", + "b9sus phryg 7b9sus 7b9sus4", + ], + // ==Other== + ["1P 5P", "fifth", "5"], + ["1P 3M 5A", "augmented", "aug + +5 ^#5"], + ["1P 3m 5A", "minor augmented", "m#5 -#5 m+"], + ["1P 3M 5A 7M", "augmented seventh", "maj7#5 maj7+5 +maj7 ^7#5"], + [ + "1P 3M 5P 7M 9M 11A", + "major sharp eleventh (lydian)", + "maj9#11 Δ9#11 ^9#11", + ], + // ==Legacy== + ["1P 2M 4P 5P", "", "sus24 sus4add9"], + ["1P 3M 5A 7M 9M", "", "maj9#5 Maj9#5"], + ["1P 3M 5A 7m", "", "7#5 +7 7+ 7aug aug7"], + ["1P 3M 5A 7m 9A", "", "7#5#9 7#9#5 7alt"], + ["1P 3M 5A 7m 9M", "", "9#5 9+"], + ["1P 3M 5A 7m 9M 11A", "", "9#5#11"], + ["1P 3M 5A 7m 9m", "", "7#5b9 7b9#5"], + ["1P 3M 5A 7m 9m 11A", "", "7#5b9#11"], + ["1P 3M 5A 9A", "", "+add#9"], + ["1P 3M 5A 9M", "", "M#5add9 +add9"], + ["1P 3M 5P 6M 11A", "", "M6#11 M6b5 6#11 6b5"], + ["1P 3M 5P 6M 7M 9M", "", "M7add13"], + ["1P 3M 5P 6M 9M 11A", "", "69#11"], + ["1P 3m 5P 6M 9M", "", "m69 -69"], + ["1P 3M 5P 6m 7m", "", "7b6"], + ["1P 3M 5P 7M 9A 11A", "", "maj7#9#11"], + ["1P 3M 5P 7M 9M 11A 13M", "", "M13#11 maj13#11 M13+4 M13#4"], + ["1P 3M 5P 7M 9m", "", "M7b9"], + ["1P 3M 5P 7m 11A 13m", "", "7#11b13 7b5b13"], + ["1P 3M 5P 7m 13M", "", "7add6 67 7add13"], + ["1P 3M 5P 7m 9A 11A", "", "7#9#11 7b5#9 7#9b5"], + ["1P 3M 5P 7m 9A 11A 13M", "", "13#9#11"], + ["1P 3M 5P 7m 9A 11A 13m", "", "7#9#11b13"], + ["1P 3M 5P 7m 9A 13M", "", "13#9"], + ["1P 3M 5P 7m 9A 13m", "", "7#9b13"], + ["1P 3M 5P 7m 9M 11A", "", "9#11 9+4 9#4"], + ["1P 3M 5P 7m 9M 11A 13M", "", "13#11 13+4 13#4"], + ["1P 3M 5P 7m 9M 11A 13m", "", "9#11b13 9b5b13"], + ["1P 3M 5P 7m 9m 11A", "", "7b9#11 7b5b9 7b9b5"], + ["1P 3M 5P 7m 9m 11A 13M", "", "13b9#11"], + ["1P 3M 5P 7m 9m 11A 13m", "", "7b9b13#11 7b9#11b13 7b5b9b13"], + ["1P 3M 5P 7m 9m 13M", "", "13b9"], + ["1P 3M 5P 7m 9m 13m", "", "7b9b13"], + ["1P 3M 5P 7m 9m 9A", "", "7b9#9"], + ["1P 3M 5P 9M", "", "Madd9 2 add9 add2"], + ["1P 3M 5P 9m", "", "Maddb9"], + ["1P 3M 5d", "", "Mb5"], + ["1P 3M 5d 6M 7m 9M", "", "13b5"], + ["1P 3M 5d 7M", "", "M7b5"], + ["1P 3M 5d 7M 9M", "", "M9b5"], + ["1P 3M 5d 7m", "", "7b5"], + ["1P 3M 5d 7m 9M", "", "9b5"], + ["1P 3M 7m", "", "7no5"], + ["1P 3M 7m 13m", "", "7b13"], + ["1P 3M 7m 9M", "", "9no5"], + ["1P 3M 7m 9M 13M", "", "13no5"], + ["1P 3M 7m 9M 13m", "", "9b13"], + ["1P 3m 4P 5P", "", "madd4"], + ["1P 3m 5P 6m 7M", "", "mMaj7b6"], + ["1P 3m 5P 6m 7M 9M", "", "mMaj9b6"], + ["1P 3m 5P 7m 11P", "", "m7add11 m7add4"], + ["1P 3m 5P 9M", "", "madd9"], + ["1P 3m 5d 6M 7M", "", "o7M7"], + ["1P 3m 5d 7M", "", "oM7"], + ["1P 3m 6m 7M", "", "mb6M7"], + ["1P 3m 6m 7m", "", "m7#5"], + ["1P 3m 6m 7m 9M", "", "m9#5"], + ["1P 3m 5A 7m 9M 11P", "", "m11A"], + ["1P 3m 6m 9m", "", "mb6b9"], + ["1P 2M 3m 5d 7m", "", "m9b5"], + ["1P 4P 5A 7M", "", "M7#5sus4"], + ["1P 4P 5A 7M 9M", "", "M9#5sus4"], + ["1P 4P 5A 7m", "", "7#5sus4"], + ["1P 4P 5P 7M", "", "M7sus4"], + ["1P 4P 5P 7M 9M", "", "M9sus4"], + ["1P 4P 5P 7m 9M", "", "9sus4 9sus"], + ["1P 4P 5P 7m 9M 13M", "", "13sus4 13sus"], + ["1P 4P 5P 7m 9m 13m", "", "7sus4b9b13 7b9b13sus4"], + ["1P 4P 7m 10m", "", "4 quartal"], + ["1P 5P 7m 9m 11P", "", "11b9"], +]; +let dictionary = []; +/** + * Return a list of all chord types + */ +function all() { + return dictionary.slice(); +} +/** + * Add a chord to the dictionary. + * @param intervals + * @param aliases + * @param [fullName] + */ +function add(intervals, aliases, fullName) { + const quality = getQuality(intervals); + const chord = { + ...get(intervals), + name: fullName || "", + quality, + intervals, + aliases, + }; + dictionary.push(chord); + chord.aliases.forEach((alias) => addAlias()); +} +function addAlias(chord, alias) { +} +function getQuality(intervals) { + const has = (interval) => intervals.indexOf(interval) !== -1; + return has("5A") + ? "Augmented" + : has("3M") + ? "Major" + : has("5d") + ? "Diminished" + : has("3m") + ? "Minor" + : "Unknown"; +} +CHORDS.forEach(([ivls, fullName, names]) => add(ivls.split(" "), names.split(" "), fullName)); +dictionary.sort((a, b) => a.setNum - b.setNum); + +// SCALES +// Format: ["intervals", "name", "alias1", "alias2", ...] +const SCALES = [ + // 5-note scales + ["1P 2M 3M 5P 6M", "major pentatonic", "pentatonic"], + ["1P 3M 4P 5P 7M", "ionian pentatonic"], + ["1P 3M 4P 5P 7m", "mixolydian pentatonic", "indian"], + ["1P 2M 4P 5P 6M", "ritusen"], + ["1P 2M 4P 5P 7m", "egyptian"], + ["1P 3M 4P 5d 7m", "neopolitan major pentatonic"], + ["1P 3m 4P 5P 6m", "vietnamese 1"], + ["1P 2m 3m 5P 6m", "pelog"], + ["1P 2m 4P 5P 6m", "kumoijoshi"], + ["1P 2M 3m 5P 6m", "hirajoshi"], + ["1P 2m 4P 5d 7m", "iwato"], + ["1P 2m 4P 5P 7m", "in-sen"], + ["1P 3M 4A 5P 7M", "lydian pentatonic", "chinese"], + ["1P 3m 4P 6m 7m", "malkos raga"], + ["1P 3m 4P 5d 7m", "locrian pentatonic", "minor seven flat five pentatonic"], + ["1P 3m 4P 5P 7m", "minor pentatonic", "vietnamese 2"], + ["1P 3m 4P 5P 6M", "minor six pentatonic"], + ["1P 2M 3m 5P 6M", "flat three pentatonic", "kumoi"], + ["1P 2M 3M 5P 6m", "flat six pentatonic"], + ["1P 2m 3M 5P 6M", "scriabin"], + ["1P 3M 5d 6m 7m", "whole tone pentatonic"], + ["1P 3M 4A 5A 7M", "lydian #5P pentatonic"], + ["1P 3M 4A 5P 7m", "lydian dominant pentatonic"], + ["1P 3m 4P 5P 7M", "minor #7M pentatonic"], + ["1P 3m 4d 5d 7m", "super locrian pentatonic"], + // 6-note scales + ["1P 2M 3m 4P 5P 7M", "minor hexatonic"], + ["1P 2A 3M 5P 5A 7M", "augmented"], + ["1P 2M 3m 3M 5P 6M", "major blues"], + ["1P 2M 4P 5P 6M 7m", "piongio"], + ["1P 2m 3M 4A 6M 7m", "prometheus neopolitan"], + ["1P 2M 3M 4A 6M 7m", "prometheus"], + ["1P 2m 3M 5d 6m 7m", "mystery #1"], + ["1P 2m 3M 4P 5A 6M", "six tone symmetric"], + ["1P 2M 3M 4A 5A 7m", "whole tone", "messiaen's mode #1"], + ["1P 2m 4P 4A 5P 7M", "messiaen's mode #5"], + ["1P 3m 4P 5d 5P 7m", "minor blues", "blues"], + // 7-note scales + ["1P 2M 3M 4P 5d 6m 7m", "locrian major", "arabian"], + ["1P 2m 3M 4A 5P 6m 7M", "double harmonic lydian"], + ["1P 2M 3m 4P 5P 6m 7M", "harmonic minor"], + [ + "1P 2m 2A 3M 4A 6m 7m", + "altered", + "super locrian", + "diminished whole tone", + "pomeroy", + ], + ["1P 2M 3m 4P 5d 6m 7m", "locrian #2", "half-diminished", "aeolian b5"], + [ + "1P 2M 3M 4P 5P 6m 7m", + "mixolydian b6", + "melodic minor fifth mode", + "hindu", + ], + ["1P 2M 3M 4A 5P 6M 7m", "lydian dominant", "lydian b7", "overtone"], + ["1P 2M 3M 4A 5P 6M 7M", "lydian"], + ["1P 2M 3M 4A 5A 6M 7M", "lydian augmented"], + [ + "1P 2m 3m 4P 5P 6M 7m", + "dorian b2", + "phrygian #6", + "melodic minor second mode", + ], + ["1P 2M 3m 4P 5P 6M 7M", "melodic minor"], + ["1P 2m 3m 4P 5d 6m 7m", "locrian"], + [ + "1P 2m 3m 4d 5d 6m 7d", + "ultralocrian", + "superlocrian bb7", + "superlocrian diminished", + ], + ["1P 2m 3m 4P 5d 6M 7m", "locrian 6", "locrian natural 6", "locrian sharp 6"], + ["1P 2A 3M 4P 5P 5A 7M", "augmented heptatonic"], + // Source https://en.wikipedia.org/wiki/Ukrainian_Dorian_scale + [ + "1P 2M 3m 4A 5P 6M 7m", + "dorian #4", + "ukrainian dorian", + "romanian minor", + "altered dorian", + ], + ["1P 2M 3m 4A 5P 6M 7M", "lydian diminished"], + ["1P 2m 3m 4P 5P 6m 7m", "phrygian"], + ["1P 2M 3M 4A 5A 7m 7M", "leading whole tone"], + ["1P 2M 3M 4A 5P 6m 7m", "lydian minor"], + ["1P 2m 3M 4P 5P 6m 7m", "phrygian dominant", "spanish", "phrygian major"], + ["1P 2m 3m 4P 5P 6m 7M", "balinese"], + ["1P 2m 3m 4P 5P 6M 7M", "neopolitan major"], + ["1P 2M 3m 4P 5P 6m 7m", "aeolian", "minor"], + ["1P 2M 3M 4P 5P 6m 7M", "harmonic major"], + ["1P 2m 3M 4P 5P 6m 7M", "double harmonic major", "gypsy"], + ["1P 2M 3m 4P 5P 6M 7m", "dorian"], + ["1P 2M 3m 4A 5P 6m 7M", "hungarian minor"], + ["1P 2A 3M 4A 5P 6M 7m", "hungarian major"], + ["1P 2m 3M 4P 5d 6M 7m", "oriental"], + ["1P 2m 3m 3M 4A 5P 7m", "flamenco"], + ["1P 2m 3m 4A 5P 6m 7M", "todi raga"], + ["1P 2M 3M 4P 5P 6M 7m", "mixolydian", "dominant"], + ["1P 2m 3M 4P 5d 6m 7M", "persian"], + ["1P 2M 3M 4P 5P 6M 7M", "major", "ionian"], + ["1P 2m 3M 5d 6m 7m 7M", "enigmatic"], + [ + "1P 2M 3M 4P 5A 6M 7M", + "major augmented", + "major #5", + "ionian augmented", + "ionian #5", + ], + ["1P 2A 3M 4A 5P 6M 7M", "lydian #9"], + // 8-note scales + ["1P 2m 2M 4P 4A 5P 6m 7M", "messiaen's mode #4"], + ["1P 2m 3M 4P 4A 5P 6m 7M", "purvi raga"], + ["1P 2m 3m 3M 4P 5P 6m 7m", "spanish heptatonic"], + ["1P 2M 3M 4P 5P 6M 7m 7M", "bebop"], + ["1P 2M 3m 3M 4P 5P 6M 7m", "bebop minor"], + ["1P 2M 3M 4P 5P 5A 6M 7M", "bebop major"], + ["1P 2m 3m 4P 5d 5P 6m 7m", "bebop locrian"], + ["1P 2M 3m 4P 5P 6m 7m 7M", "minor bebop"], + ["1P 2M 3m 4P 5d 6m 6M 7M", "diminished", "whole-half diminished"], + ["1P 2M 3M 4P 5d 5P 6M 7M", "ichikosucho"], + ["1P 2M 3m 4P 5P 6m 6M 7M", "minor six diminished"], + [ + "1P 2m 3m 3M 4A 5P 6M 7m", + "half-whole diminished", + "dominant diminished", + "messiaen's mode #2", + ], + ["1P 3m 3M 4P 5P 6M 7m 7M", "kafi raga"], + ["1P 2M 3M 4P 4A 5A 6A 7M", "messiaen's mode #6"], + // 9-note scales + ["1P 2M 3m 3M 4P 5d 5P 6M 7m", "composite blues"], + ["1P 2M 3m 3M 4A 5P 6m 7m 7M", "messiaen's mode #3"], + // 10-note scales + ["1P 2m 2M 3m 4P 4A 5P 6m 6M 7M", "messiaen's mode #7"], + // 12-note scales + ["1P 2m 2M 3m 3M 4P 5d 5P 6m 6M 7m 7M", "chromatic"], +]; + +const NoScaleType = { + ...EmptyPcset, + intervals: [], + aliases: [], +}; +let dictionary$1 = []; +let index = {}; +function names() { + return dictionary$1.map((scale) => scale.name); +} +/** + * Given a scale name or chroma, return the scale properties + * + * @param {string} type - scale name or pitch class set chroma + * @example + * import { get } from 'tonaljs/scale-type' + * get('major') // => { name: 'major', ... } + */ +function get$1(type) { + return index[type] || NoScaleType; +} +/** + * Return a list of all scale types + */ +function all$1() { + return dictionary$1.slice(); +} +/** + * Add a scale into dictionary + * @param intervals + * @param name + * @param aliases + */ +function add$1(intervals, name, aliases = []) { + const scale = { ...get(intervals), name, intervals, aliases }; + dictionary$1.push(scale); + index[scale.name] = scale; + index[scale.setNum] = scale; + index[scale.chroma] = scale; + scale.aliases.forEach((alias) => addAlias$1(scale, alias)); + return scale; +} +function addAlias$1(scale, alias) { + index[alias] = scale; +} +SCALES.forEach(([ivls, name, ...aliases]) => add$1(ivls.split(" "), name, aliases)); + +/** + * Get the natural list of names + */ +function names$1() { + return "1P 2M 3M 4P 5P 6m 7m".split(" "); +} +/** + * Get properties of an interval + * + * @function + * @example + * Interval.get('P4') // => {"alt": 0, "dir": 1, "name": "4P", "num": 4, "oct": 0, "q": "P", "semitones": 5, "simple": 4, "step": 3, "type": "perfectable"} + */ +const get$2 = interval; +/** + * Get name of an interval + * + * @function + * @example + * Interval.name('4P') // => "4P" + * Interval.name('P4') // => "4P" + * Interval.name('C4') // => "" + */ +const name = (name) => interval(name).name; +/** + * Get semitones of an interval + * @function + * @example + * Interval.semitones('P4') // => 5 + */ +const semitones = (name) => interval(name).semitones; +/** + * Get quality of an interval + * @function + * @example + * Interval.quality('P4') // => "P" + */ +const quality = (name) => interval(name).q; +/** + * Get number of an interval + * @function + * @example + * Interval.num('P4') // => 4 + */ +const num = (name) => interval(name).num; +/** + * Get the simplified version of an interval. + * + * @function + * @param {string} interval - the interval to simplify + * @return {string} the simplified interval + * + * @example + * Interval.simplify("9M") // => "2M" + * Interval.simplify("2M") // => "2M" + * Interval.simplify("-2M") // => "7m" + * ["8P", "9M", "10M", "11P", "12P", "13M", "14M", "15P"].map(Interval.simplify) + * // => [ "8P", "2M", "3M", "4P", "5P", "6M", "7M", "8P" ] + */ +function simplify(name) { + const i = interval(name); + return i.empty ? "" : i.simple + i.q; +} +/** + * Get the inversion (https://en.wikipedia.org/wiki/Inversion_(music)#Intervals) + * of an interval. + * + * @function + * @param {string} interval - the interval to invert in interval shorthand + * notation or interval array notation + * @return {string} the inverted interval + * + * @example + * Interval.invert("3m") // => "6M" + * Interval.invert("2M") // => "7m" + */ +function invert(name) { + const i = interval(name); + if (i.empty) { + return ""; + } + const step = (7 - i.step) % 7; + const alt = i.type === "perfectable" ? -i.alt : -(i.alt + 1); + return interval({ step, alt, oct: i.oct, dir: i.dir }).name; +} +// interval numbers +const IN = [1, 2, 2, 3, 3, 4, 5, 5, 6, 6, 7, 7]; +// interval qualities +const IQ = "P m M m M P d P m M m M".split(" "); +/** + * Get interval name from semitones number. Since there are several interval + * names for the same number, the name it's arbitrary, but deterministic. + * + * @param {Integer} num - the number of semitones (can be negative) + * @return {string} the interval name + * @example + * Interval.fromSemitones(7) // => "5P" + * Interval.fromSemitones(-7) // => "-5P" + */ +function fromSemitones(semitones) { + const d = semitones < 0 ? -1 : 1; + const n = Math.abs(semitones); + const c = n % 12; + const o = Math.floor(n / 12); + return d * (IN[c] + 7 * o) + IQ[c]; +} +/** + * Find interval between two notes + * + * @example + * Interval.distance("C4", "G4"); // => "5P" + */ +const distance$1 = distance; +/** + * Adds two intervals + * + * @function + * @param {string} interval1 + * @param {string} interval2 + * @return {string} the added interval name + * @example + * Interval.add("3m", "5P") // => "7m" + */ +const add$2 = combinator((a, b) => [a[0] + b[0], a[1] + b[1]]); +/** + * Returns a function that adds an interval + * + * @function + * @example + * ['1P', '2M', '3M'].map(Interval.addTo('5P')) // => ["5P", "6M", "7M"] + */ +const addTo = (interval) => (other) => add$2(interval, other); +/** + * Subtracts two intervals + * + * @function + * @param {string} minuendInterval + * @param {string} subtrahendInterval + * @return {string} the substracted interval name + * @example + * Interval.substract('5P', '3M') // => '3m' + * Interval.substract('3M', '5P') // => '-3m' + */ +const substract = combinator((a, b) => [a[0] - b[0], a[1] - b[1]]); +function transposeFifths(interval, fifths) { + const ivl = get$2(interval); + if (ivl.empty) + return ""; + const [nFifths, nOcts, dir] = ivl.coord; + return coordToInterval([nFifths + fifths, nOcts, dir]).name; +} +var index$1 = { + names: names$1, + get: get$2, + name, + num, + semitones, + quality, + fromSemitones, + distance: distance$1, + invert, + simplify, + add: add$2, + addTo, + substract, + transposeFifths, +}; +function combinator(fn) { + return (a, b) => { + const coordA = interval(a).coord; + const coordB = interval(b).coord; + if (coordA && coordB) { + const coord = fn(coordA, coordB); + return coordToInterval(coord).name; + } + }; +} + +const L2 = Math.log(2); +const L440 = Math.log(440); +/** + * Get the midi number from a frequency in hertz. The midi number can + * contain decimals (with two digits precission) + * + * @param {number} frequency + * @return {number} + * @example + * import { freqToMidi} from '@tonaljs/midi' + * freqToMidi(220)); //=> 57 + * freqToMidi(261.62)); //=> 60 + * freqToMidi(261)); //=> 59.96 + */ +function freqToMidi(freq) { + const v = (12 * (Math.log(freq) - L440)) / L2 + 69; + return Math.round(v * 100) / 100; +} +const SHARPS = "C C# D D# E F F# G G# A A# B".split(" "); +const FLATS = "C Db D Eb E F Gb G Ab A Bb B".split(" "); +/** + * Given a midi number, returns a note name. The altered notes will have + * flats unless explicitly set with the optional `useSharps` parameter. + * + * @function + * @param {number} midi - the midi note number + * @param {Object} options = default: `{ sharps: false, pitchClass: false }` + * @param {boolean} useSharps - (Optional) set to true to use sharps instead of flats + * @return {string} the note name + * @example + * import { midiToNoteName } from '@tonaljs/midi' + * midiToNoteName(61) // => "Db4" + * midiToNoteName(61, { pitchClass: true }) // => "Db" + * midiToNoteName(61, { sharps: true }) // => "C#4" + * midiToNoteName(61, { pitchClass: true, sharps: true }) // => "C#" + * // it rounds to nearest note + * midiToNoteName(61.7) // => "D4" + */ +function midiToNoteName(midi, options = {}) { + if (isNaN(midi) || midi === -Infinity || midi === Infinity) + return ""; + midi = Math.round(midi); + const pcs = options.sharps === true ? SHARPS : FLATS; + const pc = pcs[midi % 12]; + if (options.pitchClass) { + return pc; + } + const o = Math.floor(midi / 12) - 1; + return pc + o; +} + +const NAMES = ["C", "D", "E", "F", "G", "A", "B"]; +const toName = (n) => n.name; +const onlyNotes = (array) => array.map(note).filter((n) => !n.empty); +/** + * Return the natural note names without octave + * @function + * @example + * Note.names(); // => ["C", "D", "E", "F", "G", "A", "B"] + */ +function names$2(array) { + if (array === undefined) { + return NAMES.slice(); + } + else if (!Array.isArray(array)) { + return []; + } + else { + return onlyNotes(array).map(toName); + } +} +/** + * Get a note from a note name + * + * @function + * @example + * Note.get('Bb4') // => { name: "Bb4", midi: 70, chroma: 10, ... } + */ +const get$3 = note; +/** + * Get the note name + * @function + */ +const name$1 = (note) => get$3(note).name; +/** + * Get the note pitch class name + * @function + */ +const pitchClass = (note) => get$3(note).pc; +/** + * Get the note accidentals + * @function + */ +const accidentals = (note) => get$3(note).acc; +/** + * Get the note octave + * @function + */ +const octave = (note) => get$3(note).oct; +/** + * Get the note midi + * @function + */ +const midi = (note) => get$3(note).midi; +/** + * Get the note midi + * @function + */ +const freq = (note) => get$3(note).freq; +/** + * Get the note chroma + * @function + */ +const chroma = (note) => get$3(note).chroma; +/** + * Given a midi number, returns a note name. Uses flats for altered notes. + * + * @function + * @param {number} midi - the midi note number + * @return {string} the note name + * @example + * Note.fromMidi(61) // => "Db4" + * Note.fromMidi(61.7) // => "D4" + */ +function fromMidi(midi) { + return midiToNoteName(midi); +} +/** + * Given a midi number, returns a note name. Uses flats for altered notes. + */ +function fromFreq(freq) { + return midiToNoteName(freqToMidi(freq)); +} +/** + * Given a midi number, returns a note name. Uses flats for altered notes. + */ +function fromFreqSharps(freq) { + return midiToNoteName(freqToMidi(freq), { sharps: true }); +} +/** + * Given a midi number, returns a note name. Uses flats for altered notes. + * + * @function + * @param {number} midi - the midi note number + * @return {string} the note name + * @example + * Note.fromMidiSharps(61) // => "C#4" + */ +function fromMidiSharps(midi) { + return midiToNoteName(midi, { sharps: true }); +} +/** + * Transpose a note by an interval + */ +const transpose$1 = transpose; +const tr = transpose; +/** + * Transpose by an interval. + * @function + * @param {string} interval + * @return {function} a function that transposes by the given interval + * @example + * ["C", "D", "E"].map(Note.transposeBy("5P")); + * // => ["G", "A", "B"] + */ +const transposeBy = (interval) => (note) => transpose$1(note, interval); +const trBy = transposeBy; +/** + * Transpose from a note + * @function + * @param {string} note + * @return {function} a function that transposes the the note by an interval + * ["1P", "3M", "5P"].map(Note.transposeFrom("C")); + * // => ["C", "E", "G"] + */ +const transposeFrom = (note) => (interval) => transpose$1(note, interval); +const trFrom = transposeFrom; +/** + * Transpose a note by a number of perfect fifths. + * + * @function + * @param {string} note - the note name + * @param {number} fifhts - the number of fifths + * @return {string} the transposed note name + * + * @example + * import { transposeFifths } from "@tonaljs/note" + * transposeFifths("G4", 1) // => "D" + * [0, 1, 2, 3, 4].map(fifths => transposeFifths("C", fifths)) // => ["C", "G", "D", "A", "E"] + */ +function transposeFifths$1(noteName, fifths) { + const note = get$3(noteName); + if (note.empty) { + return ""; + } + const [nFifths, nOcts] = note.coord; + const transposed = nOcts === undefined + ? coordToNote([nFifths + fifths]) + : coordToNote([nFifths + fifths, nOcts]); + return transposed.name; +} +const trFifths = transposeFifths$1; +const ascending = (a, b) => a.height - b.height; +const descending = (a, b) => b.height - a.height; +function sortedNames(notes, comparator) { + comparator = comparator || ascending; + return onlyNotes(notes).sort(comparator).map(toName); +} +function sortedUniqNames(notes) { + return sortedNames(notes, ascending).filter((n, i, a) => i === 0 || n !== a[i - 1]); +} +/** + * Simplify a note + * + * @function + * @param {string} note - the note to be simplified + * - sameAccType: default true. Use same kind of accidentals that source + * @return {string} the simplified note or '' if not valid note + * @example + * simplify("C##") // => "D" + * simplify("C###") // => "D#" + * simplify("C###") + * simplify("B#4") // => "C5" + */ +const simplify$1 = (noteName) => { + const note = get$3(noteName); + if (note.empty) { + return ""; + } + return midiToNoteName(note.midi || note.chroma, { + sharps: note.alt > 0, + pitchClass: note.midi === null, + }); +}; +/** + * Get enharmonic of a note + * + * @function + * @param {string} note + * @param [string] - [optional] Destination pitch class + * @return {string} the enharmonic note name or '' if not valid note + * @example + * Note.enharmonic("Db") // => "C#" + * Note.enharmonic("C") // => "C" + * Note.enharmonic("F2","E#") // => "E#2" + */ +function enharmonic(noteName, destName) { + const src = get$3(noteName); + if (src.empty) { + return ""; + } + // destination: use given or generate one + const dest = get$3(destName || + midiToNoteName(src.midi || src.chroma, { + sharps: src.alt < 0, + pitchClass: true, + })); + // ensure destination is valid + if (dest.empty || dest.chroma !== src.chroma) { + return ""; + } + // if src has no octave, no need to calculate anything else + if (src.oct === undefined) { + return dest.pc; + } + // detect any octave overflow + const srcChroma = src.chroma - src.alt; + const destChroma = dest.chroma - dest.alt; + const destOctOffset = srcChroma > 11 || destChroma < 0 + ? -1 + : srcChroma < 0 || destChroma > 11 + ? +1 + : 0; + // calculate the new octave + const destOct = src.oct + destOctOffset; + return dest.pc + destOct; +} +var index$2 = { + names: names$2, + get: get$3, + name: name$1, + pitchClass, + accidentals, + octave, + midi, + ascending, + descending, + sortedNames, + sortedUniqNames, + fromMidi, + fromMidiSharps, + freq, + fromFreq, + fromFreqSharps, + chroma, + transpose: transpose$1, + tr, + transposeBy, + trBy, + transposeFrom, + trFrom, + transposeFifths: transposeFifths$1, + trFifths, + simplify: simplify$1, + enharmonic, +}; + +const Empty = Object.freeze([]); + +const MODES = [ + [0, 2773, 0, "ionian", "", "Maj7", "major"], + [1, 2902, 2, "dorian", "m", "m7"], + [2, 3418, 4, "phrygian", "m", "m7"], + [3, 2741, -1, "lydian", "", "Maj7"], + [4, 2774, 1, "mixolydian", "", "7"], + [5, 2906, 3, "aeolian", "m", "m7", "minor"], + [6, 3434, 5, "locrian", "dim", "m7b5"], +]; +const NoMode = { + ...EmptyPcset, + name: "", + alt: 0, + modeNum: NaN, + triad: "", + seventh: "", + aliases: [], +}; +const modes$1 = MODES.map(toMode); +const index$3 = {}; +modes$1.forEach((mode) => { + index$3[mode.name] = mode; + mode.aliases.forEach((alias) => { + index$3[alias] = mode; + }); +}); +/** + * Get a Mode by it's name + * + * @example + * get('dorian') + * // => + * // { + * // intervals: [ '1P', '2M', '3m', '4P', '5P', '6M', '7m' ], + * // modeNum: 1, + * // chroma: '101101010110', + * // normalized: '101101010110', + * // name: 'dorian', + * // setNum: 2902, + * // alt: 2, + * // triad: 'm', + * // seventh: 'm7', + * // aliases: [] + * // } + */ +function get$4(name) { + return typeof name === "string" + ? index$3[name.toLowerCase()] || NoMode + : name && name.name + ? get$4(name.name) + : NoMode; +} +function toMode(mode) { + const [modeNum, setNum, alt, name, triad, seventh, alias] = mode; + const aliases = alias ? [alias] : []; + const chroma = Number(setNum).toString(2); + const intervals = get$1(name).intervals; + return { + empty: false, + intervals, + modeNum, + chroma, + normalized: chroma, + name, + setNum, + alt, + triad, + seventh, + aliases, + }; +} +function chords(chords) { + return (modeName, tonic) => { + const mode = get$4(modeName); + if (mode.empty) + return []; + const triads = rotate(mode.modeNum, chords); + const tonics = mode.intervals.map((i) => transpose(tonic, i)); + return triads.map((triad, i) => tonics[i] + triad); + }; +} +const triads = chords(MODES.map((x) => x[4])); +const seventhChords = chords(MODES.map((x) => x[5])); + +/** + * References: + * - https://www.researchgate.net/publication/327567188_An_Algorithm_for_Spelling_the_Pitches_of_Any_Musical_Scale + * @module scale + */ +const NoScale = { + empty: true, + name: "", + type: "", + tonic: null, + setNum: NaN, + chroma: "", + normalized: "", + aliases: [], + notes: [], + intervals: [], +}; +/** + * Given a string with a scale name and (optionally) a tonic, split + * that components. + * + * It retuns an array with the form [ name, tonic ] where tonic can be a + * note name or null and name can be any arbitrary string + * (this function doesn"t check if that scale name exists) + * + * @function + * @param {string} name - the scale name + * @return {Array} an array [tonic, name] + * @example + * tokenize("C mixolydean") // => ["C", "mixolydean"] + * tokenize("anything is valid") // => ["", "anything is valid"] + * tokenize() // => ["", ""] + */ +function tokenize(name) { + if (typeof name !== "string") { + return ["", ""]; + } + const i = name.indexOf(" "); + const tonic = note(name.substring(0, i)); + if (tonic.empty) { + const n = note(name); + return n.empty ? ["", name] : [n.name, ""]; + } + const type = name.substring(tonic.name.length + 1); + return [tonic.name, type.length ? type : ""]; +} +/** + * Get all scale names + * @function + */ +const names$3 = names; +/** + * Get a Scale from a scale name. + */ +function get$5(src) { + const tokens = Array.isArray(src) ? src : tokenize(src); + const tonic = note(tokens[0]).name; + const st = get$1(tokens[1]); + if (st.empty) { + return NoScale; + } + const type = st.name; + const notes = tonic + ? st.intervals.map((i) => transpose(tonic, i)) + : []; + const name = tonic ? tonic + " " + type : type; + return { ...st, name, type, tonic, notes }; +} +const scale = deprecate("Scale.scale", "Scale.get", get$5); +/** + * Get all chords that fits a given scale + * + * @function + * @param {string} name - the scale name + * @return {Array} - the chord names + * + * @example + * scaleChords("pentatonic") // => ["5", "64", "M", "M6", "Madd9", "Msus2"] + */ +function scaleChords(name) { + const s = get$5(name); + const inScale = isSubsetOf(s.chroma); + return all() + .filter((chord) => inScale(chord.chroma)) + .map((chord) => chord.aliases[0]); +} +/** + * Get all scales names that are a superset of the given one + * (has the same notes and at least one more) + * + * @function + * @param {string} name + * @return {Array} a list of scale names + * @example + * extended("major") // => ["bebop", "bebop dominant", "bebop major", "chromatic", "ichikosucho"] + */ +function extended(name) { + const s = get$5(name); + const isSuperset = isSupersetOf(s.chroma); + return all$1() + .filter((scale) => isSuperset(scale.chroma)) + .map((scale) => scale.name); +} +/** + * Find all scales names that are a subset of the given one + * (has less notes but all from the given scale) + * + * @function + * @param {string} name + * @return {Array} a list of scale names + * + * @example + * reduced("major") // => ["ionian pentatonic", "major pentatonic", "ritusen"] + */ +function reduced(name) { + const isSubset = isSubsetOf(get$5(name).chroma); + return all$1() + .filter((scale) => isSubset(scale.chroma)) + .map((scale) => scale.name); +} +/** + * Given an array of notes, return the scale: a pitch class set starting from + * the first note of the array + * + * @function + * @param {string[]} notes + * @return {string[]} pitch classes with same tonic + * @example + * scaleNotes(['C4', 'c3', 'C5', 'C4', 'c4']) // => ["C"] + * scaleNotes(['D4', 'c#5', 'A5', 'F#6']) // => ["D", "F#", "A", "C#"] + */ +function scaleNotes(notes) { + const pcset = notes.map((n) => note(n).pc).filter((x) => x); + const tonic = pcset[0]; + const scale = sortedUniqNames(pcset); + return rotate(scale.indexOf(tonic), scale); +} +/** + * Find mode names of a scale + * + * @function + * @param {string} name - scale name + * @example + * modeNames("C pentatonic") // => [ + * ["C", "major pentatonic"], + * ["D", "egyptian"], + * ["E", "malkos raga"], + * ["G", "ritusen"], + * ["A", "minor pentatonic"] + * ] + */ +function modeNames(name) { + const s = get$5(name); + if (s.empty) { + return []; + } + const tonics = s.tonic ? s.notes : s.intervals; + return modes(s.chroma) + .map((chroma, i) => { + const modeName = get$5(chroma).name; + return modeName ? [tonics[i], modeName] : ["", ""]; + }) + .filter((x) => x[0]); +} +function getNoteNameOf(scale) { + const names = Array.isArray(scale) ? scaleNotes(scale) : get$5(scale).notes; + const chromas = names.map((name) => note(name).chroma); + return (noteOrMidi) => { + const currNote = typeof noteOrMidi === "number" + ? note(fromMidi(noteOrMidi)) + : note(noteOrMidi); + const height = currNote.height; + if (height === undefined) + return undefined; + const chroma = height % 12; + const position = chromas.indexOf(chroma); + if (position === -1) + return undefined; + return enharmonic(currNote.name, names[position]); + }; +} +function rangeOf(scale) { + const getName = getNoteNameOf(scale); + return (fromNote, toNote) => { + const from = note(fromNote).height; + const to = note(toNote).height; + if (from === undefined || to === undefined) + return []; + return range(from, to) + .map(getName) + .filter((x) => x); + }; +} +var index$4 = { + get: get$5, + names: names$3, + extended, + modeNames, + reduced, + scaleChords, + scaleNotes, + tokenize, + rangeOf, + // deprecated + scale, +}; + +export { index$1 as Interval, index$2 as Note, index$4 as Scale }; diff --git a/docs/_snowpack/pkg/import-map.json b/docs/_snowpack/pkg/import-map.json index e4c35bfb..b90886af 100644 --- a/docs/_snowpack/pkg/import-map.json +++ b/docs/_snowpack/pkg/import-map.json @@ -1,5 +1,6 @@ { "imports": { + "@tonaljs/tonal": "./@tonaljs/tonal.js", "fraction.js": "./fractionjs.js", "react": "./react.js", "react-dom": "./react-dom.js", diff --git a/docs/dist/App.js b/docs/dist/App.js index 2565580f..835554e6 100644 --- a/docs/dist/App.js +++ b/docs/dist/App.js @@ -5,10 +5,10 @@ import cx from "./cx.js"; import * as Tone from "../_snowpack/pkg/tone.js"; import useCycle from "./useCycle.js"; import * as tunes from "./tunes.js"; -import _mini from "./mini.js"; -const {tetris, tetrisMini} = tunes; +import * as krill from "./parse.js"; +const {tetris, tetrisMini, tetrisHaskell} = tunes; const {sequence, pure, reify, slowcat, fastcat, cat, stack, silence} = strudel; -const mini = _mini; +const {mini, h} = krill; const parse = (code) => eval(code); const synth = new Tone.PolySynth().toDestination(); synth.set({ @@ -18,7 +18,7 @@ synth.set({ } }); function App() { - const [code, setCode] = useState(tetrisMini); + const [code, setCode] = useState(tetrisHaskell); const [log, setLog] = useState(""); const logBox = useRef(); const [error, setError] = useState(); diff --git a/docs/dist/mini.js b/docs/dist/mini.js deleted file mode 100644 index c491801b..00000000 --- a/docs/dist/mini.js +++ /dev/null @@ -1,27 +0,0 @@ -import * as krill from "../_snowpack/link/repl/krill-parser.js"; -import * as strudel from "../_snowpack/link/strudel.js"; -const {sequence, stack, silence} = strudel; -export function patternifyAST(ast) { - switch (ast.type_) { - case "pattern": - if (ast.arguments_.alignment === "v") { - return stack(...ast.source_.map(patternifyAST)); - } - return sequence(...ast.source_.map(patternifyAST)); - case "element": - if (ast.source_ === "~") { - return silence; - } - if (typeof ast.source_ !== "object") { - return ast.source_; - } - return patternifyAST(ast.source_); - } -} -export default (...strings) => { - const pattern = sequence(...strings.map((str) => { - const ast = krill.parse(`"${str}"`); - return patternifyAST(ast); - })); - return pattern; -}; diff --git a/docs/dist/parse.js b/docs/dist/parse.js new file mode 100644 index 00000000..da747f1e --- /dev/null +++ b/docs/dist/parse.js @@ -0,0 +1,81 @@ +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"; +const {sequence, stack, silence, Fraction, pure} = strudel; +function reify(thing) { + if (thing?.constructor?.name === "Pattern") { + return thing; + } + return pure(thing); +} +const applyOptions = (parent) => (pat, i) => { + const ast = parent.source_[i]; + const options = ast.options_; + const operator = options?.operator; + if (operator) { + switch (operator.type_) { + case "stretch": + const speed = new Fraction(operator.arguments_.amount).inverse().valueOf(); + return reify(pat).fast(speed); + } + console.warn(`operator "${operator.type_}" not implemented`); + } + 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`); + } + return pat; +}; +export function patternifyAST(ast) { + switch (ast.type_) { + case "pattern": + const children = ast.source_.map(patternifyAST).map(applyOptions(ast)); + if (ast.arguments_.alignment === "v") { + return stack(...children); + } + return sequence(...children); + case "element": + if (ast.source_ === "~") { + return silence; + } + if (typeof ast.source_ !== "object") { + return ast.source_; + } + return patternifyAST(ast.source_); + case "stretch": + return patternifyAST(ast.source_).slow(ast.arguments_.amount); + case "scale": + let [tonic, scale] = Scale.tokenize(ast.arguments_.scale); + const intervals = Scale.get(scale).intervals; + const pattern = patternifyAST(ast.source_); + tonic = tonic || "C4"; + console.log("tonic", tonic); + return pattern.fmap((step) => { + step = Number(step); + if (isNaN(step)) { + console.warn(`scale step "${step}" not a number`); + return step; + } + const octaves = Math.floor(step / intervals.length); + const mod = (n, m) => n < 0 ? mod(n + m, m) : n % m; + const index = mod(step, intervals.length); + const interval = Interval.add(intervals[index], Interval.fromSemitones(octaves * 12)); + return Note.transpose(tonic, interval || "1P"); + }); + default: + console.warn(`node type "${ast.type_}" not implemented -> returning silence`); + return silence; + } +} +export const mini = (...strings) => { + const pattern = sequence(...strings.map((str) => { + const ast = krill.parse(`"${str}"`); + return patternifyAST(ast); + })); + return pattern; +}; +export const h = (string) => { + const ast = krill.parse(string); + console.log("ast", ast); + return patternifyAST(ast); +}; diff --git a/docs/dist/tunes.js b/docs/dist/tunes.js index 372061c2..dfd32bac 100644 --- a/docs/dist/tunes.js +++ b/docs/dist/tunes.js @@ -53,14 +53,31 @@ export const tetrisMini = `mini(\`[[e5 [b4 c5] d5 [c5 b4]] [e5 [~ c5] e5 [d5 c5]] [b4 [b4 c5] d5 e5] [c5 a4 a4 ~]], -[[e2 e3 e2 e3 e2 e3 e2 e3] -[a2 a3 a2 a3 a2 a3 a2 a3] -[g#2 g#3 g#2 g#3 e2 e3 e2 e3] +[[e2 e3]*4] +[[a2 a3]*4] +[[g#2 g#3]*2 [e2 e3]*2] [a2 a3 a2 a3 a2 a3 b1 c2] -[d2 d3 d2 d3 d2 d3 d2 d3] -[c2 c3 c2 c3 c2 c3 c2 c3] -[b1 b2 b1 b2 e2 e3 e2 e3] -[a1 a2 a1 a2 a1 a2 a1 a2]]\`)._slow(16); +[[d2 d3]*4] +[[c2 c3]*4] +[[b1 b2]*2 [e2 e3]*2] +[[a1 a2]*4]\`)._slow(16); +`; +export const tetrisHaskell = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]] +[a4 [a4 c5] e5 [d5 c5]] +[b4 [~ c5] d5 e5] +[c5 a4 a4 ~] +[[~ d5] [~ f5] a5 [g5 f5]] +[e5 [~ c5] e5 [d5 c5]] +[b4 [b4 c5] d5 e5] +[c5 a4 a4 ~]], +[[e2 e3]*4] +[[a2 a3]*4] +[[g#2 g#3]*2 [e2 e3]*2] +[a2 a3 a2 a3 a2 a3 b1 c2] +[[d2 d3]*4] +[[c2 c3]*4] +[[b1 b2]*2 [e2 e3]*2] +[[a1 a2]*4]"\`) `; export const spanish = `slowcat( stack('c4','eb4','g4'), From 36db8441552c6fecad6e7d1c35f0d35b9b367c32 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 21:15:03 +0100 Subject: [PATCH 11/21] add some todos --- repl/README.md | 25 +++++++++++++++++++------ repl/src/parse.ts | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/repl/README.md b/repl/README.md index 26408bc1..cddd5f7f 100644 --- a/repl/README.md +++ b/repl/README.md @@ -1,5 +1,23 @@ # Strudel REPL +## TODO + +### mini notation ([krill docs](https://github.com/Mdashdotdashn/krill#general-principles)) + +- "@" aka elongation / weight: how to write in strudel? +- "%" fixed step: how to write in strudel? +- "struct" not sure how to do this +- "c3(5,8)" bjorklund algorithm +- more [mini notation features](https://tidalcycles.org/docs/patternlib/tutorials/mini_notation) +- not tested + +### strudel + +- slow doesn't work when nested +- reify should also work for strings + +## Default Snowpack Readme + > ✨ Bootstrapped with Create Snowpack App (CSA). ## Available Scripts @@ -14,12 +32,7 @@ You will also see any lint errors in the console. ### npm run build -Builds a static copy of your site to the `build/` folder. +Builds a static copy of your site to the `docs/` folder. Your app is ready to be deployed! **For the best production performance:** Add a build bundler plugin like "@snowpack/plugin-webpack" to your `snowpack.config.mjs` config file. - -### npm test - -Launches the application test runner. -Run with the `--watch` flag (`npm test -- --watch`) to run in interactive watch mode. diff --git a/repl/src/parse.ts b/repl/src/parse.ts index 64228006..8cf15c51 100644 --- a/repl/src/parse.ts +++ b/repl/src/parse.ts @@ -20,7 +20,7 @@ const applyOptions = (parent: any) => (pat: any, i: number) => { case 'stretch': const speed = new Fraction(operator.arguments_.amount).inverse().valueOf(); return reify(pat).fast(speed); - // case 'fixed-step': "%" // https://github.com/Mdashdotdashn/krill#patterns---a-more-traditional-approach + // TODO: case 'fixed-step': "%" } console.warn(`operator "${operator.type_}" not implemented`); } From ffe0556c90ee0092ba761bfa98cb160c5b76ab15 Mon Sep 17 00:00:00 2001 From: Felix Roos Date: Sun, 6 Feb 2022 22:13:46 +0100 Subject: [PATCH 12/21] add codemirror --- .gitignore | 1 + repl/package-lock.json | 232 +++++----------------------------------- repl/package.json | 2 + repl/public/global.css | 5 + repl/src/App.tsx | 36 ++++--- repl/src/CodeMirror.tsx | 14 +++ 6 files changed, 69 insertions(+), 221 deletions(-) create mode 100644 repl/src/CodeMirror.tsx diff --git a/.gitignore b/.gitignore index 97241f88..fe54b513 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ cabal.project.local~ .HTF/ .ghc.environment.* node_modules/ +.DS_Store \ No newline at end of file diff --git a/repl/package-lock.json b/repl/package-lock.json index 2003efbc..d9166583 100644 --- a/repl/package-lock.json +++ b/repl/package-lock.json @@ -6,9 +6,10 @@ "": { "dependencies": { "@tonaljs/tonal": "^4.6.5", + "codemirror": "^5.65.1", "react": "^17.0.2", + "react-codemirror2": "^7.2.1", "react-dom": "^17.0.2", - "tonal": "^2.2.2", "tone": "^14.7.77" }, "devDependencies": { @@ -2890,6 +2891,11 @@ "node": ">=0.10.0" } }, + "node_modules/codemirror": { + "version": "5.65.1", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.1.tgz", + "integrity": "sha512-s6aac+DD+4O2u1aBmdxhB7yz2XU7tG3snOyQ05Kxifahz7hoxnfxIRHxiCSEv3TUC38dIVH8G+lZH9UWSfGQxA==" + }, "node_modules/color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -6845,6 +6851,15 @@ "node": ">=0.10.0" } }, + "node_modules/react-codemirror2": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-7.2.1.tgz", + "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==", + "peerDependencies": { + "codemirror": "5.x", + "react": ">=15.5 <=16.x" + } + }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -8066,108 +8081,6 @@ "node": ">=0.6" } }, - "node_modules/tonal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal/-/tonal-2.2.2.tgz", - "integrity": "sha512-Ze2bQc6KhAf3FKM9HzEsQ4z8hZh4WYCOsCrryONqf/THGOrOpL9Cc8Uc0dq0OA2yK2JbD5FhZckEXNYyD9946A==", - "dependencies": { - "tonal-array": "^2.2.2", - "tonal-chord": "^2.2.2", - "tonal-dictionary": "^2.2.2", - "tonal-distance": "^2.2.2", - "tonal-interval": "^2.2.2", - "tonal-key": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-pcset": "^2.2.2", - "tonal-scale": "^2.2.2" - } - }, - "node_modules/tonal-array": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-array/-/tonal-array-2.2.2.tgz", - "integrity": "sha512-h6YIq20L0EEU4EsDoKHAjl5kD2EQn467VfV79QHAuybvNCJpqqRNsQ3QNvoQyir1BgDXaDUIN9FEmQJNiaaCKA==", - "dependencies": { - "tonal-note": "^2.2.2" - } - }, - "node_modules/tonal-chord": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-chord/-/tonal-chord-2.2.2.tgz", - "integrity": "sha512-gOIXapi6Gx3ISRKdEJKEQjhDBiwjhaalyWSrN5rijGrSyyFFNZ+EVOfzcqLtnVAF9BgeO9Ca0eXCor3XpHdEJg==", - "dependencies": { - "tonal-dictionary": "^2.2.2", - "tonal-distance": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-pcset": "^2.2.2" - } - }, - "node_modules/tonal-dictionary": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-dictionary/-/tonal-dictionary-2.2.2.tgz", - "integrity": "sha512-283ppJl/0lohhlVPMI6t5C6XwaP5Wx0egu9qfG9TLCT2tn4pRwYpXkzGufd9icvkJTgOylOum3+RxWmywUIPIg==", - "dependencies": { - "tonal-array": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-pcset": "^2.2.2" - } - }, - "node_modules/tonal-distance": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-2.2.2.tgz", - "integrity": "sha512-ktA6OapCxaetXJb/JuXD5QwfyB7/G3y3ONby7Kkbezyffc57cnNfjdhlTR9XBR7eSFIY/J1KuhLwMx/qrffT4g==", - "dependencies": { - "tonal-interval": "^2.2.2", - "tonal-note": "^2.2.2" - } - }, - "node_modules/tonal-interval": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-2.2.2.tgz", - "integrity": "sha512-lrtDU8lH5IAX7YE63OhGGDRpVb4OoGxaN0wDu5XC3sUhXBwjSgNYpHY2D9JI2aWQ/Er9jhQbnw9b0ffkLy34+Q==" - }, - "node_modules/tonal-key": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-key/-/tonal-key-2.2.2.tgz", - "integrity": "sha512-KIc0b8yPl2ATDxF/65P52tIIempNsAQrug0idpD0zFvs5F5cb1hp7Rh7JJ4gECwC/6a3Hgdd1jomI+TnJ7K98w==", - "dependencies": { - "tonal-array": "^2.2.2", - "tonal-distance": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-roman-numeral": "^2.2.2" - } - }, - "node_modules/tonal-note": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-note/-/tonal-note-2.2.2.tgz", - "integrity": "sha512-RNK3Nb8PxBEW9yYGStcoczgE8bCYFZ5zfLvYJjvuzLWiwTQmqWOhTzONVobVCGFZ/jgDNwpBEKe/bngL3g3Xfw==" - }, - "node_modules/tonal-pcset": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-pcset/-/tonal-pcset-2.2.2.tgz", - "integrity": "sha512-PSqhkxzckO6J27W0GxawHYln4wvfDJ7puDmccksyFOBo97UhLnpxiyvBekhiYpkuaMtoZLQC/KALAkEj7lcb+A==", - "dependencies": { - "tonal-array": "^2.2.2", - "tonal-interval": "^2.2.2", - "tonal-note": "^2.2.2" - } - }, - "node_modules/tonal-roman-numeral": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-roman-numeral/-/tonal-roman-numeral-2.2.2.tgz", - "integrity": "sha512-+auQNObpW3OvsSqlo+Cc+0otrlEhtbEgpzkPoKbTtkCva0P9oSkSz0OZ9fI73KQM5MsBs1XbB+olxppWkzYTFw==" - }, - "node_modules/tonal-scale": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-scale/-/tonal-scale-2.2.2.tgz", - "integrity": "sha512-tDb3YCoTF50XOXq9kNhGB1JkInk7qAGN6GQnP/3xkGxkreFFRZyI58jfHlmWf/AH4+IKb/exsOmL6G8Ok/PCRw==", - "dependencies": { - "tonal-array": "^2.2.2", - "tonal-dictionary": "^2.2.2", - "tonal-distance": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-pcset": "^2.2.2" - } - }, "node_modules/tone": { "version": "14.7.77", "resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz", @@ -11039,6 +10952,11 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, + "codemirror": { + "version": "5.65.1", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.1.tgz", + "integrity": "sha512-s6aac+DD+4O2u1aBmdxhB7yz2XU7tG3snOyQ05Kxifahz7hoxnfxIRHxiCSEv3TUC38dIVH8G+lZH9UWSfGQxA==" + }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", @@ -14027,6 +13945,12 @@ "object-assign": "^4.1.1" } }, + "react-codemirror2": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-7.2.1.tgz", + "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==", + "requires": {} + }, "react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -14964,108 +14888,6 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, - "tonal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal/-/tonal-2.2.2.tgz", - "integrity": "sha512-Ze2bQc6KhAf3FKM9HzEsQ4z8hZh4WYCOsCrryONqf/THGOrOpL9Cc8Uc0dq0OA2yK2JbD5FhZckEXNYyD9946A==", - "requires": { - "tonal-array": "^2.2.2", - "tonal-chord": "^2.2.2", - "tonal-dictionary": "^2.2.2", - "tonal-distance": "^2.2.2", - "tonal-interval": "^2.2.2", - "tonal-key": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-pcset": "^2.2.2", - "tonal-scale": "^2.2.2" - } - }, - "tonal-array": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-array/-/tonal-array-2.2.2.tgz", - "integrity": "sha512-h6YIq20L0EEU4EsDoKHAjl5kD2EQn467VfV79QHAuybvNCJpqqRNsQ3QNvoQyir1BgDXaDUIN9FEmQJNiaaCKA==", - "requires": { - "tonal-note": "^2.2.2" - } - }, - "tonal-chord": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-chord/-/tonal-chord-2.2.2.tgz", - "integrity": "sha512-gOIXapi6Gx3ISRKdEJKEQjhDBiwjhaalyWSrN5rijGrSyyFFNZ+EVOfzcqLtnVAF9BgeO9Ca0eXCor3XpHdEJg==", - "requires": { - "tonal-dictionary": "^2.2.2", - "tonal-distance": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-pcset": "^2.2.2" - } - }, - "tonal-dictionary": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-dictionary/-/tonal-dictionary-2.2.2.tgz", - "integrity": "sha512-283ppJl/0lohhlVPMI6t5C6XwaP5Wx0egu9qfG9TLCT2tn4pRwYpXkzGufd9icvkJTgOylOum3+RxWmywUIPIg==", - "requires": { - "tonal-array": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-pcset": "^2.2.2" - } - }, - "tonal-distance": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-distance/-/tonal-distance-2.2.2.tgz", - "integrity": "sha512-ktA6OapCxaetXJb/JuXD5QwfyB7/G3y3ONby7Kkbezyffc57cnNfjdhlTR9XBR7eSFIY/J1KuhLwMx/qrffT4g==", - "requires": { - "tonal-interval": "^2.2.2", - "tonal-note": "^2.2.2" - } - }, - "tonal-interval": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-interval/-/tonal-interval-2.2.2.tgz", - "integrity": "sha512-lrtDU8lH5IAX7YE63OhGGDRpVb4OoGxaN0wDu5XC3sUhXBwjSgNYpHY2D9JI2aWQ/Er9jhQbnw9b0ffkLy34+Q==" - }, - "tonal-key": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-key/-/tonal-key-2.2.2.tgz", - "integrity": "sha512-KIc0b8yPl2ATDxF/65P52tIIempNsAQrug0idpD0zFvs5F5cb1hp7Rh7JJ4gECwC/6a3Hgdd1jomI+TnJ7K98w==", - "requires": { - "tonal-array": "^2.2.2", - "tonal-distance": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-roman-numeral": "^2.2.2" - } - }, - "tonal-note": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-note/-/tonal-note-2.2.2.tgz", - "integrity": "sha512-RNK3Nb8PxBEW9yYGStcoczgE8bCYFZ5zfLvYJjvuzLWiwTQmqWOhTzONVobVCGFZ/jgDNwpBEKe/bngL3g3Xfw==" - }, - "tonal-pcset": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-pcset/-/tonal-pcset-2.2.2.tgz", - "integrity": "sha512-PSqhkxzckO6J27W0GxawHYln4wvfDJ7puDmccksyFOBo97UhLnpxiyvBekhiYpkuaMtoZLQC/KALAkEj7lcb+A==", - "requires": { - "tonal-array": "^2.2.2", - "tonal-interval": "^2.2.2", - "tonal-note": "^2.2.2" - } - }, - "tonal-roman-numeral": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-roman-numeral/-/tonal-roman-numeral-2.2.2.tgz", - "integrity": "sha512-+auQNObpW3OvsSqlo+Cc+0otrlEhtbEgpzkPoKbTtkCva0P9oSkSz0OZ9fI73KQM5MsBs1XbB+olxppWkzYTFw==" - }, - "tonal-scale": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tonal-scale/-/tonal-scale-2.2.2.tgz", - "integrity": "sha512-tDb3YCoTF50XOXq9kNhGB1JkInk7qAGN6GQnP/3xkGxkreFFRZyI58jfHlmWf/AH4+IKb/exsOmL6G8Ok/PCRw==", - "requires": { - "tonal-array": "^2.2.2", - "tonal-dictionary": "^2.2.2", - "tonal-distance": "^2.2.2", - "tonal-note": "^2.2.2", - "tonal-pcset": "^2.2.2" - } - }, "tone": { "version": "14.7.77", "resolved": "https://registry.npmjs.org/tone/-/tone-14.7.77.tgz", diff --git a/repl/package.json b/repl/package.json index e5d71892..2e87f86c 100644 --- a/repl/package.json +++ b/repl/package.json @@ -9,7 +9,9 @@ }, "dependencies": { "@tonaljs/tonal": "^4.6.5", + "codemirror": "^5.65.1", "react": "^17.0.2", + "react-codemirror2": "^7.2.1", "react-dom": "^17.0.2", "tone": "^14.7.77" }, diff --git a/repl/public/global.css b/repl/public/global.css index b5c61c95..668d4a6c 100644 --- a/repl/public/global.css +++ b/repl/public/global.css @@ -1,3 +1,8 @@ @tailwind base; @tailwind components; @tailwind utilities; + +.react-codemirror2, +.CodeMirror { + height: 100% !important; +} diff --git a/repl/src/App.tsx b/repl/src/App.tsx index ee032037..5d90b2be 100644 --- a/repl/src/App.tsx +++ b/repl/src/App.tsx @@ -7,6 +7,7 @@ import useCycle from './useCycle'; import type { Hap, Pattern } from './types'; import * as tunes from './tunes'; import * as krill from './parse'; +import CodeMirror from './CodeMirror'; const { tetris, tetrisMini, tetrisHaskell } = tunes; @@ -68,6 +69,7 @@ function App() { // cycle.query(cycle.activeCycle()); // reschedule active cycle setError(undefined); } catch (err: any) { + console.warn(err); setError(err); } }, [code]); @@ -75,38 +77,40 @@ function App() { useLayoutEffect(() => { logBox.current.scrollTop = logBox.current?.scrollHeight; }, [log]); - return ( -
-
+
+
logo

Strudel REPL

-
-
-
{error?.message}
-