Merge branch 'main' into bug-when

This commit is contained in:
alex 2022-02-19 15:00:28 +00:00
commit 9e05da552f
56 changed files with 123505 additions and 2869 deletions

View File

@ -177,30 +177,33 @@ function peg$parse(input, options) {
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$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 = "/";
var peg$c19 = "*";
var peg$c20 = "%";
var peg$c21 = "struct";
var peg$c22 = "target";
var peg$c23 = "euclid";
var peg$c24 = "slow";
var peg$c25 = "rotL";
var peg$c26 = "rotR";
var peg$c27 = "fast";
var peg$c28 = "scale";
var peg$c29 = "//";
var peg$c30 = "cat";
var peg$c31 = "$";
var peg$c32 = "setcps";
var peg$c33 = "setbpm";
var peg$c34 = "hush";
var peg$r0 = /^[1-9]/;
var peg$r1 = /^[eE]/;
@ -224,63 +227,67 @@ function peg$parse(input, options) {
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$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("/", false);
var peg$e26 = peg$literalExpectation("*", false);
var peg$e27 = peg$literalExpectation("%", false);
var peg$e28 = peg$literalExpectation("struct", false);
var peg$e29 = peg$literalExpectation("target", false);
var peg$e30 = peg$literalExpectation("euclid", false);
var peg$e31 = peg$literalExpectation("slow", false);
var peg$e32 = peg$literalExpectation("rotL", false);
var peg$e33 = peg$literalExpectation("rotR", false);
var peg$e34 = peg$literalExpectation("fast", false);
var peg$e35 = peg$literalExpectation("scale", false);
var peg$e36 = peg$literalExpectation("//", false);
var peg$e37 = peg$classExpectation(["\n"], true, false);
var peg$e38 = peg$literalExpectation("cat", false);
var peg$e39 = peg$literalExpectation("$", false);
var peg$e40 = peg$literalExpectation("setcps", false);
var peg$e41 = peg$literalExpectation("setbpm", false);
var peg$e42 = 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$f5 = function(a) { return { replicate: a } };
var peg$f6 = function(p, s) { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s } } } };
var peg$f7 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a } } } };
var peg$f8 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+a } } } };
var peg$f9 = function(a) { return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } };
var peg$f10 = function(s, o) { return new ElementStub(s, o);};
var peg$f11 = function(s) { return new PatternStub(s,"h"); };
var peg$f12 = function(c, v) { return v};
var peg$f13 = function(c, cs) { if (cs.length == 0 && c instanceof Object) { return c;} else { cs.unshift(c); return new PatternStub(cs,"v");} };
var peg$f14 = function(s) { return s; };
var peg$f15 = function(s) { return { name: "struct", args: { sequence:s }}};
var peg$f16 = function(s) { return { name: "target", args : { name:s}}};
var peg$f17 = function(p, s) { return { name: "bjorklund", args :{ pulse: parseInt(p), step:parseInt(s) }}};
var peg$f18 = function(a) { return { name: "stretch", args :{ amount: a}}};
var peg$f19 = function(a) { return { name: "shift", args :{ amount: "-"+a}}};
var peg$f20 = function(a) { return { name: "shift", args :{ amount: a}}};
var peg$f21 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}};
var peg$f22 = function(s) { return { name: "scale", args :{ scale: s.join("")}}};
var peg$f23 = function(s, v) { return v};
var peg$f24 = function(s, ss) { ss.unshift(s); return new PatternStub(ss,"t"); };
var peg$f25 = function(sg) {return sg};
var peg$f26 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)};
var peg$f27 = function(sc) { return sc };
var peg$f28 = function(c) { return c };
var peg$f29 = function(v) { return new CommandStub("setcps", { value: v})};
var peg$f30 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})};
var peg$f31 = function() { return new CommandStub("hush")};
var peg$currPos = 0;
var peg$savedPos = 0;
@ -781,6 +788,24 @@ function peg$parse(input, options) {
s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e1); }
}
if (s0 === peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 94) {
s0 = peg$c8;
peg$currPos++;
} else {
s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e15); }
}
if (s0 === peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 95) {
s0 = peg$c9;
peg$currPos++;
} else {
s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e16); }
}
}
}
}
}
}
@ -821,11 +846,11 @@ function peg$parse(input, options) {
s0 = peg$currPos;
s1 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 91) {
s2 = peg$c8;
s2 = peg$c10;
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e15); }
if (peg$silentFails === 0) { peg$fail(peg$e17); }
}
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
@ -833,11 +858,11 @@ function peg$parse(input, options) {
if (s4 !== peg$FAILED) {
s5 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 93) {
s6 = peg$c9;
s6 = peg$c11;
peg$currPos++;
} else {
s6 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e16); }
if (peg$silentFails === 0) { peg$fail(peg$e18); }
}
if (s6 !== peg$FAILED) {
s7 = peg$parsews();
@ -865,11 +890,11 @@ function peg$parse(input, options) {
s0 = peg$currPos;
s1 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 60) {
s2 = peg$c10;
s2 = peg$c12;
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e17); }
if (peg$silentFails === 0) { peg$fail(peg$e19); }
}
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
@ -877,11 +902,11 @@ function peg$parse(input, options) {
if (s4 !== peg$FAILED) {
s5 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 62) {
s6 = peg$c11;
s6 = peg$c13;
peg$currPos++;
} else {
s6 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e18); }
if (peg$silentFails === 0) { peg$fail(peg$e20); }
}
if (s6 !== peg$FAILED) {
s7 = peg$parsews();
@ -929,6 +954,9 @@ function peg$parse(input, options) {
s0 = peg$parseslice_fast();
if (s0 === peg$FAILED) {
s0 = peg$parseslice_fixed_step();
if (s0 === peg$FAILED) {
s0 = peg$parseslice_replicate();
}
}
}
}
@ -942,11 +970,11 @@ function peg$parse(input, options) {
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 64) {
s1 = peg$c12;
s1 = peg$c14;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e19); }
if (peg$silentFails === 0) { peg$fail(peg$e21); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsenumber();
@ -965,16 +993,44 @@ function peg$parse(input, options) {
return s0;
}
function peg$parseslice_replicate() {
var s0, s1, s2;
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 33) {
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$f5(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;
s1 = peg$c16;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e20); }
if (peg$silentFails === 0) { peg$fail(peg$e23); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -988,15 +1044,15 @@ function peg$parse(input, options) {
if (s7 !== peg$FAILED) {
s8 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 41) {
s9 = peg$c14;
s9 = peg$c17;
peg$currPos++;
} else {
s9 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e21); }
if (peg$silentFails === 0) { peg$fail(peg$e24); }
}
if (s9 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f5(s3, s7);
s0 = peg$f6(s3, s7);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1026,39 +1082,11 @@ function peg$parse(input, options) {
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 47) {
s1 = peg$c15;
s1 = peg$c18;
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 (peg$silentFails === 0) { peg$fail(peg$e25); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsenumber();
@ -1077,16 +1105,16 @@ function peg$parse(input, options) {
return s0;
}
function peg$parseslice_fixed_step() {
function peg$parseslice_fast() {
var s0, s1, s2;
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 37) {
s1 = peg$c17;
if (input.charCodeAt(peg$currPos) === 42) {
s1 = peg$c19;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e24); }
if (peg$silentFails === 0) { peg$fail(peg$e26); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsenumber();
@ -1105,6 +1133,34 @@ function peg$parse(input, options) {
return s0;
}
function peg$parseslice_fixed_step() {
var s0, s1, s2;
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 37) {
s1 = peg$c20;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e27); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsenumber();
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f9(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;
@ -1116,7 +1172,7 @@ function peg$parse(input, options) {
s2 = null;
}
peg$savedPos = s0;
s0 = peg$f9(s1, s2);
s0 = peg$f10(s1, s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1141,7 +1197,7 @@ function peg$parse(input, options) {
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$f10(s1);
s1 = peg$f11(s1);
}
s0 = s1;
@ -1161,7 +1217,7 @@ function peg$parse(input, options) {
s5 = peg$parsesingle_cycle();
if (s5 !== peg$FAILED) {
peg$savedPos = s3;
s3 = peg$f11(s1, s5);
s3 = peg$f12(s1, s5);
} else {
peg$currPos = s3;
s3 = peg$FAILED;
@ -1178,7 +1234,7 @@ function peg$parse(input, options) {
s5 = peg$parsesingle_cycle();
if (s5 !== peg$FAILED) {
peg$savedPos = s3;
s3 = peg$f11(s1, s5);
s3 = peg$f12(s1, s5);
} else {
peg$currPos = s3;
s3 = peg$FAILED;
@ -1189,7 +1245,7 @@ function peg$parse(input, options) {
}
}
peg$savedPos = s0;
s0 = peg$f12(s1, s2);
s0 = peg$f13(s1, s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1210,7 +1266,7 @@ function peg$parse(input, options) {
s4 = peg$parsequote();
if (s4 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f13(s3);
s0 = peg$f14(s3);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1260,19 +1316,19 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c18) {
s1 = peg$c18;
if (input.substr(peg$currPos, 6) === peg$c21) {
s1 = peg$c21;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e25); }
if (peg$silentFails === 0) { peg$fail(peg$e28); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsesequence_or_operator();
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f14(s3);
s0 = peg$f15(s3);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1289,12 +1345,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c19) {
s1 = peg$c19;
if (input.substr(peg$currPos, 6) === peg$c22) {
s1 = peg$c22;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e26); }
if (peg$silentFails === 0) { peg$fail(peg$e29); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1305,7 +1361,7 @@ function peg$parse(input, options) {
s5 = peg$parsequote();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f15(s4);
s0 = peg$f16(s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1330,12 +1386,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c20) {
s1 = peg$c20;
if (input.substr(peg$currPos, 6) === peg$c23) {
s1 = peg$c23;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e27); }
if (peg$silentFails === 0) { peg$fail(peg$e30); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1345,7 +1401,7 @@ function peg$parse(input, options) {
s5 = peg$parseint();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f16(s3, s5);
s0 = peg$f17(s3, s5);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1366,41 +1422,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c21) {
s1 = peg$c21;
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$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 (peg$silentFails === 0) { peg$fail(peg$e31); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1420,16 +1447,16 @@ function peg$parse(input, options) {
return s0;
}
function peg$parserotR() {
function peg$parserotL() {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c23) {
s1 = peg$c23;
if (input.substr(peg$currPos, 4) === peg$c25) {
s1 = peg$c25;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e30); }
if (peg$silentFails === 0) { peg$fail(peg$e32); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1449,16 +1476,16 @@ function peg$parse(input, options) {
return s0;
}
function peg$parsefast() {
function peg$parserotR() {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c24) {
s1 = peg$c24;
if (input.substr(peg$currPos, 4) === peg$c26) {
s1 = peg$c26;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e31); }
if (peg$silentFails === 0) { peg$fail(peg$e33); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1478,16 +1505,45 @@ function peg$parse(input, options) {
return s0;
}
function peg$parsefast() {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c27) {
s1 = peg$c27;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e34); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsenumber();
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f21(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;
if (input.substr(peg$currPos, 5) === peg$c28) {
s1 = peg$c28;
peg$currPos += 5;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e32); }
if (peg$silentFails === 0) { peg$fail(peg$e35); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1507,7 +1563,7 @@ function peg$parse(input, options) {
s5 = peg$parsequote();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f21(s4);
s0 = peg$f22(s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1532,12 +1588,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 2) === peg$c26) {
s1 = peg$c26;
if (input.substr(peg$currPos, 2) === peg$c29) {
s1 = peg$c29;
peg$currPos += 2;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e33); }
if (peg$silentFails === 0) { peg$fail(peg$e36); }
}
if (s1 !== peg$FAILED) {
s2 = [];
@ -1546,7 +1602,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e34); }
if (peg$silentFails === 0) { peg$fail(peg$e37); }
}
while (s3 !== peg$FAILED) {
s2.push(s3);
@ -1555,7 +1611,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e34); }
if (peg$silentFails === 0) { peg$fail(peg$e37); }
}
}
s1 = [s1, s2];
@ -1572,21 +1628,21 @@ function peg$parse(input, options) {
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;
if (input.substr(peg$currPos, 3) === peg$c30) {
s1 = peg$c30;
peg$currPos += 3;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e35); }
if (peg$silentFails === 0) { peg$fail(peg$e38); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 91) {
s3 = peg$c8;
s3 = peg$c10;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e15); }
if (peg$silentFails === 0) { peg$fail(peg$e17); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parsews();
@ -1599,7 +1655,7 @@ function peg$parse(input, options) {
s9 = peg$parsesequence_or_operator();
if (s9 !== peg$FAILED) {
peg$savedPos = s7;
s7 = peg$f22(s5, s9);
s7 = peg$f23(s5, s9);
} else {
peg$currPos = s7;
s7 = peg$FAILED;
@ -1616,7 +1672,7 @@ function peg$parse(input, options) {
s9 = peg$parsesequence_or_operator();
if (s9 !== peg$FAILED) {
peg$savedPos = s7;
s7 = peg$f22(s5, s9);
s7 = peg$f23(s5, s9);
} else {
peg$currPos = s7;
s7 = peg$FAILED;
@ -1628,15 +1684,15 @@ function peg$parse(input, options) {
}
s7 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 93) {
s8 = peg$c9;
s8 = peg$c11;
peg$currPos++;
} else {
s8 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e16); }
if (peg$silentFails === 0) { peg$fail(peg$e18); }
}
if (s8 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f23(s5, s6);
s0 = peg$f24(s5, s6);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1682,7 +1738,7 @@ function peg$parse(input, options) {
s4 = peg$parsecomment();
}
peg$savedPos = s0;
s0 = peg$f24(s1);
s0 = peg$f25(s1);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1693,18 +1749,18 @@ function peg$parse(input, options) {
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 36) {
s3 = peg$c28;
s3 = peg$c31;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e36); }
if (peg$silentFails === 0) { peg$fail(peg$e39); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parsews();
s5 = peg$parsesequence_or_operator();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f25(s1, s5);
s0 = peg$f26(s1, s5);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1729,7 +1785,7 @@ function peg$parse(input, options) {
s1 = peg$parsesequence_or_operator();
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$f26(s1);
s1 = peg$f27(s1);
}
s0 = s1;
if (s0 === peg$FAILED) {
@ -1762,7 +1818,7 @@ function peg$parse(input, options) {
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
peg$savedPos = s0;
s0 = peg$f27(s2);
s0 = peg$f28(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1775,41 +1831,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c29) {
s1 = peg$c29;
if (input.substr(peg$currPos, 6) === peg$c32) {
s1 = peg$c32;
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 (peg$silentFails === 0) { peg$fail(peg$e40); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1829,20 +1856,49 @@ function peg$parse(input, options) {
return s0;
}
function peg$parsesetbpm() {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c33) {
s1 = peg$c33;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e41); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsenumber();
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f30(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;
if (input.substr(peg$currPos, 4) === peg$c34) {
s1 = peg$c34;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e39); }
if (peg$silentFails === 0) { peg$fail(peg$e42); }
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$f30();
s1 = peg$f31();
}
s0 = s1;

View File

@ -1,17 +1,26 @@
import Fraction from "../pkg/fractionjs.js";
import {compose} from "../pkg/ramda.js";
const removeUndefineds = (xs) => xs.filter((x) => x != void 0);
const flatten = (arr) => [].concat(...arr);
const id = (a) => a;
function curry(func) {
return function curried(...args) {
export function curry(func, overload) {
const fn = function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args);
} else {
return function(...args2) {
const partial = function(...args2) {
return curried.apply(this, args.concat(args2));
};
if (overload) {
overload(partial, args);
}
return partial;
}
};
if (overload) {
overload(fn, []);
}
return fn;
}
Fraction.prototype.sam = function() {
return Fraction(Math.floor(this));
@ -73,6 +82,9 @@ class TimeSpan {
withTime(func_time) {
return new TimeSpan(func_time(this.begin), func_time(this.end));
}
withEnd(func_time) {
return new TimeSpan(this.begin, func_time(this.end));
}
intersection(other) {
const intersect_begin = this.begin.max(other.begin);
const intersect_end = this.end.min(other.end);
@ -132,8 +144,16 @@ class Hap {
}
}
class Pattern {
constructor(query2) {
this.query = query2;
constructor(query) {
this.query = query;
const proto = Object.getPrototypeOf(this);
proto.patternified.forEach((prop) => {
this[prop] = (...args) => this._patternify(Pattern.prototype["_" + prop])(...args);
Object.assign(this[prop], Object.fromEntries(Object.entries(Pattern.prototype.factories).map(([type, func]) => [
type,
(...args) => this[prop](func(...args))
])));
});
}
_splitQueries() {
const pat = this;
@ -172,10 +192,10 @@ class Pattern {
}
_appWhole(whole_func, pat_val) {
const pat_func = this;
query = function(span) {
const query = function(span) {
const event_funcs = pat_func.query(span);
const event_vals = pat_val.query(span);
apply = function(event_func, event_val) {
const apply = function(event_func, event_val) {
const s = event_func.part.intersection(event_val.part);
if (s == void 0) {
return void 0;
@ -188,7 +208,7 @@ class Pattern {
}
appBoth(pat_val) {
const whole_func = function(span_a, span_b) {
if (span_a == void 0 || span_B == void 0) {
if (span_a == void 0 || span_b == void 0) {
return void 0;
}
return span_a.intersection_e(span_b);
@ -197,7 +217,7 @@ class Pattern {
}
appLeft(pat_val) {
const pat_func = this;
const query2 = function(span) {
const query = function(span) {
const haps = [];
for (const hap_func of pat_func.query(span)) {
const event_vals = pat_val.query(hap_func.part);
@ -211,11 +231,11 @@ class Pattern {
}
return haps;
};
return new Pattern(query2);
return new Pattern(query);
}
appRight(pat_val) {
const pat_func = this;
const query2 = function(span) {
const query = function(span) {
const haps = [];
for (const hap_val of pat_val.query(span)) {
const hap_funcs = pat_func.query(hap_val.part);
@ -229,7 +249,7 @@ class Pattern {
}
return haps;
};
return new Pattern(query2);
return new Pattern(query);
}
get firstCycle() {
return this.query(new TimeSpan(Fraction(0), Fraction(1)));
@ -251,7 +271,7 @@ class Pattern {
}
_bindWhole(choose_whole, func) {
const pat_val = this;
const query2 = function(span) {
const query = function(span) {
const withWhole = function(a, b) {
return new Hap(choose_whole(a.whole, b.whole), b.part, b.value);
};
@ -260,7 +280,7 @@ class Pattern {
};
return flatten(pat_val.query(span).map((a) => match(a)));
};
return new Pattern(query2);
return new Pattern(query);
}
bind(func) {
const whole_func = function(a, b) {
@ -321,28 +341,16 @@ class Pattern {
const 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) {
const true_pat = binary_pat._filterValues(id);
const false_pat = binary_pat._filterValues((val) => !val);
@ -354,16 +362,17 @@ class Pattern {
return stack([this, func(this._early(time_pat))]);
}
every(n, func) {
const pats = Array(n - 1).fill(this);
pats.unshift(func(this));
return slowcat(...pats);
const pat = this;
const pats = Array(n - 1).fill(pat);
pats.unshift(func(pat));
return slowcatPrime(...pats);
}
append(other) {
return fastcat(...[this, other]);
}
rev() {
const pat = this;
const query2 = function(span) {
const query = function(span) {
const cycle = span.begin.sam();
const next_cycle = span.begin.nextSam();
const reflect = function(to_reflect) {
@ -376,7 +385,7 @@ class Pattern {
const haps = pat.query(reflect(span));
return haps.map((hap) => hap.withSpan(reflect));
};
return new Pattern(query2)._splitQueries();
return new Pattern(query)._splitQueries();
}
jux(func, by = 1) {
by /= 2;
@ -390,37 +399,70 @@ class Pattern {
const right = this.withValue((val) => Object.assign({}, val, {pan: elem_or(val, "pan", 0.5) + by}));
return stack([left, func(right)]);
}
stack(...pats) {
return stack(this, ...pats);
}
sequence(...pats) {
return sequence(this, ...pats);
}
superimpose(...funcs) {
return this.stack(...funcs.map((func) => func(this)));
}
edit(...funcs) {
return stack(...funcs.map((func) => func(this)));
}
_bypass(on2) {
on2 = Boolean(parseInt(on2));
return on2 ? silence : this;
}
hush() {
return silence;
}
}
Pattern.prototype.patternified = ["fast", "slow", "early", "late"];
Pattern.prototype.factories = {pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
const silence = new Pattern((_) => []);
function pure(value) {
function query2(span) {
function query(span) {
return span.spanCycles.map((subspan) => new Hap(Fraction(subspan.begin).wholeCycle(), subspan, value));
}
return new Pattern(query2);
return new Pattern(query);
}
function steady(value) {
return new Pattern((span) => Hap(void 0, span, value));
}
function reify(thing) {
if (thing.constructor.name == "Pattern") {
if (thing?.constructor?.name == "Pattern") {
return thing;
}
return pure(thing);
}
function stack(...pats) {
const reified = pats.map((pat) => reify(pat));
const query2 = (span) => flatten(reified.map((pat) => pat.query(span)));
return new Pattern(query2);
const query = (span) => flatten(reified.map((pat) => pat.query(span)));
return new Pattern(query);
}
function slowcat(...pats) {
pats = pats.map(reify);
const query2 = function(span) {
const query = function(span) {
const pat_n = Math.floor(span.begin) % pats.length;
const pat = pats[pat_n];
if (!pat) {
return [];
}
const offset = span.begin.floor().sub(span.begin.div(pats.length).floor());
return pat.withEventTime((t) => t.add(offset)).query(span.withTime((t) => t.sub(offset)));
};
return new Pattern(query2)._splitQueries();
return new Pattern(query)._splitQueries();
}
function slowcatPrime(...pats) {
pats = pats.map(reify);
const query = function(span) {
const pat_n = Math.floor(span.begin) % pats.length;
const pat = pats[pat_n];
return pat.query(span);
};
return new Pattern(query)._splitQueries();
}
function fastcat(...pats) {
return slowcat(...pats)._fast(pats.length);
@ -493,6 +535,46 @@ const slow = curry((a, pat) => pat.slow(a));
const early = curry((a, pat) => pat.early(a));
const late = curry((a, pat) => pat.late(a));
const rev = (pat) => pat.rev();
const add = curry((a, pat) => pat.add(a));
const sub = curry((a, pat) => pat.sub(a));
const mul = curry((a, pat) => pat.mul(a));
const div = curry((a, pat) => pat.div(a));
const union = curry((a, pat) => pat.union(a));
const every = curry((i, f, pat) => pat.every(i, f));
const when = curry((binary, f, pat) => pat.when(binary, f));
const off = curry((t, f, pat) => pat.off(t, f));
const jux = curry((f, pat) => pat.jux(f));
const append = curry((a, pat) => pat.append(a));
const superimpose = curry((array, pat) => pat.superimpose(...array));
Pattern.prototype.composable = {fast, slow, early, late, superimpose};
export function makeComposable(func) {
Object.entries(Pattern.prototype.composable).forEach(([functionName, composable]) => {
func[functionName] = (...args) => {
const composition = compose(func, composable(...args));
return makeComposable(composition);
};
});
return func;
}
Pattern.prototype.define = (name, func, options = {}) => {
if (options.composable) {
Pattern.prototype.composable[name] = func;
}
if (options.patternified) {
Pattern.prototype.patternified = Pattern.prototype.patternified.concat([name]);
}
};
Pattern.prototype.define("hush", (pat) => pat.hush(), {patternified: false, composable: true});
Pattern.prototype.define("bypass", (pat) => pat.bypass(on), {patternified: true, composable: true});
Pattern.prototype.bootstrap = () => {
const bootstrapped = Object.fromEntries(Object.entries(Pattern.prototype.composable).map(([functionName, composable]) => {
if (Pattern.prototype[functionName]) {
Pattern.prototype[functionName] = makeComposable(Pattern.prototype[functionName]);
}
return [functionName, curry(composable, makeComposable)];
}));
return bootstrapped;
};
export {
Fraction,
TimeSpan,
@ -515,5 +597,16 @@ export {
slow,
early,
late,
rev
rev,
add,
sub,
mul,
div,
union,
every,
when,
off,
jux,
append,
superimpose
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,828 @@
import { c as createCommonjsModule, a as commonjsGlobal, g as getDefaultExportFromCjs } from './common/_commonjsHelpers-8c19dec8.js';
import { n as note, t as transpose$2, d as distance$1, m as modes, c as all, e as tokenizeNote, g as get$2, f as deprecate, h as isSupersetOf, j as all$1, k as isSubsetOf, l as get$3, o as interval, p as compact$1, q as toMidi, r as range$1, s as midiToNoteName, C as Core, u as index$5, v as index$1$1, w as index$1$2, x as index$6, y as index$7, b as index$8, z as index$9, A as index$a, B as index$1$3, a as index$b, D as index$c, i as index$d, E as accToAlt, F as altToAcc, G as coordToInterval, H as coordToNote, I as decode, J as encode, K as fillStr$1, L as isNamed, M as isPitch, N as stepToLetter, O as tokenizeInterval } from './common/index.es-d2606df1.js';
const fillStr = (character, times) => Array(times + 1).join(character);
const REGEX = /^(_{1,}|=|\^{1,}|)([abcdefgABCDEFG])([,']*)$/;
function tokenize(str) {
const m = REGEX.exec(str);
if (!m) {
return ["", "", ""];
}
return [m[1], m[2], m[3]];
}
/**
* Convert a (string) note in ABC notation into a (string) note in scientific notation
*
* @example
* abcToScientificNotation("c") // => "C5"
*/
function abcToScientificNotation(str) {
const [acc, letter, oct] = tokenize(str);
if (letter === "") {
return "";
}
let o = 4;
for (let i = 0; i < oct.length; i++) {
o += oct.charAt(i) === "," ? -1 : 1;
}
const a = acc[0] === "_"
? acc.replace(/_/g, "b")
: acc[0] === "^"
? acc.replace(/\^/g, "#")
: "";
return letter.charCodeAt(0) > 96
? letter.toUpperCase() + a + (o + 1)
: letter + a + o;
}
/**
* Convert a (string) note in scientific notation into a (string) note in ABC notation
*
* @example
* scientificToAbcNotation("C#4") // => "^C"
*/
function scientificToAbcNotation(str) {
const n = note(str);
if (n.empty || (!n.oct && n.oct !== 0)) {
return "";
}
const { letter, acc, oct } = n;
const a = acc[0] === "b" ? acc.replace(/b/g, "_") : acc.replace(/#/g, "^");
const l = oct > 4 ? letter.toLowerCase() : letter;
const o = oct === 5 ? "" : oct > 4 ? fillStr("'", oct - 5) : fillStr(",", 4 - oct);
return a + l + o;
}
function transpose(note, interval) {
return scientificToAbcNotation(transpose$2(abcToScientificNotation(note), interval));
}
function distance(from, to) {
return distance$1(abcToScientificNotation(from), abcToScientificNotation(to));
}
var index = {
abcToScientificNotation,
scientificToAbcNotation,
tokenize,
transpose,
distance,
};
// 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<number>}
*
* @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} array
* @return {Array} the rotated array
*
* @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 array with the null values removed
* @function
* @param {Array} array
* @return {Array}
*
* @example
* compact(["a", "b", null, "c"]) // => ["a", "b", "c"]
*/
function compact(arr) {
return arr.filter((n) => n === 0 || n);
}
/**
* Sort an array of notes in ascending order. Pitch classes are listed
* before notes. Any string that is not a note is removed.
*
* @param {string[]} notes
* @return {string[]} sorted array of notes
*
* @example
* sortedNoteNames(['c2', 'c5', 'c1', 'c0', 'c6', 'c'])
* // => ['C', 'C0', 'C1', 'C2', 'C5', 'C6']
* sortedNoteNames(['c', 'F', 'G', 'a', 'b', 'h', 'J'])
* // => ['C', 'F', 'G', 'A', 'B']
*/
function sortedNoteNames(notes) {
const valid = notes.map((n) => note(n)).filter((n) => !n.empty);
return valid.sort((a, b) => a.height - b.height).map((n) => n.name);
}
/**
* Get sorted notes with duplicates removed. Pitch classes are listed
* before notes.
*
* @function
* @param {string[]} array
* @return {string[]} unique sorted notes
*
* @example
* Array.sortedUniqNoteNames(['a', 'b', 'c2', '1p', 'p2', 'c2', 'b', 'c', 'c3' ])
* // => [ 'C', 'A', 'B', 'C2', 'C3' ]
*/
function sortedUniqNoteNames(arr) {
return sortedNoteNames(arr).filter((n, i, a) => i === 0 || n !== a[i - 1]);
}
/**
* Randomizes the order of the specified array in-place, using the FisherYates shuffle.
*
* @function
* @param {Array} array
* @return {Array} the array shuffled
*
* @example
* shuffle(["C", "D", "E", "F"]) // => [...]
*/
function shuffle(arr, rnd = Math.random) {
let i;
let t;
let m = arr.length;
while (m) {
i = Math.floor(rnd() * m--);
t = arr[m];
arr[m] = arr[i];
arr[i] = t;
}
return arr;
}
/**
* Get all permutations of an array
*
* @param {Array} array - the array
* @return {Array<Array>} an array with all the permutations
* @example
* permutations(["a", "b", "c"])) // =>
* [
* ["a", "b", "c"],
* ["b", "a", "c"],
* ["b", "c", "a"],
* ["a", "c", "b"],
* ["c", "a", "b"],
* ["c", "b", "a"]
* ]
*/
function permutations(arr) {
if (arr.length === 0) {
return [[]];
}
return permutations(arr.slice(1)).reduce((acc, perm) => {
return acc.concat(arr.map((e, pos) => {
const newPerm = perm.slice();
newPerm.splice(pos, 0, arr[0]);
return newPerm;
}));
}, []);
}
var index_es = /*#__PURE__*/Object.freeze({
__proto__: null,
compact: compact,
permutations: permutations,
range: range,
rotate: rotate,
shuffle: shuffle,
sortedNoteNames: sortedNoteNames,
sortedUniqNoteNames: sortedUniqNoteNames
});
const namedSet = (notes) => {
const pcToName = notes.reduce((record, n) => {
const chroma = note(n).chroma;
if (chroma !== undefined) {
record[chroma] = record[chroma] || note(n).name;
}
return record;
}, {});
return (chroma) => pcToName[chroma];
};
function detect(source) {
const notes = source.map((n) => note(n).pc).filter((x) => x);
if (note.length === 0) {
return [];
}
const found = findExactMatches(notes, 1);
return found
.filter((chord) => chord.weight)
.sort((a, b) => b.weight - a.weight)
.map((chord) => chord.name);
}
function findExactMatches(notes, weight) {
const tonic = notes[0];
const tonicChroma = note(tonic).chroma;
const noteName = namedSet(notes);
// we need to test all chormas to get the correct baseNote
const allModes = modes(notes, false);
const found = [];
allModes.forEach((mode, index) => {
// some chords could have the same chroma but different interval spelling
const chordTypes = all().filter((chordType) => chordType.chroma === mode);
chordTypes.forEach((chordType) => {
const chordName = chordType.aliases[0];
const baseNote = noteName(index);
const isInversion = index !== tonicChroma;
if (isInversion) {
found.push({
weight: 0.5 * weight,
name: `${baseNote}${chordName}/${tonic}`,
});
}
else {
found.push({ weight: 1 * weight, name: `${baseNote}${chordName}` });
}
});
});
return found;
}
const NoChord = {
empty: true,
name: "",
symbol: "",
root: "",
rootDegree: 0,
type: "",
tonic: null,
setNum: NaN,
quality: "Unknown",
chroma: "",
normalized: "",
aliases: [],
notes: [],
intervals: [],
};
// 6, 64, 7, 9, 11 and 13 are consider part of the chord
// (see https://github.com/danigb/tonal/issues/55)
const NUM_TYPES = /^(6|64|7|9|11|13)$/;
/**
* Tokenize a chord name. It returns an array with the tonic and chord type
* If not tonic is found, all the name is considered the chord name.
*
* This function does NOT check if the chord type exists or not. It only tries
* to split the tonic and chord type.
*
* @function
* @param {string} name - the chord name
* @return {Array} an array with [tonic, type]
* @example
* tokenize("Cmaj7") // => [ "C", "maj7" ]
* tokenize("C7") // => [ "C", "7" ]
* tokenize("mMaj7") // => [ null, "mMaj7" ]
* tokenize("Cnonsense") // => [ null, "nonsense" ]
*/
function tokenize$1(name) {
const [letter, acc, oct, type] = tokenizeNote(name);
if (letter === "") {
return ["", name];
}
// aug is augmented (see https://github.com/danigb/tonal/issues/55)
if (letter === "A" && type === "ug") {
return ["", "aug"];
}
// see: https://github.com/tonaljs/tonal/issues/70
if (!type && (oct === "4" || oct === "5")) {
return [letter + acc, oct];
}
if (NUM_TYPES.test(oct)) {
return [letter + acc, oct + type];
}
else {
return [letter + acc + oct, type];
}
}
/**
* Get a Chord from a chord name.
*/
function get(src) {
if (src === "") {
return NoChord;
}
if (Array.isArray(src) && src.length === 2) {
return getChord(src[1], src[0]);
}
else {
const [tonic, type] = tokenize$1(src);
const chord = getChord(type, tonic);
return chord.empty ? getChord(src) : chord;
}
}
/**
* Get chord properties
*
* @param typeName - the chord type name
* @param [tonic] - Optional tonic
* @param [root] - Optional root (requires a tonic)
*/
function getChord(typeName, optionalTonic, optionalRoot) {
const type = get$2(typeName);
const tonic = note(optionalTonic || "");
const root = note(optionalRoot || "");
if (type.empty ||
(optionalTonic && tonic.empty) ||
(optionalRoot && root.empty)) {
return NoChord;
}
const rootInterval = distance$1(tonic.pc, root.pc);
const rootDegree = type.intervals.indexOf(rootInterval) + 1;
if (!root.empty && !rootDegree) {
return NoChord;
}
const intervals = Array.from(type.intervals);
for (let i = 1; i < rootDegree; i++) {
const num = intervals[0][0];
const quality = intervals[0][1];
const newNum = parseInt(num, 10) + 7;
intervals.push(`${newNum}${quality}`);
intervals.shift();
}
const notes = tonic.empty
? []
: intervals.map((i) => transpose$2(tonic, i));
typeName = type.aliases.indexOf(typeName) !== -1 ? typeName : type.aliases[0];
const symbol = `${tonic.empty ? "" : tonic.pc}${typeName}${root.empty || rootDegree <= 1 ? "" : "/" + root.pc}`;
const name = `${optionalTonic ? tonic.pc + " " : ""}${type.name}${rootDegree > 1 && optionalRoot ? " over " + root.pc : ""}`;
return {
...type,
name,
symbol,
type: type.name,
root: root.name,
intervals,
rootDegree,
tonic: tonic.name,
notes,
};
}
const chord = deprecate("Chord.chord", "Chord.get", get);
/**
* Transpose a chord name
*
* @param {string} chordName - the chord name
* @return {string} the transposed chord
*
* @example
* transpose('Dm7', 'P4') // => 'Gm7
*/
function transpose$1(chordName, interval) {
const [tonic, type] = tokenize$1(chordName);
if (!tonic) {
return chordName;
}
return transpose$2(tonic, interval) + type;
}
/**
* Get all scales where the given chord fits
*
* @example
* chordScales('C7b9')
* // => ["phrygian dominant", "flamenco", "spanish heptatonic", "half-whole diminished", "chromatic"]
*/
function chordScales(name) {
const s = get(name);
const isChordIncluded = isSupersetOf(s.chroma);
return all$1()
.filter((scale) => isChordIncluded(scale.chroma))
.map((scale) => scale.name);
}
/**
* Get all chords names that are a superset of the given one
* (has the same notes and at least one more)
*
* @function
* @example
* extended("CMaj7")
* // => [ 'Cmaj#4', 'Cmaj7#9#11', 'Cmaj9', 'CM7add13', 'Cmaj13', 'Cmaj9#11', 'CM13#11', 'CM7b9' ]
*/
function extended(chordName) {
const s = get(chordName);
const isSuperset = isSupersetOf(s.chroma);
return all()
.filter((chord) => isSuperset(chord.chroma))
.map((chord) => s.tonic + chord.aliases[0]);
}
/**
* Find all chords names that are a subset of the given one
* (has less notes but all from the given chord)
*
* @example
*/
function reduced(chordName) {
const s = get(chordName);
const isSubset = isSubsetOf(s.chroma);
return all()
.filter((chord) => isSubset(chord.chroma))
.map((chord) => s.tonic + chord.aliases[0]);
}
var index$1 = {
getChord,
get,
detect,
chordScales,
extended,
reduced,
tokenize: tokenize$1,
transpose: transpose$1,
// deprecate
chord,
};
/**
* Given a tonic and a chord list expressed with roman numeral notation
* returns the progression expressed with leadsheet chords symbols notation
* @example
* fromRomanNumerals("C", ["I", "IIm7", "V7"]);
* // => ["C", "Dm7", "G7"]
*/
function fromRomanNumerals(tonic, chords) {
const romanNumerals = chords.map(get$3);
return romanNumerals.map((rn) => transpose$2(tonic, interval(rn)) + rn.chordType);
}
/**
* Given a tonic and a chord list with leadsheet symbols notation,
* return the chord list with roman numeral notation
* @example
* toRomanNumerals("C", ["CMaj7", "Dm7", "G7"]);
* // => ["IMaj7", "IIm7", "V7"]
*/
function toRomanNumerals(tonic, chords) {
return chords.map((chord) => {
const [note, chordType] = tokenize$1(chord);
const intervalName = distance$1(tonic, note);
const roman = get$3(interval(intervalName));
return roman.name + chordType;
});
}
var index$2 = { fromRomanNumerals, toRomanNumerals };
/**
* Create a numeric range. You supply a list of notes or numbers and it will
* be connected to create complex ranges.
*
* @param {Array} notes - the list of notes or midi numbers used
* @return {Array} an array of numbers or empty array if not valid parameters
*
* @example
* numeric(["C5", "C4"]) // => [ 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60 ]
* // it works midi notes
* numeric([10, 5]) // => [ 10, 9, 8, 7, 6, 5 ]
* // complex range
* numeric(["C4", "E4", "Bb3"]) // => [60, 61, 62, 63, 64, 63, 62, 61, 60, 59, 58]
*/
function numeric(notes) {
const midi = compact$1(notes.map(toMidi));
if (!notes.length || midi.length !== notes.length) {
// there is no valid notes
return [];
}
return midi.reduce((result, note) => {
const last = result[result.length - 1];
return result.concat(range$1(last, note).slice(1));
}, [midi[0]]);
}
/**
* Create a range of chromatic notes. The altered notes will use flats.
*
* @function
* @param {Array} notes - the list of notes or midi note numbers to create a range from
* @param {Object} options - The same as `midiToNoteName` (`{ sharps: boolean, pitchClass: boolean }`)
* @return {Array} an array of note names
*
* @example
* Range.chromatic(["C2, "E2", "D2"]) // => ["C2", "Db2", "D2", "Eb2", "E2", "Eb2", "D2"]
* // with sharps
* Range.chromatic(["C2", "C3"], { sharps: true }) // => [ "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", "C3" ]
*/
function chromatic(notes, options) {
return numeric(notes).map((midi) => midiToNoteName(midi, options));
}
var index$3 = { numeric, chromatic };
// CONSTANTS
const NONE = {
empty: true,
name: "",
upper: undefined,
lower: undefined,
type: undefined,
additive: [],
};
const NAMES = ["4/4", "3/4", "2/4", "2/2", "12/8", "9/8", "6/8", "3/8"];
// PUBLIC API
function names() {
return NAMES.slice();
}
const REGEX$1 = /^(\d?\d(?:\+\d)*)\/(\d)$/;
const CACHE = new Map();
function get$1(literal) {
const cached = CACHE.get(literal);
if (cached) {
return cached;
}
const ts = build(parse(literal));
CACHE.set(literal, ts);
return ts;
}
function parse(literal) {
if (typeof literal === "string") {
const [_, up, low] = REGEX$1.exec(literal) || [];
return parse([up, low]);
}
const [up, down] = literal;
const denominator = +down;
if (typeof up === "number") {
return [up, denominator];
}
const list = up.split("+").map((n) => +n);
return list.length === 1 ? [list[0], denominator] : [list, denominator];
}
var index$4 = { names, parse, get: get$1 };
// PRIVATE
function build([up, down]) {
const upper = Array.isArray(up) ? up.reduce((a, b) => a + b, 0) : up;
const lower = down;
if (upper === 0 || lower === 0) {
return NONE;
}
const name = Array.isArray(up) ? `${up.join("+")}/${down}` : `${up}/${down}`;
const additive = Array.isArray(up) ? up : [];
const type = lower === 4 || lower === 2
? "simple"
: lower === 8 && upper % 3 === 0
? "compound"
: "irregular";
return {
empty: false,
name,
type,
upper,
lower,
additive,
};
}
// deprecated (backwards compatibility)
const Tonal = Core;
const PcSet = index$5;
const ChordDictionary = index$1$1;
const ScaleDictionary = index$1$2;
var index_es$1 = /*#__PURE__*/Object.freeze({
__proto__: null,
Array: index_es,
Core: Core,
ChordDictionary: ChordDictionary,
PcSet: PcSet,
ScaleDictionary: ScaleDictionary,
Tonal: Tonal,
AbcNotation: index,
Chord: index$1,
ChordType: index$1$1,
Collection: index$6,
DurationValue: index$7,
Interval: index$8,
Key: index$9,
Midi: index$a,
Mode: index$1$3,
Note: index$b,
Pcset: index$5,
Progression: index$2,
Range: index$3,
RomanNumeral: index$c,
Scale: index$d,
ScaleType: index$1$2,
TimeSignature: index$4,
accToAlt: accToAlt,
altToAcc: altToAcc,
coordToInterval: coordToInterval,
coordToNote: coordToNote,
decode: decode,
deprecate: deprecate,
distance: distance$1,
encode: encode,
fillStr: fillStr$1,
interval: interval,
isNamed: isNamed,
isPitch: isPitch,
note: note,
stepToLetter: stepToLetter,
tokenizeInterval: tokenizeInterval,
tokenizeNote: tokenizeNote,
transpose: transpose$2
});
var getBestVoicing_1 = createCommonjsModule(function (module, exports) {
exports.__esModule = true;
exports.getBestVoicing = void 0;
function getBestVoicing(voicingOptions) {
var chord = voicingOptions.chord, range = voicingOptions.range, finder = voicingOptions.finder, picker = voicingOptions.picker, lastVoicing = voicingOptions.lastVoicing;
var voicings = finder(chord, range);
if (!voicings.length) {
return [];
}
return picker(voicings, lastVoicing);
}
exports.getBestVoicing = getBestVoicing;
});
var tokenizeChord_1 = createCommonjsModule(function (module, exports) {
exports.__esModule = true;
exports.tokenizeChord = void 0;
function tokenizeChord(chord) {
var match = (chord || '').match(/^([A-G][b#]*)([^\/]*)[\/]?([A-G][b#]*)?$/);
if (!match) {
// console.warn('could not tokenize chord', chord);
return [];
}
return match.slice(1);
}
exports.tokenizeChord = tokenizeChord;
});
var voicingsInRange_1 = createCommonjsModule(function (module, exports) {
exports.__esModule = true;
exports.voicingsInRange = void 0;
function voicingsInRange(chord, dictionary, range) {
if (dictionary === void 0) { dictionary = dictionaryVoicing_1.lefthand; }
if (range === void 0) { range = ['D3', 'A4']; }
var _a = (0, tokenizeChord_1.tokenizeChord)(chord), tonic = _a[0], symbol = _a[1];
if (!dictionary[symbol]) {
return [];
}
// resolve array of interval arrays for the wanted symbol
var voicings = dictionary[symbol].map(function (intervals) { return intervals.split(' '); });
var notesInRange = index_es$1.Range.chromatic(range); // gives array of notes inside range
return voicings.reduce(function (voiced, voicing) {
// transpose intervals relative to first interval (e.g. 3m 5P > 1P 3M)
var relativeIntervals = voicing.map(function (interval) { return index_es$1.Interval.substract(interval, voicing[0]); });
// get enharmonic correct pitch class the bottom note
var bottomPitchClass = index_es$1.Note.transpose(tonic, voicing[0]);
// get all possible start notes for voicing
var starts = notesInRange
// only get the start notes:
.filter(function (note) { return index_es$1.Note.chroma(note) === index_es$1.Note.chroma(bottomPitchClass); })
// filter out start notes that will overshoot the top end of the range
.filter(function (note) {
return index_es$1.Note.midi(index_es$1.Note.transpose(note, relativeIntervals[relativeIntervals.length - 1])) <= index_es$1.Note.midi(range[1]);
})
// replace Range.chromatic notes with the correct enharmonic equivalents
.map(function (note) { return index_es$1.Note.enharmonic(note, bottomPitchClass); });
// render one voicing for each start note
var notes = starts.map(function (start) { return relativeIntervals.map(function (interval) { return index_es$1.Note.transpose(start, interval); }); });
return voiced.concat(notes);
}, []);
}
exports.voicingsInRange = voicingsInRange;
});
var dictionaryVoicing_1 = createCommonjsModule(function (module, exports) {
var __assign = (commonjsGlobal && commonjsGlobal.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __rest = (commonjsGlobal && commonjsGlobal.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
exports.__esModule = true;
exports.dictionaryVoicing = exports.dictionaryVoicingFinder = exports.triads = exports.guidetones = exports.lefthand = void 0;
exports.lefthand = {
m7: ['3m 5P 7m 9M', '7m 9M 10m 12P'],
'7': ['3M 6M 7m 9M', '7m 9M 10M 13M'],
'^7': ['3M 5P 7M 9M', '7M 9M 10M 12P'],
'69': ['3M 5P 6A 9M'],
m7b5: ['3m 5d 7m 8P', '7m 8P 10m 12d'],
'7b9': ['3M 6m 7m 9m', '7m 9m 10M 13m'],
'7b13': ['3M 6m 7m 9m', '7m 9m 10M 13m'],
o7: ['1P 3m 5d 6M', '5d 6M 8P 10m'],
'7#11': ['7m 9M 11A 13A'],
'7#9': ['3M 7m 9A'],
mM7: ['3m 5P 7M 9M', '7M 9M 10m 12P'],
m6: ['3m 5P 6M 9M', '6M 9M 10m 12P']
};
exports.guidetones = {
m7: ['3m 7m', '7m 10m'],
m9: ['3m 7m', '7m 10m'],
'7': ['3M 7m', '7m 10M'],
'^7': ['3M 7M', '7M 10M'],
'^9': ['3M 7M', '7M 10M'],
'69': ['3M 6M'],
'6': ['3M 6M', '6M 10M'],
m7b5: ['3m 7m', '7m 10m'],
'7b9': ['3M 7m', '7m 10M'],
'7b13': ['3M 7m', '7m 10M'],
o7: ['3m 6M', '6M 10m'],
'7#11': ['3M 7m', '7m 10M'],
'7#9': ['3M 7m', '7m 10M'],
mM7: ['3m 7M', '7M 10m'],
m6: ['3m 6M', '6M 10m']
};
exports.triads = {
M: ['1P 3M 5P', '3M 5P 8P', '5P 8P 10M'],
m: ['1P 3m 5P', '3m 5P 8P', '5P 8P 10m'],
o: ['1P 3m 5d', '3m 5d 8P', '5d 8P 10m'],
aug: ['1P 3m 5A', '3m 5A 8P', '5A 8P 10m']
};
var dictionaryVoicingFinder = function (dictionary) { return function (chordSymbol, range) {
return (0, voicingsInRange_1.voicingsInRange)(chordSymbol, dictionary, range);
}; };
exports.dictionaryVoicingFinder = dictionaryVoicingFinder;
var dictionaryVoicing = function (props) {
var dictionary = props.dictionary, range = props.range, rest = __rest(props, ["dictionary", "range"]);
return (0, getBestVoicing_1.getBestVoicing)(__assign(__assign({}, rest), { range: range, finder: (0, exports.dictionaryVoicingFinder)(dictionary) }));
};
exports.dictionaryVoicing = dictionaryVoicing;
});
var minTopNoteDiff_1 = createCommonjsModule(function (module, exports) {
exports.__esModule = true;
exports.minTopNoteDiff = void 0;
function minTopNoteDiff(voicings, lastVoicing) {
if (!lastVoicing) {
return voicings[0];
}
var diff = function (voicing) {
return Math.abs(index_es$1.Note.midi(lastVoicing[lastVoicing.length - 1]) - index_es$1.Note.midi(voicing[voicing.length - 1]));
};
return voicings.reduce(function (best, current) { return (diff(current) < diff(best) ? current : best); }, voicings[0]);
}
exports.minTopNoteDiff = minTopNoteDiff;
});
var dist = createCommonjsModule(function (module, exports) {
exports.__esModule = true;
exports["default"] = {
tokenizeChord: tokenizeChord_1.tokenizeChord,
getBestVoicing: getBestVoicing_1.getBestVoicing,
dictionaryVoicing: dictionaryVoicing_1.dictionaryVoicing,
dictionaryVoicingFinder: dictionaryVoicing_1.dictionaryVoicingFinder,
lefthand: dictionaryVoicing_1.lefthand,
guidetones: dictionaryVoicing_1.guidetones,
triads: dictionaryVoicing_1.triads,
minTopNoteDiff: minTopNoteDiff_1.minTopNoteDiff
};
});
var __pika_web_default_export_for_treeshaking__ = /*@__PURE__*/getDefaultExportFromCjs(dist);
export default __pika_web_default_export_for_treeshaking__;

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
{
"imports": {
"@tonaljs/tonal": "./@tonaljs/tonal.js",
"chord-voicings": "./chord-voicings.js",
"codemirror/lib/codemirror.css": "./codemirror/lib/codemirror.css",
"codemirror/mode/javascript/javascript.js": "./codemirror/mode/javascript/javascript.js",
"codemirror/mode/pegjs/pegjs.js": "./codemirror/mode/pegjs/pegjs.js",
@ -8,6 +9,7 @@
"estraverse": "./estraverse.js",
"fraction.js": "./fractionjs.js",
"multimap": "./multimap.js",
"ramda": "./ramda.js",
"react": "./react.js",
"react-codemirror2": "./react-codemirror2.js",
"react-dom": "./react-dom.js",

606
docs/_snowpack/pkg/ramda.js Normal file
View File

@ -0,0 +1,606 @@
function _isPlaceholder(a) {
return a != null && typeof a === 'object' && a['@@functional/placeholder'] === true;
}
/**
* Optimized internal one-arity curry function.
*
* @private
* @category Function
* @param {Function} fn The function to curry.
* @return {Function} The curried function.
*/
function _curry1(fn) {
return function f1(a) {
if (arguments.length === 0 || _isPlaceholder(a)) {
return f1;
} else {
return fn.apply(this, arguments);
}
};
}
/**
* Optimized internal two-arity curry function.
*
* @private
* @category Function
* @param {Function} fn The function to curry.
* @return {Function} The curried function.
*/
function _curry2(fn) {
return function f2(a, b) {
switch (arguments.length) {
case 0:
return f2;
case 1:
return _isPlaceholder(a) ? f2 : _curry1(function (_b) {
return fn(a, _b);
});
default:
return _isPlaceholder(a) && _isPlaceholder(b) ? f2 : _isPlaceholder(a) ? _curry1(function (_a) {
return fn(_a, b);
}) : _isPlaceholder(b) ? _curry1(function (_b) {
return fn(a, _b);
}) : fn(a, b);
}
};
}
function _arity(n, fn) {
/* eslint-disable no-unused-vars */
switch (n) {
case 0:
return function () {
return fn.apply(this, arguments);
};
case 1:
return function (a0) {
return fn.apply(this, arguments);
};
case 2:
return function (a0, a1) {
return fn.apply(this, arguments);
};
case 3:
return function (a0, a1, a2) {
return fn.apply(this, arguments);
};
case 4:
return function (a0, a1, a2, a3) {
return fn.apply(this, arguments);
};
case 5:
return function (a0, a1, a2, a3, a4) {
return fn.apply(this, arguments);
};
case 6:
return function (a0, a1, a2, a3, a4, a5) {
return fn.apply(this, arguments);
};
case 7:
return function (a0, a1, a2, a3, a4, a5, a6) {
return fn.apply(this, arguments);
};
case 8:
return function (a0, a1, a2, a3, a4, a5, a6, a7) {
return fn.apply(this, arguments);
};
case 9:
return function (a0, a1, a2, a3, a4, a5, a6, a7, a8) {
return fn.apply(this, arguments);
};
case 10:
return function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9) {
return fn.apply(this, arguments);
};
default:
throw new Error('First argument to _arity must be a non-negative integer no greater than ten');
}
}
/**
* Optimized internal three-arity curry function.
*
* @private
* @category Function
* @param {Function} fn The function to curry.
* @return {Function} The curried function.
*/
function _curry3(fn) {
return function f3(a, b, c) {
switch (arguments.length) {
case 0:
return f3;
case 1:
return _isPlaceholder(a) ? f3 : _curry2(function (_b, _c) {
return fn(a, _b, _c);
});
case 2:
return _isPlaceholder(a) && _isPlaceholder(b) ? f3 : _isPlaceholder(a) ? _curry2(function (_a, _c) {
return fn(_a, b, _c);
}) : _isPlaceholder(b) ? _curry2(function (_b, _c) {
return fn(a, _b, _c);
}) : _curry1(function (_c) {
return fn(a, b, _c);
});
default:
return _isPlaceholder(a) && _isPlaceholder(b) && _isPlaceholder(c) ? f3 : _isPlaceholder(a) && _isPlaceholder(b) ? _curry2(function (_a, _b) {
return fn(_a, _b, c);
}) : _isPlaceholder(a) && _isPlaceholder(c) ? _curry2(function (_a, _c) {
return fn(_a, b, _c);
}) : _isPlaceholder(b) && _isPlaceholder(c) ? _curry2(function (_b, _c) {
return fn(a, _b, _c);
}) : _isPlaceholder(a) ? _curry1(function (_a) {
return fn(_a, b, c);
}) : _isPlaceholder(b) ? _curry1(function (_b) {
return fn(a, _b, c);
}) : _isPlaceholder(c) ? _curry1(function (_c) {
return fn(a, b, _c);
}) : fn(a, b, c);
}
};
}
/**
* Tests whether or not an object is an array.
*
* @private
* @param {*} val The object to test.
* @return {Boolean} `true` if `val` is an array, `false` otherwise.
* @example
*
* _isArray([]); //=> true
* _isArray(null); //=> false
* _isArray({}); //=> false
*/
var _isArray = Array.isArray || function _isArray(val) {
return val != null && val.length >= 0 && Object.prototype.toString.call(val) === '[object Array]';
};
function _isString(x) {
return Object.prototype.toString.call(x) === '[object String]';
}
/**
* Tests whether or not an object is similar to an array.
*
* @private
* @category Type
* @category List
* @sig * -> Boolean
* @param {*} x The object to test.
* @return {Boolean} `true` if `x` has a numeric length property and extreme indices defined; `false` otherwise.
* @example
*
* _isArrayLike([]); //=> true
* _isArrayLike(true); //=> false
* _isArrayLike({}); //=> false
* _isArrayLike({length: 10}); //=> false
* _isArrayLike({0: 'zero', 9: 'nine', length: 10}); //=> true
* _isArrayLike({nodeType: 1, length: 1}) // => false
*/
var _isArrayLike =
/*#__PURE__*/
_curry1(function isArrayLike(x) {
if (_isArray(x)) {
return true;
}
if (!x) {
return false;
}
if (typeof x !== 'object') {
return false;
}
if (_isString(x)) {
return false;
}
if (x.length === 0) {
return true;
}
if (x.length > 0) {
return x.hasOwnProperty(0) && x.hasOwnProperty(x.length - 1);
}
return false;
});
var XWrap =
/*#__PURE__*/
function () {
function XWrap(fn) {
this.f = fn;
}
XWrap.prototype['@@transducer/init'] = function () {
throw new Error('init not implemented on XWrap');
};
XWrap.prototype['@@transducer/result'] = function (acc) {
return acc;
};
XWrap.prototype['@@transducer/step'] = function (acc, x) {
return this.f(acc, x);
};
return XWrap;
}();
function _xwrap(fn) {
return new XWrap(fn);
}
/**
* Creates a function that is bound to a context.
* Note: `R.bind` does not provide the additional argument-binding capabilities of
* [Function.prototype.bind](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
*
* @func
* @memberOf R
* @since v0.6.0
* @category Function
* @category Object
* @sig (* -> *) -> {*} -> (* -> *)
* @param {Function} fn The function to bind to context
* @param {Object} thisObj The context to bind `fn` to
* @return {Function} A function that will execute in the context of `thisObj`.
* @see R.partial
* @example
*
* const log = R.bind(console.log, console);
* R.pipe(R.assoc('a', 2), R.tap(log), R.assoc('a', 3))({a: 1}); //=> {a: 3}
* // logs {a: 2}
* @symb R.bind(f, o)(a, b) = f.call(o, a, b)
*/
var bind =
/*#__PURE__*/
_curry2(function bind(fn, thisObj) {
return _arity(fn.length, function () {
return fn.apply(thisObj, arguments);
});
});
function _arrayReduce(xf, acc, list) {
var idx = 0;
var len = list.length;
while (idx < len) {
acc = xf['@@transducer/step'](acc, list[idx]);
if (acc && acc['@@transducer/reduced']) {
acc = acc['@@transducer/value'];
break;
}
idx += 1;
}
return xf['@@transducer/result'](acc);
}
function _iterableReduce(xf, acc, iter) {
var step = iter.next();
while (!step.done) {
acc = xf['@@transducer/step'](acc, step.value);
if (acc && acc['@@transducer/reduced']) {
acc = acc['@@transducer/value'];
break;
}
step = iter.next();
}
return xf['@@transducer/result'](acc);
}
function _methodReduce(xf, acc, obj, methodName) {
return xf['@@transducer/result'](obj[methodName](bind(xf['@@transducer/step'], xf), acc));
}
var symIterator = typeof Symbol !== 'undefined' ? Symbol.iterator : '@@iterator';
function _reduce(fn, acc, list) {
if (typeof fn === 'function') {
fn = _xwrap(fn);
}
if (_isArrayLike(list)) {
return _arrayReduce(fn, acc, list);
}
if (typeof list['fantasy-land/reduce'] === 'function') {
return _methodReduce(fn, acc, list, 'fantasy-land/reduce');
}
if (list[symIterator] != null) {
return _iterableReduce(fn, acc, list[symIterator]());
}
if (typeof list.next === 'function') {
return _iterableReduce(fn, acc, list);
}
if (typeof list.reduce === 'function') {
return _methodReduce(fn, acc, list, 'reduce');
}
throw new TypeError('reduce: list must be array or iterable');
}
/**
* Returns a single item by iterating through the list, successively calling
* the iterator function and passing it an accumulator value and the current
* value from the array, and then passing the result to the next call.
*
* The iterator function receives two values: *(acc, value)*. It may use
* [`R.reduced`](#reduced) to shortcut the iteration.
*
* The arguments' order of [`reduceRight`](#reduceRight)'s iterator function
* is *(value, acc)*.
*
* Note: `R.reduce` does not skip deleted or unassigned indices (sparse
* arrays), unlike the native `Array.prototype.reduce` method. For more details
* on this behavior, see:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce#Description
*
* Dispatches to the `reduce` method of the third argument, if present. When
* doing so, it is up to the user to handle the [`R.reduced`](#reduced)
* shortcuting, as this is not implemented by `reduce`.
*
* @func
* @memberOf R
* @since v0.1.0
* @category List
* @sig ((a, b) -> a) -> a -> [b] -> a
* @param {Function} fn The iterator function. Receives two values, the accumulator and the
* current element from the array.
* @param {*} acc The accumulator value.
* @param {Array} list The list to iterate over.
* @return {*} The final, accumulated value.
* @see R.reduced, R.addIndex, R.reduceRight
* @example
*
* R.reduce(R.subtract, 0, [1, 2, 3, 4]) // => ((((0 - 1) - 2) - 3) - 4) = -10
* // - -10
* // / \ / \
* // - 4 -6 4
* // / \ / \
* // - 3 ==> -3 3
* // / \ / \
* // - 2 -1 2
* // / \ / \
* // 0 1 0 1
*
* @symb R.reduce(f, a, [b, c, d]) = f(f(f(a, b), c), d)
*/
var reduce =
/*#__PURE__*/
_curry3(_reduce);
function _pipe(f, g) {
return function () {
return g.call(this, f.apply(this, arguments));
};
}
/**
* This checks whether a function has a [methodname] function. If it isn't an
* array it will execute that function otherwise it will default to the ramda
* implementation.
*
* @private
* @param {Function} fn ramda implementation
* @param {String} methodname property to check for a custom implementation
* @return {Object} Whatever the return value of the method is.
*/
function _checkForMethod(methodname, fn) {
return function () {
var length = arguments.length;
if (length === 0) {
return fn();
}
var obj = arguments[length - 1];
return _isArray(obj) || typeof obj[methodname] !== 'function' ? fn.apply(this, arguments) : obj[methodname].apply(obj, Array.prototype.slice.call(arguments, 0, length - 1));
};
}
/**
* Returns the elements of the given list or string (or object with a `slice`
* method) from `fromIndex` (inclusive) to `toIndex` (exclusive).
*
* Dispatches to the `slice` method of the third argument, if present.
*
* @func
* @memberOf R
* @since v0.1.4
* @category List
* @sig Number -> Number -> [a] -> [a]
* @sig Number -> Number -> String -> String
* @param {Number} fromIndex The start index (inclusive).
* @param {Number} toIndex The end index (exclusive).
* @param {*} list
* @return {*}
* @example
*
* R.slice(1, 3, ['a', 'b', 'c', 'd']); //=> ['b', 'c']
* R.slice(1, Infinity, ['a', 'b', 'c', 'd']); //=> ['b', 'c', 'd']
* R.slice(0, -1, ['a', 'b', 'c', 'd']); //=> ['a', 'b', 'c']
* R.slice(-3, -1, ['a', 'b', 'c', 'd']); //=> ['b', 'c']
* R.slice(0, 3, 'ramda'); //=> 'ram'
*/
var slice =
/*#__PURE__*/
_curry3(
/*#__PURE__*/
_checkForMethod('slice', function slice(fromIndex, toIndex, list) {
return Array.prototype.slice.call(list, fromIndex, toIndex);
}));
/**
* Returns all but the first element of the given list or string (or object
* with a `tail` method).
*
* Dispatches to the `slice` method of the first argument, if present.
*
* @func
* @memberOf R
* @since v0.1.0
* @category List
* @sig [a] -> [a]
* @sig String -> String
* @param {*} list
* @return {*}
* @see R.head, R.init, R.last
* @example
*
* R.tail([1, 2, 3]); //=> [2, 3]
* R.tail([1, 2]); //=> [2]
* R.tail([1]); //=> []
* R.tail([]); //=> []
*
* R.tail('abc'); //=> 'bc'
* R.tail('ab'); //=> 'b'
* R.tail('a'); //=> ''
* R.tail(''); //=> ''
*/
var tail =
/*#__PURE__*/
_curry1(
/*#__PURE__*/
_checkForMethod('tail',
/*#__PURE__*/
slice(1, Infinity)));
/**
* Performs left-to-right function composition. The first argument may have
* any arity; the remaining arguments must be unary.
*
* In some libraries this function is named `sequence`.
*
* **Note:** The result of pipe is not automatically curried.
*
* @func
* @memberOf R
* @since v0.1.0
* @category Function
* @sig (((a, b, ..., n) -> o), (o -> p), ..., (x -> y), (y -> z)) -> ((a, b, ..., n) -> z)
* @param {...Function} functions
* @return {Function}
* @see R.compose
* @example
*
* const f = R.pipe(Math.pow, R.negate, R.inc);
*
* f(3, 4); // -(3^4) + 1
* @symb R.pipe(f, g, h)(a, b) = h(g(f(a, b)))
* @symb R.pipe(f, g, h)(a)(b) = h(g(f(a)))(b)
*/
function pipe() {
if (arguments.length === 0) {
throw new Error('pipe requires at least one argument');
}
return _arity(arguments[0].length, reduce(_pipe, arguments[0], tail(arguments)));
}
/**
* Returns a new list or string with the elements or characters in reverse
* order.
*
* @func
* @memberOf R
* @since v0.1.0
* @category List
* @sig [a] -> [a]
* @sig String -> String
* @param {Array|String} list
* @return {Array|String}
* @example
*
* R.reverse([1, 2, 3]); //=> [3, 2, 1]
* R.reverse([1, 2]); //=> [2, 1]
* R.reverse([1]); //=> [1]
* R.reverse([]); //=> []
*
* R.reverse('abc'); //=> 'cba'
* R.reverse('ab'); //=> 'ba'
* R.reverse('a'); //=> 'a'
* R.reverse(''); //=> ''
*/
var reverse =
/*#__PURE__*/
_curry1(function reverse(list) {
return _isString(list) ? list.split('').reverse().join('') : Array.prototype.slice.call(list, 0).reverse();
});
/**
* Performs right-to-left function composition. The last argument may have
* any arity; the remaining arguments must be unary.
*
* **Note:** The result of compose is not automatically curried.
*
* @func
* @memberOf R
* @since v0.1.0
* @category Function
* @sig ((y -> z), (x -> y), ..., (o -> p), ((a, b, ..., n) -> o)) -> ((a, b, ..., n) -> z)
* @param {...Function} ...functions The functions to compose
* @return {Function}
* @see R.pipe
* @example
*
* const classyGreeting = (firstName, lastName) => "The name's " + lastName + ", " + firstName + " " + lastName
* const yellGreeting = R.compose(R.toUpper, classyGreeting);
* yellGreeting('James', 'Bond'); //=> "THE NAME'S BOND, JAMES BOND"
*
* R.compose(Math.abs, R.add(1), R.multiply(2))(-4) //=> 7
*
* @symb R.compose(f, g, h)(a, b) = f(g(h(a, b)))
* @symb R.compose(f, g, h)(a)(b) = f(g(h(a)))(b)
*/
function compose() {
if (arguments.length === 0) {
throw new Error('compose requires at least one argument');
}
return pipe.apply(this, reverse(arguments));
}
export { compose };

View File

@ -8,6 +8,7 @@ import './common/index-d01087d6.js';
var useCallback = react.useCallback;
var useEffect = react.useEffect;
var useLayoutEffect = react.useLayoutEffect;
var useMemo = react.useMemo;
var useRef = react.useRef;
var useState = react.useState;
export { useCallback, useEffect, useLayoutEffect, useRef, useState };
export { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState };

174
docs/dist/App.js vendored
View File

@ -1,114 +1,57 @@
import React, {useCallback, useEffect, useLayoutEffect, useRef, useState} from "../_snowpack/pkg/react.js";
import logo from "./logo.svg.proxy.js";
import cx from "./cx.js";
import React, {useCallback, useLayoutEffect, useRef} from "../_snowpack/pkg/react.js";
import * as Tone from "../_snowpack/pkg/tone.js";
import useCycle from "./useCycle.js";
import * as tunes from "./tunes.js";
import * as parser from "./parse.js";
import CodeMirror from "./CodeMirror.js";
import hot from "../hot.js";
import {isNote} from "../_snowpack/pkg/tone.js";
import cx from "./cx.js";
import {evaluate} from "./evaluate.js";
import logo from "./logo.svg.proxy.js";
import {useWebMidi} from "./midi.js";
const {tetris, tetrisRev, shapeShifted} = tunes;
const {parse} = parser;
const getHotCode = async () => {
return fetch("/hot.js").then((res) => res.text()).then((src) => {
return src.split("export default").slice(-1)[0].trim();
});
};
const defaultSynth = new Tone.PolySynth().toDestination();
import * as tunes from "./tunes.js";
import useRepl from "./useRepl.js";
const [_, codeParam] = window.location.href.split("#");
let decoded;
try {
decoded = atob(decodeURIComponent(codeParam || ""));
} catch (err) {
console.warn("failed to decode", err);
}
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination);
defaultSynth.set({
oscillator: {type: "triangle"},
envelope: {
release: 0.01
}
});
function getRandomTune() {
const allTunes = Object.values(tunes);
const randomItem = (arr) => arr[Math.floor(Math.random() * arr.length)];
return randomItem(allTunes);
}
const randomTune = getRandomTune();
function App() {
const [mode, setMode] = useState("javascript");
const [code, setCode] = useState(shapeShifted);
const [log, setLog] = useState("");
const logBox = useRef();
const [error, setError] = useState();
const [pattern, setPattern] = useState();
const [activePattern, setActivePattern] = useState();
const [isHot, setIsHot] = useState(false);
const pushLog = (message) => setLog((log2) => log2 + `${log2 ? "\n\n" : ""}${message}`);
const logCycle = (_events, cycle2) => {
if (_events.length) {
pushLog(`# cycle ${cycle2}
` + _events.map((e) => e.show()).join("\n"));
}
};
const cycle = useCycle({
onEvent: useCallback((time, event) => {
try {
if (typeof event.value === "string") {
if (!isNote(event.value)) {
throw new Error("not a note: " + event.value);
}
defaultSynth.triggerAttackRelease(event.value, event.duration, time);
} else {
const {onTrigger} = event.value;
onTrigger(time, event);
}
} catch (err) {
console.warn(err);
err.message = "unplayable event: " + err?.message;
pushLog(err.message);
}
}, []),
onQuery: useCallback((span) => {
try {
return activePattern?.query(span) || [];
} catch (err) {
setError(err);
return [];
}
}, [activePattern]),
onSchedule: useCallback((_events, cycle2) => logCycle(_events, cycle2), [activePattern]),
ready: !!activePattern
const {setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog} = useRepl({
tune: decoded || randomTune,
defaultSynth
});
const logBox = useRef();
useLayoutEffect(() => {
logBox.current.scrollTop = logBox.current?.scrollHeight;
}, [log]);
useLayoutEffect(() => {
const handleKeyPress = (e) => {
if (e.ctrlKey && e.code === "Enter") {
setActivePattern(() => pattern);
!cycle.started && cycle.start();
if (e.ctrlKey || e.altKey) {
switch (e.code) {
case "Enter":
activateCode();
!cycle.started && cycle.start();
break;
case "Period":
cycle.stop();
}
}
};
document.addEventListener("keypress", handleKeyPress);
return () => document.removeEventListener("keypress", handleKeyPress);
}, [pattern]);
useEffect(() => {
let _code = code;
if (isHot) {
if (typeof hot !== "string") {
getHotCode().then((_code2) => {
setCode(_code2);
setMode("javascript");
});
setActivePattern(hot);
return;
} else {
_code = hot;
setCode(_code);
}
}
try {
const parsed = parse(_code);
setPattern(() => parsed.pattern);
if (!activePattern || isHot) {
setActivePattern(() => parsed.pattern);
}
setMode(parsed.mode);
setError(void 0);
} catch (err) {
console.warn(err);
setError(err);
}
}, [code, isHot]);
useLayoutEffect(() => {
logBox.current.scrollTop = logBox.current?.scrollHeight;
}, [log]);
}, [pattern, code]);
useWebMidi({
ready: useCallback(({outputs}) => {
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `"${o.name}"`).join(" | ")}) to the pattern. `);
@ -121,7 +64,7 @@ function App() {
}, [])
});
return /* @__PURE__ */ React.createElement("div", {
className: "h-screen bg-slate-900 flex flex-col"
className: "min-h-screen bg-[#2A3236] flex flex-col"
}, /* @__PURE__ */ React.createElement("header", {
className: "flex-none w-full h-16 px-2 flex border-b border-gray-200 bg-white justify-between"
}, /* @__PURE__ */ React.createElement("div", {
@ -132,42 +75,43 @@ function App() {
alt: "logo"
}), /* @__PURE__ */ React.createElement("h1", {
className: "text-2xl"
}, "Strudel REPL")), window.location.href.includes("http://localhost:8080") && /* @__PURE__ */ React.createElement("button", {
}, "Strudel REPL")), /* @__PURE__ */ React.createElement("div", {
className: "flex space-x-4"
}, /* @__PURE__ */ React.createElement("button", {
onClick: () => {
if (isHot || confirm("Really switch? You might loose your current pattern..")) {
setIsHot((h) => !h);
}
const _code = getRandomTune();
console.log("tune", _code);
setCode(_code);
const parsed = evaluate(_code);
setPattern(parsed.pattern);
}
}, isHot ? "🔥" : " ", " toggle hot mode")), /* @__PURE__ */ React.createElement("section", {
className: "grow flex flex-col p-2 text-gray-100"
}, "🎲 random tune"), /* @__PURE__ */ React.createElement("button", null, /* @__PURE__ */ React.createElement("a", {
href: "./tutorial"
}, "📚 tutorial")))), /* @__PURE__ */ React.createElement("section", {
className: "grow flex flex-col text-gray-100"
}, /* @__PURE__ */ React.createElement("div", {
className: "grow relative"
}, /* @__PURE__ */ React.createElement("div", {
className: cx("h-full bg-[#2A3236]", error ? "focus:ring-red-500" : "focus:ring-slate-800")
className: cx("h-full bg-[#2A3236]", error ? "focus:ring-red-500" : "focus:ring-slate-800")
}, /* @__PURE__ */ React.createElement(CodeMirror, {
value: code,
readOnly: isHot,
options: {
mode,
mode: "javascript",
theme: "material",
lineNumbers: true
},
onChange: (_, __, value) => {
if (!isHot) {
setCode(value);
}
}
onChange: (_2, __, value) => setCode(value)
}), /* @__PURE__ */ React.createElement("span", {
className: "p-4 absolute bottom-0 left-0 text-xs whitespace-pre"
className: "p-4 absolute top-0 right-0 text-xs whitespace-pre text-right"
}, !cycle.started ? `press ctrl+enter to play
` : !isHot && activePattern !== pattern ? `ctrl+enter to update
` : "no changes\n", !isHot && /* @__PURE__ */ React.createElement(React.Fragment, null, {pegjs: "mini"}[mode] || mode, " mode"), isHot && "🔥 hot mode: go to hot.js to edit pattern, then save")), error && /* @__PURE__ */ React.createElement("div", {
className: "absolute right-2 bottom-2 text-red-500"
` : dirty ? `ctrl+enter to update
` : "no changes\n")), error && /* @__PURE__ */ React.createElement("div", {
className: cx("absolute right-2 bottom-2", "text-red-500")
}, error?.message || "unknown error")), /* @__PURE__ */ React.createElement("button", {
className: "flex-none w-full border border-gray-700 p-2 bg-slate-700 hover:bg-slate-500",
onClick: () => cycle.toggle()
onClick: () => togglePlay()
}, cycle.started ? "pause" : "play"), /* @__PURE__ */ React.createElement("textarea", {
className: "grow bg-[#283237] border-0 text-xs",
className: "grow bg-[#283237] border-0 text-xs min-h-[200px]",
value: log,
readOnly: true,
ref: logBox,

36
docs/dist/evaluate.js vendored Normal file
View File

@ -0,0 +1,36 @@
import * as strudel from "../_snowpack/link/strudel.js";
import "./tone.js";
import "./midi.js";
import "./voicings.js";
import "./tonal.js";
import "./groove.js";
import shapeshifter from "./shapeshifter.js";
import {minify} from "./parse.js";
import * as Tone from "../_snowpack/pkg/tone.js";
import * as toneHelpers from "./tone.js";
const bootstrapped = {...strudel, ...strudel.Pattern.prototype.bootstrap()};
function hackLiteral(literal, names, func) {
names.forEach((name) => {
Object.defineProperty(literal.prototype, name, {
get: function() {
return func(String(this));
}
});
});
}
hackLiteral(String, ["mini", "m"], bootstrapped.mini);
hackLiteral(String, ["pure", "p"], bootstrapped.pure);
Object.assign(globalThis, bootstrapped, Tone, toneHelpers);
export const evaluate = (code) => {
const shapeshifted = shapeshifter(code);
let evaluated = eval(shapeshifted);
if (typeof evaluated === "function") {
evaluated = evaluated();
}
const pattern = minify(evaluated);
if (pattern?.constructor?.name !== "Pattern") {
const message = `got "${typeof pattern}" instead of pattern`;
throw new Error(message + (typeof pattern === "function" ? ", did you forget to call a function?" : "."));
}
return {mode: "javascript", pattern};
};

6
docs/dist/groove.js vendored Normal file
View File

@ -0,0 +1,6 @@
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
const Pattern = _Pattern;
Pattern.prototype.groove = function(groove) {
return groove.fmap(() => (v) => v).appLeft(this);
};
Pattern.prototype.define("groove", (groove, pat) => pat.groove(groove), {composable: true});

3
docs/dist/midi.js vendored
View File

@ -24,6 +24,7 @@ Pattern.prototype.midi = function(output, channel = 1) {
return this.fmap((value) => ({
...value,
onTrigger: (time, event) => {
value = value.value || value;
if (!isNote(value)) {
throw new Error("not a note: " + value);
}
@ -41,7 +42,7 @@ Pattern.prototype.midi = function(output, channel = 1) {
time = time * 1e3 + timingOffset;
device.playNote(value, channel, {
time,
duration: event.duration * 1e3,
duration: event.duration * 1e3 - 5,
velocity: 0.9
});
}

120
docs/dist/parse.js vendored
View File

@ -1,38 +1,7 @@
import * as krill from "../_snowpack/link/repl/krill-parser.js";
import * as strudel from "../_snowpack/link/strudel.js";
import {Scale, Note, Interval} from "../_snowpack/pkg/@tonaljs/tonal.js";
import "./tone.js";
import "./midi.js";
import * as toneStuff from "./tone.js";
import shapeshifter from "./shapeshifter.js";
const {
pure,
stack,
slowcat,
fastcat,
cat,
sequence,
polymeter,
pm,
polyrhythm,
pr,
silence,
Fraction,
timeCat
} = strudel;
const {autofilter, filter, gain} = toneStuff;
function reify(thing) {
if (thing?.constructor?.name === "Pattern") {
return thing;
}
return pure(thing);
}
function minify(thing) {
if (typeof thing === "string") {
return mini(thing);
}
return reify(thing);
}
const {pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence} = strudel;
const applyOptions = (parent) => (pat, i) => {
const ast = parent.source_[i];
const options = ast.options_;
@ -54,16 +23,56 @@ const applyOptions = (parent) => (pat, i) => {
}
return pat;
};
function resolveReplications(ast) {
ast.source_ = ast.source_.map((child) => {
const {replicate, ...options} = child.options_ || {};
if (replicate) {
return {
...child,
options_: {...options, weight: replicate},
source_: {
type_: "pattern",
arguments_: {
alignment: "h"
},
source_: [
{
type_: "element",
source_: child.source_,
options_: {
operator: {
type_: "stretch",
arguments_: {amount: String(new Fraction(replicate).inverse().valueOf())}
}
}
}
]
}
};
}
return child;
});
}
export function patternifyAST(ast) {
switch (ast.type_) {
case "pattern":
resolveReplications(ast);
const children = ast.source_.map(patternifyAST).map(applyOptions(ast));
if (ast.arguments_.alignment === "v") {
const alignment = ast.arguments_.alignment;
if (alignment === "v") {
return stack(...children);
}
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === "t") {
return slowcat(...children);
}
if (weightedChildren) {
return timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
const pat = timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
if (alignment === "t") {
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
return pat._slow(weightSum);
}
return pat;
}
return sequence(...children);
case "element":
@ -89,7 +98,7 @@ export function patternifyAST(ast) {
return step;
}
const octaves = Math.floor(step / intervals.length);
const mod = (n, m2) => n < 0 ? mod(n + m2, m2) : n % m2;
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");
@ -100,35 +109,28 @@ export function patternifyAST(ast) {
}
}
export const mini = (...strings) => {
const pattern = sequence(...strings.map((str) => {
const pats = strings.map((str) => {
const ast = krill.parse(`"${str}"`);
return patternifyAST(ast);
}));
return pattern;
};
const m = mini;
const s = (...strings) => {
const patternified = strings.map((s2) => minify(s2));
return stack(...patternified);
});
return sequence(...pats);
};
export const h = (string) => {
const ast = krill.parse(string);
return patternifyAST(ast);
};
export const parse = (code) => {
let _pattern;
let mode;
try {
_pattern = h(code);
mode = "pegjs";
} catch (err) {
mode = "javascript";
code = shapeshifter(code);
_pattern = eval(code);
if (_pattern?.constructor?.name !== "Pattern") {
const message = `got "${typeof _pattern}" instead of pattern`;
throw new Error(message + (typeof _pattern === "function" ? ", did you forget to call a function?" : "."));
}
Pattern.prototype.define("mini", mini, {composable: true});
Pattern.prototype.define("m", mini, {composable: true});
Pattern.prototype.define("h", h, {composable: true});
export function reify(thing) {
if (thing?.constructor?.name === "Pattern") {
return thing;
}
return {mode, pattern: _pattern};
};
return pure(thing);
}
export function minify(thing) {
if (typeof thing === "string") {
return mini(thing);
}
return reify(thing);
}

View File

@ -24,3 +24,7 @@ export default (code) => {
});
return codegen(shifted);
};
// TODO: turn x.groove['[~ x]*2'] into x.groove('[~ x]*2'.m)
// and ['c1*2'].xx into 'c1*2'.m.xx ??
// or just all templated strings?? x.groove(`[~ x]*2`)

71
docs/dist/tonal.js vendored Normal file
View File

@ -0,0 +1,71 @@
import {Note, Interval, Scale} from "../_snowpack/pkg/@tonaljs/tonal.js";
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
const Pattern = _Pattern;
function toNoteEvent(event) {
if (typeof event === "string") {
return {value: event};
}
if (event.value) {
return event;
}
throw new Error("not a valid note event: " + JSON.stringify(event));
}
const mod = (n, m) => n < 0 ? mod(n + m, m) : n % m;
export function intervalDirection(from, to, direction = 1) {
const sign = Math.sign(direction);
const interval = sign < 0 ? Interval.distance(to, from) : Interval.distance(from, to);
return (sign < 0 ? "-" : "") + interval;
}
function scaleTranspose(scale, offset, note) {
let [tonic, scaleName] = Scale.tokenize(scale);
const {notes} = Scale.get(`${tonic} ${scaleName}`);
offset = Number(offset);
if (isNaN(offset)) {
throw new Error(`scale offset "${offset}" not a number`);
}
const {pc: fromPc, oct = 3} = Note.get(note);
const noteIndex = notes.indexOf(fromPc);
if (noteIndex === -1) {
throw new Error(`note "${note}" is not in scale "${scale}"`);
}
let i = noteIndex, o = oct, n = fromPc;
const direction = Math.sign(offset);
while (Math.abs(i - noteIndex) < Math.abs(offset)) {
i += direction;
const index = mod(i, notes.length);
if (direction < 0 && n === "C") {
o += direction;
}
n = notes[index];
if (direction > 0 && n === "C") {
o += direction;
}
}
return n + o;
}
Pattern.prototype._mapNotes = function(func) {
return this.fmap((event) => {
const noteEvent = toNoteEvent(event);
return {...noteEvent, ...func(noteEvent)};
});
};
Pattern.prototype._transpose = function(intervalOrSemitones) {
return this._mapNotes(({value, scale}) => {
const interval = !isNaN(Number(intervalOrSemitones)) ? Interval.fromSemitones(intervalOrSemitones) : String(intervalOrSemitones);
return {value: Note.transpose(value, interval), scale};
});
};
Pattern.prototype._scaleTranspose = function(offset) {
return this._mapNotes(({value, scale}) => {
if (!scale) {
throw new Error("can only use scaleOffset after .scale");
}
return {value: scaleTranspose(scale, Number(offset), value), scale};
});
};
Pattern.prototype._scale = function(scale) {
return this._mapNotes((value) => ({...value, scale}));
};
Pattern.prototype.define("transpose", (a, pat) => pat.transpose(a), {composable: true, patternified: true});
Pattern.prototype.define("scale", (a, pat) => pat.scale(a), {composable: true, patternified: true});
Pattern.prototype.define("scaleTranspose", (a, pat) => pat.scaleTranspose(a), {composable: true, patternified: true});

70
docs/dist/tone.js vendored
View File

@ -1,6 +1,62 @@
import {Pattern as _Pattern} from "../_snowpack/link/strudel.js";
import {AutoFilter, Destination, Filter, Gain, isNote, Synth} from "../_snowpack/pkg/tone.js";
import {AutoFilter, Destination, Filter, Gain, isNote, Synth, PolySynth} from "../_snowpack/pkg/tone.js";
const Pattern = _Pattern;
Pattern.prototype.tone = function(instrument) {
return this.fmap((value) => {
value = typeof value !== "object" && !Array.isArray(value) ? {value} : value;
const onTrigger = (time, event) => {
if (instrument.constructor.name === "PluckSynth") {
instrument.triggerAttack(value.value, time);
} else if (instrument.constructor.name === "NoiseSynth") {
instrument.triggerAttackRelease(event.duration, time);
} else {
instrument.triggerAttackRelease(value.value, event.duration, time);
}
};
return {...value, instrument, onTrigger};
});
};
Pattern.prototype.define("tone", (type, pat) => pat.tone(type), {composable: true, patternified: false});
export const vol = (v) => new Gain(v);
export const lowpass = (v) => new Filter(v, "lowpass");
export const highpass = (v) => new Filter(v, "highpass");
export const adsr = (a, d = 0.1, s = 0.4, r = 0.01) => ({envelope: {attack: a, decay: d, sustain: s, release: r}});
export const osc = (type) => ({oscillator: {type}});
export const out = Destination;
const chainable = function(instr) {
const _chain = instr.chain.bind(instr);
let chained = [];
instr.chain = (...args) => {
chained = chained.concat(args);
instr.disconnect();
return _chain(...chained, Destination);
};
instr.filter = (freq = 1e3, type = "lowpass") => instr.chain(new Filter(freq, type));
instr.gain = (gain2 = 0.9) => instr.chain(new Gain(gain2));
return instr;
};
export const poly = (type) => {
const s = new PolySynth(Synth, {oscillator: {type}}).toDestination();
return chainable(s);
};
Pattern.prototype._poly = function(type = "triangle") {
const instrumentConfig = {
oscillator: {type},
envelope: {attack: 0.01, decay: 0.01, sustain: 0.6, release: 0.01}
};
if (!this.instrument) {
this.instrument = poly(type);
}
return this.fmap((value) => {
value = typeof value !== "object" && !Array.isArray(value) ? {value} : value;
const onTrigger = (time, event) => {
this.instrument.set(instrumentConfig);
this.instrument.triggerAttackRelease(value.value, event.duration, time);
};
return {...value, instrumentConfig, onTrigger};
});
};
Pattern.prototype.define("poly", (type, pat) => pat.poly(type), {composable: true, patternified: true});
const getTrigger = (getChain, value) => (time, event) => {
const chain = getChain();
if (!isNote(value)) {
@ -27,9 +83,6 @@ Pattern.prototype._synth = function(type = "triangle") {
return {...value, getInstrument, instrumentConfig, onTrigger};
});
};
Pattern.prototype.synth = function(type = "triangle") {
return this._patternify(Pattern.prototype._synth)(type);
};
Pattern.prototype.adsr = function(attack = 0.01, decay = 0.01, sustain = 0.6, release = 0.01) {
return this.fmap((value) => {
if (!value?.getInstrument) {
@ -65,15 +118,12 @@ export const gain = (gain2 = 0.9) => () => new Gain(gain2);
Pattern.prototype._gain = function(g) {
return this.chain(gain(g));
};
Pattern.prototype.gain = function(g) {
return this._patternify(Pattern.prototype._gain)(g);
};
Pattern.prototype._filter = function(freq, q, type = "lowpass") {
return this.chain(filter(freq, q, type));
};
Pattern.prototype.filter = function(freq) {
return this._patternify(Pattern.prototype._filter)(freq);
};
Pattern.prototype.autofilter = function(g) {
return this.chain(autofilter(g));
};
Pattern.prototype.define("synth", (type, pat) => pat.synth(type), {composable: true, patternified: true});
Pattern.prototype.define("gain", (gain2, pat) => pat.synth(gain2), {composable: true, patternified: true});
Pattern.prototype.define("filter", (cutoff, pat) => pat.filter(cutoff), {composable: true, patternified: true});

381
docs/dist/tunes.js vendored
View File

@ -1,7 +1,7 @@
export const timeCatMini = `s(
'c3@3 [eb3, g3, [c4 d4]/2]',
'c2 g2',
m('[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2').slow(8)
export const timeCatMini = `stack(
'c3@3 [eb3, g3, [c4 d4]/2]'.mini,
'c2 g2'.mini,
'[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2'.mini.slow(8)
)`;
export const timeCat = `stack(
timeCat([3, c3], [1, stack(eb3, g3, m(c4, d4).slow(2))]),
@ -32,8 +32,7 @@ export const shapeShifted = `stack(
b1, b2, b1, b2, e2, e3, e2, e3,
a1, a2, a1, a2, a1, a2, a1, a2,
).rev()
).slow(16).rev()`;
export const tetrisMidi = `${shapeShifted}.midi('IAC-Treiber Bus 1')`;
).slow(16)`;
export const tetrisWithFunctions = `stack(sequence(
'e5', sequence('b4', 'c5'), 'd5', sequence('c5', 'b4'),
'a4', sequence('a4', 'c5'), 'e5', sequence('d5', 'c5'),
@ -53,9 +52,8 @@ export const tetrisWithFunctions = `stack(sequence(
'b1', 'b2', 'b1', 'b2', 'e2', 'e3', 'e2', 'e3',
'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2',
)
)._slow(16)`;
).slow(16)`;
export const tetris = `stack(
sequence(
mini(
'e5 [b4 c5] d5 [c5 b4]',
'a4 [a4 c5] e5 [d5 c5]',
@ -65,9 +63,7 @@ export const tetris = `stack(
'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',
@ -77,13 +73,9 @@ export const tetris = `stack(
'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).synth({
oscillator: {type: 'sawtooth'}
})`;
).slow(16)`;
export const tetrisRev = `stack(
sequence(
mini(
'e5 [b4 c5] d5 [c5 b4]',
'a4 [a4 c5] e5 [d5 c5]',
@ -93,9 +85,7 @@ export const tetrisRev = `stack(
'e5 [~ c5] e5 [d5 c5]',
'b4 [b4 c5] d5 e5',
'c5 a4 a4 ~'
).rev()
),
sequence(
).rev(),
mini(
'e2 e3 e2 e3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 a2 a3',
@ -106,10 +96,8 @@ export const tetrisRev = `stack(
'b1 b2 b1 b2 e2 e3 e2 e3',
'a1 a2 a1 a2 a1 a2 a1 a2'
).rev()
)
).slow(16).synth('sawtooth').filter(1000).gain(0.6)`;
export const tetrisMini1 = `m\`[[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 = `m\`[[e5 [b4 c5] d5 [c5 b4]]
).slow(16)`;
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
[c5 a4 a4 ~]
@ -124,50 +112,309 @@ export const tetrisMini = `m\`[[e5 [b4 c5] d5 [c5 b4]]
[[d2 d3]*4]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]\`._slow(16);
`;
export const tetrisHaskellH = `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 = `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]"
[[a1 a2]*4]\`.mini.slow(16)
`;
export const spanish = `slowcat(
stack('c4','eb4','g4'),
stack('bb3','d4','f4'),
stack('ab3','c4','eb4'),
stack('g3','b3','d4')
)`;
stack(c4,eb4,g4),
stack(bb3,d4,f4),
stack(ab3,c4,eb4),
stack(g3,b3,d4)
)`;
export const whirlyStrudel = `mini("[e4 [b2 b3] c4]")
.every(4, x => x.fast(2))
.every(3, x => x.slow(1.5))
.fast(slowcat(1.25,1,1.5))
.every(2, _ => mini("e4 ~ e3 d4 ~"))`;
.every(4, fast(2))
.every(3, slow(1.5))
.fast(slowcat(1.25, 1, 1.5))
.every(2, _ => mini("e4 ~ e3 d4 ~"))`;
export const swimming = `stack(
mini(
'~',
'~',
'~',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [F5@2 C6] A5 G5',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [Bb5 A5 G5] F5@2',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [F5@2 C6] A5 G5',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [Bb5 A5 G5] F5@2',
'A5 [F5@2 C5] A5 F5',
'Ab5 [F5@2 Ab5] G5@2',
'A5 [F5@2 C5] A5 F5',
'Ab5 [F5@2 C5] C6@2',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [Bb5 A5 G5] F5@2'
),
mini(
'[F4,Bb4,D5] [[D4,G4,Bb4]@2 [Bb3,D4,F4]] [[G3,C4,E4]@2 [[Ab3,F4] [A3,Gb4]]] [Bb3,E4,G4]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, Bb3, Db3] [F3, Bb3, Db3]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]',
'[~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]',
'[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]',
'[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]',
'[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]',
'[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]'
),
mini(
'[G3 G3 C3 E3]',
'[F2 D2 G2 C2]',
'[F2 D2 G2 C2]',
'[F2 A2 Bb2 B2]',
'[A2 Ab2 G2 C2]',
'[F2 A2 Bb2 B2]',
'[G2 C2 F2 F2]',
'[F2 A2 Bb2 B2]',
'[A2 Ab2 G2 C2]',
'[F2 A2 Bb2 B2]',
'[G2 C2 F2 F2]',
'[Bb2 Bb2 A2 A2]',
'[Ab2 Ab2 G2 [C2 D2 E2]]',
'[Bb2 Bb2 A2 A2]',
'[Ab2 Ab2 G2 [C2 D2 E2]]',
'[F2 A2 Bb2 B2]',
'[G2 C2 F2 F2]'
)
).slow(51);
`;
export const giantSteps = `stack(
// melody
mini(
'[F#5 D5] [B4 G4] Bb4 [B4 A4]',
'[D5 Bb4] [G4 Eb4] F#4 [G4 F4]',
'Bb4 [B4 A4] D5 [D#5 C#5]',
'F#5 [G5 F5] Bb5 [F#5 F#5]',
),
// chords
mini(
'[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]',
'[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]',
'Eb^7 [Am7 D7] G^7 [C#m7 F#7]',
'B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]'
).voicings(['E3', 'G4']),
// bass
mini(
'[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]',
'[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]',
'[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]',
'[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]'
)
).slow(20);`;
export const giantStepsReggae = `stack(
// melody
mini(
'[F#5 D5] [B4 G4] Bb4 [B4 A4]',
'[D5 Bb4] [G4 Eb4] F#4 [G4 F4]',
'Bb4 [B4 A4] D5 [D#5 C#5]',
'F#5 [G5 F5] Bb5 [F#5 [F#5 ~@3]]',
),
// chords
mini(
'[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]',
'[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]',
'Eb^7 [Am7 D7] G^7 [C#m7 F#7]',
'B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]'
)
.groove('~ [x ~]'.m.fast(4*8))
.voicings(['E3', 'G4']),
// bass
mini(
'[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]',
'[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]',
'[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]',
'[B2 F#2] [F2 Bb2] [Eb2 Bb2] [C#2 F#2]'
)
.groove('x ~'.m.fast(4*8))
).slow(25)`;
export const transposedChordsHacked = `stack(
'c2 eb2 g2'.mini,
'Cm7'.pure.voicings(['g2','c4']).slow(2)
).transpose(
slowcat(1, 2, 3, 2).slow(2)
).transpose(5)`;
export const scaleTranspose = `stack(f2, f3, c4, ab4)
.scale(sequence('F minor', 'F harmonic minor').slow(4))
.scaleTranspose(sequence(0, -1, -2, -3).slow(4))
.transpose(sequence(0, 1).slow(16))`;
export const groove = `stack(
'c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'.mini,
'[C^7 A7] [Dm7 G7]'.mini.groove('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'.mini)
.voicings(['G3','A4'])
).slow(4)`;
export const magicSofa = `stack(
'<C^7 F^7 ~> <Dm7 G7 A7 ~>'.m
.every(2, fast(2))
.voicings(),
'<c2 f2 g2> <d2 g2 a2 e2>'.m
).slow(1).transpose.slowcat(0, 2, 3, 4)`;
export const confusedPhoneDynamic = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
.superimpose(
...[-12,7,10,12,24].slice(0,5).map((t,i,{length}) => x => transpose(t,x).late(i/length))
)
.scale(sequence('C dorian', 'C mixolydian').slow(4))
.scaleTranspose(slowcat(0,1,2,1).slow(2))
.synth('triangle').gain(0.5).filter(1500)`;
export const confusedPhone = `'[g2 ~@1.3] [c3 ~@1.3]'.mini
.superimpose(
transpose(-12).late(0),
transpose(7).late(0.1),
transpose(10).late(0.2),
transpose(12).late(0.3),
transpose(24).late(0.4)
)
.scale(slowcat('C dorian', 'C mixolydian'))
.scaleTranspose(slowcat(0,1,2,1))
.slow(2)`;
export const zeldasRescue = `stack(
// melody
\`[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] [D4@2 [C4 B3]] [A3]
[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] D5@2
[D5@2 [C5 B4]] [[C5 B4] G4@2] [C5@2 [B4 A4]] [[B4 A4] E4@2]
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`.mini,
// bass
\`[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[D2 A2] C3@2] [[C2 G2] B2@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`.mini
).transpose(12).slow(48).tone(
new PolySynth().chain(
new Gain(0.3),
new Chorus(2, 2.5, 0.5).start(),
new Freeverb(),
Destination)
)`;
export const technoDrums = `stack(
'c1*2'.m.tone(new Tone.MembraneSynth().toDestination()),
'~ x'.m.tone(new Tone.NoiseSynth().toDestination()),
'[~ c4]*2'.m.tone(new Tone.MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),Destination))
)`;
export const loungerave = `() => {
const delay = new FeedbackDelay(1/8, .2).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(1);
const synths = stack(
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b9>/2'.m.groove('~ [x@0.1 ~]'.m).voicings().edit(thru).every(2, early(1/4)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
)
return stack(
drums,
synths
)
//.bypass('<0 1>*4'.m)
//.early('0.25 0'.m);
}`;
export const caverave = `() => {
const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(-1);
const synths = stack(
'<eb4 d4 c4 b3>/2'.m.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove('[~ x]*2'.m)
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass('<1 0>/16'.m),
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m.fast(2)).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b13>/2'.m.groove('~ [x@0.1 ~]'.m.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
)
return stack(
drums.fast(2),
synths
).slow(2);
}`;
export const caveravefuture = `() => {
const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
\`c1*2\`.tone(kick).bypass(\`<0@7 1>/8\`),
\`~ <x!7 [x@3 x]>\`.tone(snare).bypass(\`<0@7 1>/4\`),
\`[~ c4]*2\`.tone(hihat)
);
const thru = (x) => x.transpose(\`<0 1>/8\`).transpose(-1);
const synths = stack(
\`<eb4 d4 c4 b3>/2\`.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove(\`[~ x]*2\`)
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass(\`<1 0>/16\`),
\`<C2 Bb1 Ab1 [G1 [G2 G1]]>/2\`.groove(\`x [~ x] <[~ [~ x]]!3 [x x]>@2\`).edit(thru).tone(bass),
\`<Cm7 Bb7 Fm7 G7b13>/2\`.groove(\`~ [x@0.5 ~]\`.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass(\`<0@7 1>/8\`.early(1/4)),
)
return stack(
drums.fast(2),
synths
).slow(2);
}`;
export const caveravefuture2 = `const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3, 'C minor'], [1, 'C melodic minor']).slow(8)).groove("[~ x]*2")
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass("<1 0>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".groove("x [~ x] <[~ [~ x]]!3 [x x]>@2").edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".groove("~ [x@0.5 ~]".fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass("<0@7 1>/8".early(1/4)),
)
$: stack(
drums.fast(2),
synths
).slow(2);
`;

27
docs/dist/useCycle.js vendored
View File

@ -8,28 +8,20 @@ function useCycle(props) {
const activeCycle = () => Math.floor(Tone.Transport.seconds / cycleDuration);
const query = (cycle = activeCycle()) => {
const timespan = new TimeSpan(cycle, cycle + 1);
const _events = onQuery?.(timespan) || [];
onSchedule?.(_events, cycle);
schedule(_events, cycle);
};
const schedule = (events, cycle = activeCycle()) => {
const timespan = new TimeSpan(cycle, cycle + 1);
const events = onQuery?.(timespan) || [];
onSchedule?.(events, cycle);
const cancelFrom = timespan.begin.valueOf();
Tone.Transport.cancel(cancelFrom);
const queryNextTime = (cycle + 1) * cycleDuration - 0.1;
const delta = queryNextTime - Tone.Transport.seconds;
if (delta < 0.2) {
const queryNextTime = (cycle + 1) * cycleDuration - 0.5;
const t = Math.max(Tone.Transport.seconds, queryNextTime) + 0.1;
Tone.Transport.schedule(() => {
query(cycle + 1);
} else {
Tone.Transport.schedule(() => {
query(cycle + 1);
}, queryNextTime);
}
}, t);
events?.filter((event) => event.part.begin.valueOf() === event.whole.begin.valueOf()).forEach((event) => {
Tone.Transport.schedule((time) => {
const toneEvent = {
time: event.part.begin.valueOf(),
duration: event.whole.end.valueOf() - event.whole.begin.valueOf(),
duration: event.whole.end.sub(event.whole.begin).valueOf(),
value: event.value
};
onEvent(time, toneEvent);
@ -38,9 +30,8 @@ function useCycle(props) {
};
useEffect(() => {
ready && query();
}, [onEvent, onSchedule, onQuery]);
}, [onEvent, onSchedule, onQuery, ready]);
const start = async () => {
console.log("start");
setStarted(true);
await Tone.start();
Tone.Transport.start("+0.1");
@ -51,6 +42,6 @@ function useCycle(props) {
Tone.Transport.pause();
};
const toggle = () => started ? stop() : start();
return {start, stop, onEvent, started, toggle, schedule, query, activeCycle};
return {start, stop, setStarted, onEvent, started, toggle, query, activeCycle};
}
export default useCycle;

9
docs/dist/usePostMessage.js vendored Normal file
View File

@ -0,0 +1,9 @@
import {useEffect} from "../_snowpack/pkg/react.js";
function usePostMessage(listener) {
useEffect(() => {
window.addEventListener("message", listener);
return () => window.removeEventListener("message", listener);
}, [listener]);
return (data) => window.postMessage(data, "*");
}
export default usePostMessage;

105
docs/dist/useRepl.js vendored Normal file
View File

@ -0,0 +1,105 @@
import {useCallback, useState, useMemo} from "../_snowpack/pkg/react.js";
import {isNote} from "../_snowpack/pkg/tone.js";
import {evaluate} from "./evaluate.js";
import useCycle from "./useCycle.js";
import usePostMessage from "./usePostMessage.js";
let s4 = () => {
return Math.floor((1 + Math.random()) * 65536).toString(16).substring(1);
};
function useRepl({tune, defaultSynth, autolink = true}) {
const id = useMemo(() => s4(), []);
const [code, setCode] = useState(tune);
const [activeCode, setActiveCode] = useState();
const [log, setLog] = useState("");
const [error, setError] = useState();
const [pattern, setPattern] = useState();
const dirty = code !== activeCode;
const activateCode = (_code = code) => {
!cycle.started && cycle.start();
broadcast({type: "start", from: id});
if (activeCode && !dirty) {
setError(void 0);
return;
}
try {
const parsed = evaluate(_code);
setPattern(() => parsed.pattern);
if (autolink) {
window.location.hash = "#" + encodeURIComponent(btoa(code));
}
setError(void 0);
setActiveCode(_code);
} catch (err) {
setError(err);
}
};
const pushLog = (message) => setLog((log2) => log2 + `${log2 ? "\n\n" : ""}${message}`);
const logCycle = (_events, cycle2) => {
if (_events.length) {
pushLog(`# cycle ${cycle2}
` + _events.map((e) => e.show()).join("\n"));
}
};
const cycle = useCycle({
onEvent: useCallback((time, event) => {
try {
if (!event.value?.onTrigger) {
const note = event.value?.value || event.value;
if (!isNote(note)) {
throw new Error("not a note: " + note);
}
if (defaultSynth) {
defaultSynth.triggerAttackRelease(note, event.duration, time);
} else {
throw new Error("no defaultSynth passed to useRepl.");
}
} else {
const {onTrigger} = event.value;
onTrigger(time, event);
}
} catch (err) {
console.warn(err);
err.message = "unplayable event: " + err?.message;
pushLog(err.message);
}
}, []),
onQuery: useCallback((span) => {
try {
return pattern?.query(span) || [];
} catch (err) {
setError(err);
return [];
}
}, [pattern]),
onSchedule: useCallback((_events, cycle2) => logCycle(_events, cycle2), [pattern]),
ready: !!pattern
});
const broadcast = usePostMessage(({data: {from, type}}) => {
if (type === "start" && from !== id) {
cycle.setStarted(false);
setActiveCode(void 0);
}
});
const togglePlay = () => {
if (!cycle.started) {
activateCode();
} else {
cycle.stop();
}
};
return {
code,
setCode,
pattern,
error,
cycle,
setPattern,
dirty,
log,
togglePlay,
activateCode,
activeCode,
pushLog
};
}
export default useRepl;

37
docs/dist/voicings.js vendored Normal file
View File

@ -0,0 +1,37 @@
import {Pattern as _Pattern, stack, Hap, reify} from "../_snowpack/link/strudel.js";
import _voicings from "../_snowpack/pkg/chord-voicings.js";
const {dictionaryVoicing, minTopNoteDiff, lefthand} = _voicings;
const getVoicing = (chord, lastVoicing, range = ["F3", "A4"]) => dictionaryVoicing({
chord,
dictionary: lefthand,
range,
picker: minTopNoteDiff,
lastVoicing
});
const Pattern = _Pattern;
Pattern.prototype.fmapNested = function(func) {
return new Pattern((span) => this.query(span).map((event) => reify(func(event)).query(span).map((hap) => new Hap(event.whole, event.part, hap.value))).flat());
};
Pattern.prototype.voicings = function(range) {
let lastVoicing;
if (!range?.length) {
range = ["F3", "A4"];
}
return this.fmapNested((event) => {
lastVoicing = getVoicing(event.value, lastVoicing, range);
return stack(...lastVoicing);
});
};
Pattern.prototype.chordBass = function() {
return this._mapNotes((value) => {
console.log("value", value);
const [_, root] = value.value.match(/^([a-gC-G])[b#]?.*$/);
const bassNote = root + "2";
return {...value, value: bassNote};
});
};
Pattern.prototype.define("voicings", (range, pat) => pat.voicings(range), {composable: true});
Pattern.prototype.define("chordBass", (pat) => {
console.log("call chordBass ...", pat);
return pat.chordBass();
}, {composable: true});

View File

@ -590,6 +590,343 @@ select {
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.prose {
color: var(--tw-prose-body);
max-width: 65ch;
}
.prose :where([class~="lead"]):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-lead);
font-size: 1.25em;
line-height: 1.6;
margin-top: 1.2em;
margin-bottom: 1.2em;
}
.prose :where(a):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-links);
text-decoration: underline;
font-weight: 500;
}
.prose :where(strong):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-bold);
font-weight: 600;
}
.prose :where(ol):not(:where([class~="not-prose"] *)) {
list-style-type: decimal;
padding-left: 1.625em;
}
.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)) {
list-style-type: upper-alpha;
}
.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)) {
list-style-type: lower-alpha;
}
.prose :where(ol[type="A" s]):not(:where([class~="not-prose"] *)) {
list-style-type: upper-alpha;
}
.prose :where(ol[type="a" s]):not(:where([class~="not-prose"] *)) {
list-style-type: lower-alpha;
}
.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)) {
list-style-type: upper-roman;
}
.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)) {
list-style-type: lower-roman;
}
.prose :where(ol[type="I" s]):not(:where([class~="not-prose"] *)) {
list-style-type: upper-roman;
}
.prose :where(ol[type="i" s]):not(:where([class~="not-prose"] *)) {
list-style-type: lower-roman;
}
.prose :where(ol[type="1"]):not(:where([class~="not-prose"] *)) {
list-style-type: decimal;
}
.prose :where(ul):not(:where([class~="not-prose"] *)) {
list-style-type: disc;
padding-left: 1.625em;
}
.prose :where(ol > li):not(:where([class~="not-prose"] *))::marker {
font-weight: 400;
color: var(--tw-prose-counters);
}
.prose :where(ul > li):not(:where([class~="not-prose"] *))::marker {
color: var(--tw-prose-bullets);
}
.prose :where(hr):not(:where([class~="not-prose"] *)) {
border-color: var(--tw-prose-hr);
border-top-width: 1px;
margin-top: 3em;
margin-bottom: 3em;
}
.prose :where(blockquote):not(:where([class~="not-prose"] *)) {
font-weight: 500;
font-style: italic;
color: var(--tw-prose-quotes);
border-left-width: 0.25rem;
border-left-color: var(--tw-prose-quote-borders);
quotes: "\201C""\201D""\2018""\2019";
margin-top: 1.6em;
margin-bottom: 1.6em;
padding-left: 1em;
}
.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"] *))::before {
content: open-quote;
}
.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"] *))::after {
content: close-quote;
}
.prose :where(h1):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 800;
font-size: 2.25em;
margin-top: 0;
margin-bottom: 0.8888889em;
line-height: 1.1111111;
}
.prose :where(h1 strong):not(:where([class~="not-prose"] *)) {
font-weight: 900;
}
.prose :where(h2):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 700;
font-size: 1.5em;
margin-top: 2em;
margin-bottom: 1em;
line-height: 1.3333333;
}
.prose :where(h2 strong):not(:where([class~="not-prose"] *)) {
font-weight: 800;
}
.prose :where(h3):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 600;
font-size: 1.25em;
margin-top: 1.6em;
margin-bottom: 0.6em;
line-height: 1.6;
}
.prose :where(h3 strong):not(:where([class~="not-prose"] *)) {
font-weight: 700;
}
.prose :where(h4):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 600;
margin-top: 1.5em;
margin-bottom: 0.5em;
line-height: 1.5;
}
.prose :where(h4 strong):not(:where([class~="not-prose"] *)) {
font-weight: 700;
}
.prose :where(figure > *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
margin-bottom: 0;
}
.prose :where(figcaption):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-captions);
font-size: 0.875em;
line-height: 1.4285714;
margin-top: 0.8571429em;
}
.prose :where(code):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-code);
font-weight: 600;
font-size: 0.875em;
}
.prose :where(code):not(:where([class~="not-prose"] *))::before {
content: "`";
}
.prose :where(code):not(:where([class~="not-prose"] *))::after {
content: "`";
}
.prose :where(a code):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-links);
}
.prose :where(pre):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-pre-code);
background-color: var(--tw-prose-pre-bg);
overflow-x: auto;
font-weight: 400;
font-size: 0.875em;
line-height: 1.7142857;
margin-top: 1.7142857em;
margin-bottom: 1.7142857em;
border-radius: 0.375rem;
padding-top: 0.8571429em;
padding-right: 1.1428571em;
padding-bottom: 0.8571429em;
padding-left: 1.1428571em;
}
.prose :where(pre code):not(:where([class~="not-prose"] *)) {
background-color: transparent;
border-width: 0;
border-radius: 0;
padding: 0;
font-weight: inherit;
color: inherit;
font-size: inherit;
font-family: inherit;
line-height: inherit;
}
.prose :where(pre code):not(:where([class~="not-prose"] *))::before {
content: none;
}
.prose :where(pre code):not(:where([class~="not-prose"] *))::after {
content: none;
}
.prose :where(table):not(:where([class~="not-prose"] *)) {
width: 100%;
table-layout: auto;
text-align: left;
margin-top: 2em;
margin-bottom: 2em;
font-size: 0.875em;
line-height: 1.7142857;
}
.prose :where(thead):not(:where([class~="not-prose"] *)) {
border-bottom-width: 1px;
border-bottom-color: var(--tw-prose-th-borders);
}
.prose :where(thead th):not(:where([class~="not-prose"] *)) {
color: var(--tw-prose-headings);
font-weight: 600;
vertical-align: bottom;
padding-right: 0.5714286em;
padding-bottom: 0.5714286em;
padding-left: 0.5714286em;
}
.prose :where(tbody tr):not(:where([class~="not-prose"] *)) {
border-bottom-width: 1px;
border-bottom-color: var(--tw-prose-td-borders);
}
.prose :where(tbody tr:last-child):not(:where([class~="not-prose"] *)) {
border-bottom-width: 0;
}
.prose :where(tbody td):not(:where([class~="not-prose"] *)) {
vertical-align: baseline;
padding-top: 0.5714286em;
padding-right: 0.5714286em;
padding-bottom: 0.5714286em;
padding-left: 0.5714286em;
}
.prose {
--tw-prose-body: #374151;
--tw-prose-headings: #111827;
--tw-prose-lead: #4b5563;
--tw-prose-links: #111827;
--tw-prose-bold: #111827;
--tw-prose-counters: #6b7280;
--tw-prose-bullets: #d1d5db;
--tw-prose-hr: #e5e7eb;
--tw-prose-quotes: #111827;
--tw-prose-quote-borders: #e5e7eb;
--tw-prose-captions: #6b7280;
--tw-prose-code: #111827;
--tw-prose-pre-code: #e5e7eb;
--tw-prose-pre-bg: #1f2937;
--tw-prose-th-borders: #d1d5db;
--tw-prose-td-borders: #e5e7eb;
--tw-prose-invert-body: #d1d5db;
--tw-prose-invert-headings: #fff;
--tw-prose-invert-lead: #9ca3af;
--tw-prose-invert-links: #fff;
--tw-prose-invert-bold: #fff;
--tw-prose-invert-counters: #9ca3af;
--tw-prose-invert-bullets: #4b5563;
--tw-prose-invert-hr: #374151;
--tw-prose-invert-quotes: #f3f4f6;
--tw-prose-invert-quote-borders: #374151;
--tw-prose-invert-captions: #9ca3af;
--tw-prose-invert-code: #fff;
--tw-prose-invert-pre-code: #d1d5db;
--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);
--tw-prose-invert-th-borders: #4b5563;
--tw-prose-invert-td-borders: #374151;
font-size: 1rem;
line-height: 1.75;
}
.prose :where(p):not(:where([class~="not-prose"] *)) {
margin-top: 1.25em;
margin-bottom: 1.25em;
}
.prose :where(img):not(:where([class~="not-prose"] *)) {
margin-top: 2em;
margin-bottom: 2em;
}
.prose :where(video):not(:where([class~="not-prose"] *)) {
margin-top: 2em;
margin-bottom: 2em;
}
.prose :where(figure):not(:where([class~="not-prose"] *)) {
margin-top: 2em;
margin-bottom: 2em;
}
.prose :where(h2 code):not(:where([class~="not-prose"] *)) {
font-size: 0.875em;
}
.prose :where(h3 code):not(:where([class~="not-prose"] *)) {
font-size: 0.9em;
}
.prose :where(li):not(:where([class~="not-prose"] *)) {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
.prose :where(ol > li):not(:where([class~="not-prose"] *)) {
padding-left: 0.375em;
}
.prose :where(ul > li):not(:where([class~="not-prose"] *)) {
padding-left: 0.375em;
}
.prose > :where(ul > li p):not(:where([class~="not-prose"] *)) {
margin-top: 0.75em;
margin-bottom: 0.75em;
}
.prose > :where(ul > li > *:first-child):not(:where([class~="not-prose"] *)) {
margin-top: 1.25em;
}
.prose > :where(ul > li > *:last-child):not(:where([class~="not-prose"] *)) {
margin-bottom: 1.25em;
}
.prose > :where(ol > li > *:first-child):not(:where([class~="not-prose"] *)) {
margin-top: 1.25em;
}
.prose > :where(ol > li > *:last-child):not(:where([class~="not-prose"] *)) {
margin-bottom: 1.25em;
}
.prose :where(ul ul, ul ol, ol ul, ol ol):not(:where([class~="not-prose"] *)) {
margin-top: 0.75em;
margin-bottom: 0.75em;
}
.prose :where(hr + *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose :where(h2 + *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose :where(h3 + *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose :where(h4 + *):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose :where(thead th:first-child):not(:where([class~="not-prose"] *)) {
padding-left: 0;
}
.prose :where(thead th:last-child):not(:where([class~="not-prose"] *)) {
padding-right: 0;
}
.prose :where(tbody td:first-child):not(:where([class~="not-prose"] *)) {
padding-left: 0;
}
.prose :where(tbody td:last-child):not(:where([class~="not-prose"] *)) {
padding-right: 0;
}
.prose > :where(:first-child):not(:where([class~="not-prose"] *)) {
margin-top: 0;
}
.prose > :where(:last-child):not(:where([class~="not-prose"] *)) {
margin-bottom: 0;
}
.static {
position: static;
}
@ -603,11 +940,11 @@ select {
position: -webkit-sticky;
position: sticky;
}
.bottom-0 {
bottom: 0px;
.top-0 {
top: 0px;
}
.left-0 {
left: 0px;
.right-0 {
right: 0px;
}
.right-2 {
right: 0.5rem;
@ -621,8 +958,8 @@ select {
.flex {
display: flex;
}
.h-screen {
height: 100vh;
.contents {
display: contents;
}
.h-16 {
height: 4rem;
@ -630,24 +967,42 @@ select {
.h-full {
height: 100%;
}
.min-h-screen {
min-height: 100vh;
}
.min-h-\[200px\] {
min-height: 200px;
}
.w-full {
width: 100%;
}
.w-16 {
width: 4rem;
}
.max-w-3xl {
max-width: 48rem;
}
.flex-none {
flex: none;
}
.grow {
flex-grow: 1;
}
.transform {
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
.cursor-not-allowed {
cursor: not-allowed;
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.justify-center {
justify-content: center;
}
.justify-between {
justify-content: space-between;
}
@ -656,6 +1011,19 @@ select {
margin-right: calc(0.5rem * var(--tw-space-x-reverse));
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse));
margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse)));
}
.space-y-0 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(0px * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(0px * var(--tw-space-y-reverse));
}
.overflow-auto {
overflow: auto;
}
.whitespace-pre {
white-space: pre;
}
@ -676,18 +1044,18 @@ select {
--tw-border-opacity: 1;
border-color: rgb(55 65 81 / var(--tw-border-opacity));
}
.bg-slate-900 {
--tw-bg-opacity: 1;
background-color: rgb(15 23 42 / var(--tw-bg-opacity));
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
.border-slate-500 {
--tw-border-opacity: 1;
border-color: rgb(100 116 139 / var(--tw-border-opacity));
}
.bg-\[\#2A3236\] {
--tw-bg-opacity: 1;
background-color: rgb(42 50 54 / var(--tw-bg-opacity));
}
.bg-white {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.bg-slate-700 {
--tw-bg-opacity: 1;
background-color: rgb(51 65 85 / var(--tw-bg-opacity));
@ -696,16 +1064,23 @@ select {
--tw-bg-opacity: 1;
background-color: rgb(40 50 55 / var(--tw-bg-opacity));
}
.p-2 {
padding: 0.5rem;
.bg-slate-600 {
--tw-bg-opacity: 1;
background-color: rgb(71 85 105 / var(--tw-bg-opacity));
}
.p-4 {
padding: 1rem;
}
.p-2 {
padding: 0.5rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.text-right {
text-align: right;
}
.text-2xl {
font-size: 1.5rem;
line-height: 2rem;
@ -722,6 +1097,14 @@ select {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.text-slate-400 {
--tw-text-opacity: 1;
color: rgb(148 163 184 / var(--tw-text-opacity));
}
.filter {
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
@ -736,6 +1119,11 @@ select {
background-color: rgb(100 116 139 / var(--tw-bg-opacity));
}
.hover\:bg-slate-600:hover {
--tw-bg-opacity: 1;
background-color: rgb(71 85 105 / var(--tw-bg-opacity));
}
.focus\:ring-red-500:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity));

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

108738
docs/tutorial/index.a1b5cf57.js Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

16
docs/tutorial/index.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="icon" href="/tutorial/favicon.e3ab9dd9.ico">
<link rel="stylesheet" type="text/css" href="/tutorial/index.1e09ac22.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Strudel REPL">
<title>Strudel Tutorial</title>
</head>
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script src="/tutorial/index.a1b5cf57.js" defer=""></script>
</body>
</html>

3
repl/.gitignore vendored
View File

@ -1,4 +1,5 @@
.snowpack
build
node_modules
.DS_Store
.DS_Store
.parcel-cache

View File

@ -1,16 +1,5 @@
# 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
## Default Snowpack Readme
> ✨ Bootstrapped with Create Snowpack App (CSA).

View File

@ -177,30 +177,33 @@ function peg$parse(input, options) {
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$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 = "/";
var peg$c19 = "*";
var peg$c20 = "%";
var peg$c21 = "struct";
var peg$c22 = "target";
var peg$c23 = "euclid";
var peg$c24 = "slow";
var peg$c25 = "rotL";
var peg$c26 = "rotR";
var peg$c27 = "fast";
var peg$c28 = "scale";
var peg$c29 = "//";
var peg$c30 = "cat";
var peg$c31 = "$";
var peg$c32 = "setcps";
var peg$c33 = "setbpm";
var peg$c34 = "hush";
var peg$r0 = /^[1-9]/;
var peg$r1 = /^[eE]/;
@ -224,63 +227,67 @@ function peg$parse(input, options) {
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$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("/", false);
var peg$e26 = peg$literalExpectation("*", false);
var peg$e27 = peg$literalExpectation("%", false);
var peg$e28 = peg$literalExpectation("struct", false);
var peg$e29 = peg$literalExpectation("target", false);
var peg$e30 = peg$literalExpectation("euclid", false);
var peg$e31 = peg$literalExpectation("slow", false);
var peg$e32 = peg$literalExpectation("rotL", false);
var peg$e33 = peg$literalExpectation("rotR", false);
var peg$e34 = peg$literalExpectation("fast", false);
var peg$e35 = peg$literalExpectation("scale", false);
var peg$e36 = peg$literalExpectation("//", false);
var peg$e37 = peg$classExpectation(["\n"], true, false);
var peg$e38 = peg$literalExpectation("cat", false);
var peg$e39 = peg$literalExpectation("$", false);
var peg$e40 = peg$literalExpectation("setcps", false);
var peg$e41 = peg$literalExpectation("setbpm", false);
var peg$e42 = 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$f5 = function(a) { return { replicate: a } };
var peg$f6 = function(p, s) { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s } } } };
var peg$f7 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:a } } } };
var peg$f8 = function(a) { return { operator : { type_: "stretch", arguments_ :{ amount:"1/"+a } } } };
var peg$f9 = function(a) { return { operator : { type_: "fixed-step", arguments_ :{ amount:a } } } };
var peg$f10 = function(s, o) { return new ElementStub(s, o);};
var peg$f11 = function(s) { return new PatternStub(s,"h"); };
var peg$f12 = function(c, v) { return v};
var peg$f13 = function(c, cs) { if (cs.length == 0 && c instanceof Object) { return c;} else { cs.unshift(c); return new PatternStub(cs,"v");} };
var peg$f14 = function(s) { return s; };
var peg$f15 = function(s) { return { name: "struct", args: { sequence:s }}};
var peg$f16 = function(s) { return { name: "target", args : { name:s}}};
var peg$f17 = function(p, s) { return { name: "bjorklund", args :{ pulse: parseInt(p), step:parseInt(s) }}};
var peg$f18 = function(a) { return { name: "stretch", args :{ amount: a}}};
var peg$f19 = function(a) { return { name: "shift", args :{ amount: "-"+a}}};
var peg$f20 = function(a) { return { name: "shift", args :{ amount: a}}};
var peg$f21 = function(a) { return { name: "stretch", args :{ amount: "1/"+a}}};
var peg$f22 = function(s) { return { name: "scale", args :{ scale: s.join("")}}};
var peg$f23 = function(s, v) { return v};
var peg$f24 = function(s, ss) { ss.unshift(s); return new PatternStub(ss,"t"); };
var peg$f25 = function(sg) {return sg};
var peg$f26 = function(o, soc) { return new OperatorStub(o.name,o.args,soc)};
var peg$f27 = function(sc) { return sc };
var peg$f28 = function(c) { return c };
var peg$f29 = function(v) { return new CommandStub("setcps", { value: v})};
var peg$f30 = function(v) { return new CommandStub("setcps", { value: (v/120/2)})};
var peg$f31 = function() { return new CommandStub("hush")};
var peg$currPos = 0;
var peg$savedPos = 0;
@ -781,6 +788,24 @@ function peg$parse(input, options) {
s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e1); }
}
if (s0 === peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 94) {
s0 = peg$c8;
peg$currPos++;
} else {
s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e15); }
}
if (s0 === peg$FAILED) {
if (input.charCodeAt(peg$currPos) === 95) {
s0 = peg$c9;
peg$currPos++;
} else {
s0 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e16); }
}
}
}
}
}
}
@ -821,11 +846,11 @@ function peg$parse(input, options) {
s0 = peg$currPos;
s1 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 91) {
s2 = peg$c8;
s2 = peg$c10;
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e15); }
if (peg$silentFails === 0) { peg$fail(peg$e17); }
}
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
@ -833,11 +858,11 @@ function peg$parse(input, options) {
if (s4 !== peg$FAILED) {
s5 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 93) {
s6 = peg$c9;
s6 = peg$c11;
peg$currPos++;
} else {
s6 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e16); }
if (peg$silentFails === 0) { peg$fail(peg$e18); }
}
if (s6 !== peg$FAILED) {
s7 = peg$parsews();
@ -865,11 +890,11 @@ function peg$parse(input, options) {
s0 = peg$currPos;
s1 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 60) {
s2 = peg$c10;
s2 = peg$c12;
peg$currPos++;
} else {
s2 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e17); }
if (peg$silentFails === 0) { peg$fail(peg$e19); }
}
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
@ -877,11 +902,11 @@ function peg$parse(input, options) {
if (s4 !== peg$FAILED) {
s5 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 62) {
s6 = peg$c11;
s6 = peg$c13;
peg$currPos++;
} else {
s6 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e18); }
if (peg$silentFails === 0) { peg$fail(peg$e20); }
}
if (s6 !== peg$FAILED) {
s7 = peg$parsews();
@ -929,6 +954,9 @@ function peg$parse(input, options) {
s0 = peg$parseslice_fast();
if (s0 === peg$FAILED) {
s0 = peg$parseslice_fixed_step();
if (s0 === peg$FAILED) {
s0 = peg$parseslice_replicate();
}
}
}
}
@ -942,11 +970,11 @@ function peg$parse(input, options) {
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 64) {
s1 = peg$c12;
s1 = peg$c14;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e19); }
if (peg$silentFails === 0) { peg$fail(peg$e21); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsenumber();
@ -965,16 +993,44 @@ function peg$parse(input, options) {
return s0;
}
function peg$parseslice_replicate() {
var s0, s1, s2;
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 33) {
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$f5(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;
s1 = peg$c16;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e20); }
if (peg$silentFails === 0) { peg$fail(peg$e23); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -988,15 +1044,15 @@ function peg$parse(input, options) {
if (s7 !== peg$FAILED) {
s8 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 41) {
s9 = peg$c14;
s9 = peg$c17;
peg$currPos++;
} else {
s9 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e21); }
if (peg$silentFails === 0) { peg$fail(peg$e24); }
}
if (s9 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f5(s3, s7);
s0 = peg$f6(s3, s7);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1026,39 +1082,11 @@ function peg$parse(input, options) {
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 47) {
s1 = peg$c15;
s1 = peg$c18;
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 (peg$silentFails === 0) { peg$fail(peg$e25); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsenumber();
@ -1077,16 +1105,16 @@ function peg$parse(input, options) {
return s0;
}
function peg$parseslice_fixed_step() {
function peg$parseslice_fast() {
var s0, s1, s2;
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 37) {
s1 = peg$c17;
if (input.charCodeAt(peg$currPos) === 42) {
s1 = peg$c19;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e24); }
if (peg$silentFails === 0) { peg$fail(peg$e26); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsenumber();
@ -1105,6 +1133,34 @@ function peg$parse(input, options) {
return s0;
}
function peg$parseslice_fixed_step() {
var s0, s1, s2;
s0 = peg$currPos;
if (input.charCodeAt(peg$currPos) === 37) {
s1 = peg$c20;
peg$currPos++;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e27); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsenumber();
if (s2 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f9(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;
@ -1116,7 +1172,7 @@ function peg$parse(input, options) {
s2 = null;
}
peg$savedPos = s0;
s0 = peg$f9(s1, s2);
s0 = peg$f10(s1, s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1141,7 +1197,7 @@ function peg$parse(input, options) {
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$f10(s1);
s1 = peg$f11(s1);
}
s0 = s1;
@ -1161,7 +1217,7 @@ function peg$parse(input, options) {
s5 = peg$parsesingle_cycle();
if (s5 !== peg$FAILED) {
peg$savedPos = s3;
s3 = peg$f11(s1, s5);
s3 = peg$f12(s1, s5);
} else {
peg$currPos = s3;
s3 = peg$FAILED;
@ -1178,7 +1234,7 @@ function peg$parse(input, options) {
s5 = peg$parsesingle_cycle();
if (s5 !== peg$FAILED) {
peg$savedPos = s3;
s3 = peg$f11(s1, s5);
s3 = peg$f12(s1, s5);
} else {
peg$currPos = s3;
s3 = peg$FAILED;
@ -1189,7 +1245,7 @@ function peg$parse(input, options) {
}
}
peg$savedPos = s0;
s0 = peg$f12(s1, s2);
s0 = peg$f13(s1, s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1210,7 +1266,7 @@ function peg$parse(input, options) {
s4 = peg$parsequote();
if (s4 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f13(s3);
s0 = peg$f14(s3);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1260,19 +1316,19 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c18) {
s1 = peg$c18;
if (input.substr(peg$currPos, 6) === peg$c21) {
s1 = peg$c21;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e25); }
if (peg$silentFails === 0) { peg$fail(peg$e28); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsesequence_or_operator();
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f14(s3);
s0 = peg$f15(s3);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1289,12 +1345,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c19) {
s1 = peg$c19;
if (input.substr(peg$currPos, 6) === peg$c22) {
s1 = peg$c22;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e26); }
if (peg$silentFails === 0) { peg$fail(peg$e29); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1305,7 +1361,7 @@ function peg$parse(input, options) {
s5 = peg$parsequote();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f15(s4);
s0 = peg$f16(s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1330,12 +1386,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3, s4, s5;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c20) {
s1 = peg$c20;
if (input.substr(peg$currPos, 6) === peg$c23) {
s1 = peg$c23;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e27); }
if (peg$silentFails === 0) { peg$fail(peg$e30); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1345,7 +1401,7 @@ function peg$parse(input, options) {
s5 = peg$parseint();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f16(s3, s5);
s0 = peg$f17(s3, s5);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1366,41 +1422,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c21) {
s1 = peg$c21;
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$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 (peg$silentFails === 0) { peg$fail(peg$e31); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1420,16 +1447,16 @@ function peg$parse(input, options) {
return s0;
}
function peg$parserotR() {
function peg$parserotL() {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c23) {
s1 = peg$c23;
if (input.substr(peg$currPos, 4) === peg$c25) {
s1 = peg$c25;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e30); }
if (peg$silentFails === 0) { peg$fail(peg$e32); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1449,16 +1476,16 @@ function peg$parse(input, options) {
return s0;
}
function peg$parsefast() {
function peg$parserotR() {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c24) {
s1 = peg$c24;
if (input.substr(peg$currPos, 4) === peg$c26) {
s1 = peg$c26;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e31); }
if (peg$silentFails === 0) { peg$fail(peg$e33); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1478,16 +1505,45 @@ function peg$parse(input, options) {
return s0;
}
function peg$parsefast() {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 4) === peg$c27) {
s1 = peg$c27;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e34); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsenumber();
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f21(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;
if (input.substr(peg$currPos, 5) === peg$c28) {
s1 = peg$c28;
peg$currPos += 5;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e32); }
if (peg$silentFails === 0) { peg$fail(peg$e35); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1507,7 +1563,7 @@ function peg$parse(input, options) {
s5 = peg$parsequote();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f21(s4);
s0 = peg$f22(s4);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1532,12 +1588,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 2) === peg$c26) {
s1 = peg$c26;
if (input.substr(peg$currPos, 2) === peg$c29) {
s1 = peg$c29;
peg$currPos += 2;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e33); }
if (peg$silentFails === 0) { peg$fail(peg$e36); }
}
if (s1 !== peg$FAILED) {
s2 = [];
@ -1546,7 +1602,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e34); }
if (peg$silentFails === 0) { peg$fail(peg$e37); }
}
while (s3 !== peg$FAILED) {
s2.push(s3);
@ -1555,7 +1611,7 @@ function peg$parse(input, options) {
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e34); }
if (peg$silentFails === 0) { peg$fail(peg$e37); }
}
}
s1 = [s1, s2];
@ -1572,21 +1628,21 @@ function peg$parse(input, options) {
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;
if (input.substr(peg$currPos, 3) === peg$c30) {
s1 = peg$c30;
peg$currPos += 3;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e35); }
if (peg$silentFails === 0) { peg$fail(peg$e38); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 91) {
s3 = peg$c8;
s3 = peg$c10;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e15); }
if (peg$silentFails === 0) { peg$fail(peg$e17); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parsews();
@ -1599,7 +1655,7 @@ function peg$parse(input, options) {
s9 = peg$parsesequence_or_operator();
if (s9 !== peg$FAILED) {
peg$savedPos = s7;
s7 = peg$f22(s5, s9);
s7 = peg$f23(s5, s9);
} else {
peg$currPos = s7;
s7 = peg$FAILED;
@ -1616,7 +1672,7 @@ function peg$parse(input, options) {
s9 = peg$parsesequence_or_operator();
if (s9 !== peg$FAILED) {
peg$savedPos = s7;
s7 = peg$f22(s5, s9);
s7 = peg$f23(s5, s9);
} else {
peg$currPos = s7;
s7 = peg$FAILED;
@ -1628,15 +1684,15 @@ function peg$parse(input, options) {
}
s7 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 93) {
s8 = peg$c9;
s8 = peg$c11;
peg$currPos++;
} else {
s8 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e16); }
if (peg$silentFails === 0) { peg$fail(peg$e18); }
}
if (s8 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f23(s5, s6);
s0 = peg$f24(s5, s6);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1682,7 +1738,7 @@ function peg$parse(input, options) {
s4 = peg$parsecomment();
}
peg$savedPos = s0;
s0 = peg$f24(s1);
s0 = peg$f25(s1);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1693,18 +1749,18 @@ function peg$parse(input, options) {
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
if (input.charCodeAt(peg$currPos) === 36) {
s3 = peg$c28;
s3 = peg$c31;
peg$currPos++;
} else {
s3 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e36); }
if (peg$silentFails === 0) { peg$fail(peg$e39); }
}
if (s3 !== peg$FAILED) {
s4 = peg$parsews();
s5 = peg$parsesequence_or_operator();
if (s5 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f25(s1, s5);
s0 = peg$f26(s1, s5);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1729,7 +1785,7 @@ function peg$parse(input, options) {
s1 = peg$parsesequence_or_operator();
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$f26(s1);
s1 = peg$f27(s1);
}
s0 = s1;
if (s0 === peg$FAILED) {
@ -1762,7 +1818,7 @@ function peg$parse(input, options) {
if (s2 !== peg$FAILED) {
s3 = peg$parsews();
peg$savedPos = s0;
s0 = peg$f27(s2);
s0 = peg$f28(s2);
} else {
peg$currPos = s0;
s0 = peg$FAILED;
@ -1775,41 +1831,12 @@ function peg$parse(input, options) {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c29) {
s1 = peg$c29;
if (input.substr(peg$currPos, 6) === peg$c32) {
s1 = peg$c32;
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 (peg$silentFails === 0) { peg$fail(peg$e40); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
@ -1829,20 +1856,49 @@ function peg$parse(input, options) {
return s0;
}
function peg$parsesetbpm() {
var s0, s1, s2, s3;
s0 = peg$currPos;
if (input.substr(peg$currPos, 6) === peg$c33) {
s1 = peg$c33;
peg$currPos += 6;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e41); }
}
if (s1 !== peg$FAILED) {
s2 = peg$parsews();
s3 = peg$parsenumber();
if (s3 !== peg$FAILED) {
peg$savedPos = s0;
s0 = peg$f30(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;
if (input.substr(peg$currPos, 4) === peg$c34) {
s1 = peg$c34;
peg$currPos += 4;
} else {
s1 = peg$FAILED;
if (peg$silentFails === 0) { peg$fail(peg$e39); }
if (peg$silentFails === 0) { peg$fail(peg$e42); }
}
if (s1 !== peg$FAILED) {
peg$savedPos = s0;
s1 = peg$f30();
s1 = peg$f31();
}
s0 = s1;

View File

@ -80,7 +80,7 @@ quote = '"' / "'"
// ------------------ steps and cycles ---------------------------
// single step definition (e.g bd)
step_char = [0-9a-zA-Z~] / "-" / "#" / "."
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]]
@ -95,10 +95,13 @@ 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_modifier = slice_weight / slice_bjorklund / slice_slow / slice_fast / slice_fixed_step / slice_replicate
slice_weight = "@" a:number
{ return { weight: a} }
slice_replicate = "!"a:number
{ return { replicate: a } }
slice_bjorklund = "(" ws p:number ws comma ws s:number ws")"
{ return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s } } } }

6047
repl/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,22 @@
{
"scripts": {
"start": "snowpack dev",
"build": "snowpack build && cp ./public/.nojekyll ../docs",
"build": "snowpack build && cp ./public/.nojekyll ../docs && npm run build-tutorial",
"static": "npx serve ../docs",
"test": "web-test-runner \"src/**/*.test.tsx\"",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
"lint": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
"peggy": "peggy -o krill-parser.js --format es ./krill.pegjs"
"peggy": "peggy -o krill-parser.js --format es ./krill.pegjs",
"tutorial": "parcel src/tutorial/index.html",
"build-tutorial": "parcel build src/tutorial/index.html --dist-dir ../docs/tutorial --public-url /tutorial --no-optimize --no-scope-hoist --no-cache"
},
"dependencies": {
"@tonaljs/tonal": "^4.6.5",
"chord-voicings": "^0.0.1",
"codemirror": "^5.65.1",
"estraverse": "^5.3.0",
"multimap": "^1.1.0",
"ramda": "^0.28.0",
"react": "^17.0.2",
"react-codemirror2": "^7.2.1",
"react-dom": "^17.0.2",
@ -24,12 +28,15 @@
"webmidi": "^2.5.2"
},
"devDependencies": {
"@mdx-js/react": "^1.6.22",
"@parcel/transformer-mdx": "^2.3.1",
"@snowpack/plugin-dotenv": "^2.1.0",
"@snowpack/plugin-postcss": "^1.4.3",
"@snowpack/plugin-react-refresh": "^2.5.0",
"@snowpack/plugin-typescript": "^1.2.1",
"@snowpack/web-test-runner-plugin": "^0.2.2",
"@tailwindcss/forms": "^0.4.0",
"@tailwindcss/typography": "^0.5.2",
"@testing-library/react": "^11.2.6",
"@types/chai": "^4.2.17",
"@types/mocha": "^8.2.2",
@ -39,6 +46,7 @@
"@web/test-runner": "^0.13.3",
"autoprefixer": "^10.4.2",
"chai": "^4.3.4",
"parcel": "^2.3.1",
"peggy": "^1.2.0",
"postcss": "^8.4.6",
"prettier": "^2.2.1",

View File

@ -21,6 +21,7 @@ export default {
/* Enable an SPA Fallback in development: */
// {"match": "routes", "src": ".*", "dest": "/index.html"},
],
exclude: ['**/node_modules/**/*', '**/tutorial/**/*'],
optimize: {
/* Example: Bundle your final build: */
// "bundle": true,

View File

@ -1,28 +1,24 @@
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import logo from './logo.svg';
import cx from './cx';
import React, { useCallback, useLayoutEffect, useRef } from 'react';
import * as Tone from 'tone';
import useCycle from './useCycle';
import type { Pattern } from './types';
import * as tunes from './tunes';
import * as parser from './parse';
import CodeMirror from './CodeMirror';
import hot from '../public/hot';
import { isNote } from 'tone';
import cx from './cx';
import { evaluate } from './evaluate';
import logo from './logo.svg';
import { useWebMidi } from './midi';
import * as tunes from './tunes';
import useRepl from './useRepl';
const { tetris, tetrisRev, shapeShifted } = tunes;
const { parse } = parser;
// TODO: use https://www.npmjs.com/package/@monaco-editor/react
const getHotCode = async () => {
return fetch('/hot.js')
.then((res) => res.text())
.then((src) => {
return src.split('export default').slice(-1)[0].trim();
});
};
const [_, codeParam] = window.location.href.split('#');
let decoded;
try {
decoded = atob(decodeURIComponent(codeParam || ''));
} catch (err) {
console.warn('failed to decode', err);
}
const defaultSynth = new Tone.PolySynth().toDestination();
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination);
defaultSynth.set({
oscillator: { type: 'triangle' },
envelope: {
@ -30,109 +26,44 @@ defaultSynth.set({
},
});
function getRandomTune() {
const allTunes = Object.values(tunes);
const randomItem = (arr: any[]) => arr[Math.floor(Math.random() * arr.length)];
return randomItem(allTunes);
}
const randomTune = getRandomTune();
function App() {
const [mode, setMode] = useState<string>('javascript');
const [code, setCode] = useState<string>(shapeShifted);
const [log, setLog] = useState('');
const logBox = useRef<any>();
const [error, setError] = useState<Error>();
const [pattern, setPattern] = useState<Pattern>();
const [activePattern, setActivePattern] = useState<Pattern>();
const [isHot, setIsHot] = useState(false); // set to true to enable live coding in hot.js, using dev server
const pushLog = (message: string) => setLog((log) => log + `${log ? '\n\n' : ''}${message}`);
// logs events of cycle
const logCycle = (_events: any, cycle: any) => {
if (_events.length) {
pushLog(`# cycle ${cycle}\n` + _events.map((e: any) => e.show()).join('\n'));
}
};
// cycle hook to control scheduling
const cycle = useCycle({
onEvent: useCallback((time, event) => {
try {
if (typeof event.value === 'string') {
if (!isNote(event.value)) {
throw new Error('not a note: ' + event.value);
}
defaultSynth.triggerAttackRelease(event.value, event.duration, time);
/* console.warn('no instrument chosen', event);
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
} else {
const { onTrigger } = event.value;
onTrigger(time, event);
}
} catch (err: any) {
console.warn(err);
err.message = 'unplayable event: ' + err?.message;
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
}
}, []),
onQuery: useCallback(
(span) => {
try {
return activePattern?.query(span) || [];
} catch (err: any) {
setError(err);
return [];
}
},
[activePattern]
),
onSchedule: useCallback((_events, cycle) => logCycle(_events, cycle), [activePattern]),
ready: !!activePattern,
const { setCode, setPattern, error, code, cycle, dirty, log, togglePlay, activateCode, pattern, pushLog } = useRepl({
tune: decoded || randomTune,
defaultSynth,
});
// set active pattern on ctrl+enter
useLayoutEffect(() => {
const handleKeyPress = (e: any) => {
if (e.ctrlKey && e.code === 'Enter') {
setActivePattern(() => pattern);
!cycle.started && cycle.start();
}
};
document.addEventListener('keypress', handleKeyPress);
return () => document.removeEventListener('keypress', handleKeyPress);
}, [pattern]);
// parse pattern when code changes
useEffect(() => {
let _code = code;
// handle hot mode
if (isHot) {
if (typeof hot !== 'string') {
getHotCode().then((_code) => {
setCode(_code);
setMode('javascript');
}); // if using HMR, just use changed file
setActivePattern(hot);
return;
} else {
_code = hot;
setCode(_code);
}
}
// normal mode
try {
const parsed = parse(_code);
// need arrow function here! otherwise if user returns a function, react will think it's a state reducer
// only first time, then need ctrl+enter
setPattern(() => parsed.pattern);
if (!activePattern || isHot) {
setActivePattern(() => parsed.pattern);
}
setMode(parsed.mode);
setError(undefined);
} catch (err: any) {
console.warn(err);
setError(err);
}
}, [code, isHot]);
const logBox = useRef<any>();
// scroll log box to bottom when log changes
useLayoutEffect(() => {
logBox.current.scrollTop = logBox.current?.scrollHeight;
}, [log]);
// set active pattern on ctrl+enter
useLayoutEffect(() => {
// TODO: make sure this is only fired when editor has focus
const handleKeyPress = (e: any) => {
if (e.ctrlKey || e.altKey) {
switch (e.code) {
case 'Enter':
activateCode();
!cycle.started && cycle.start();
break;
case 'Period':
cycle.stop();
}
}
};
document.addEventListener('keypress', handleKeyPress);
return () => document.removeEventListener('keypress', handleKeyPress);
}, [pattern, code]);
useWebMidi({
ready: useCallback(({ outputs }) => {
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `"${o.name}"`).join(' | ')}) to the pattern. `);
@ -146,62 +77,58 @@ function App() {
});
return (
<div className="h-screen bg-slate-900 flex flex-col">
<div className="min-h-screen bg-[#2A3236] flex flex-col">
<header className="flex-none w-full h-16 px-2 flex border-b border-gray-200 bg-white justify-between">
<div className="flex items-center space-x-2">
<img src={logo} className="Tidal-logo w-16 h-16" alt="logo" />
<h1 className="text-2xl">Strudel REPL</h1>
</div>
{window.location.href.includes('http://localhost:8080') && (
<div className="flex space-x-4">
<button
onClick={() => {
if (isHot || confirm('Really switch? You might loose your current pattern..')) {
setIsHot((h) => !h);
}
const _code = getRandomTune();
console.log('tune', _code); // uncomment this to debug when random code fails
setCode(_code);
const parsed = evaluate(_code);
// Tone.Transport.cancel(Tone.Transport.seconds);
setPattern(parsed.pattern);
}}
>
{isHot ? '🔥' : ' '} toggle hot mode
🎲 random tune
</button>
)}
<button>
<a href="./tutorial">📚 tutorial</a>
</button>
</div>
</header>
<section className="grow flex flex-col p-2 text-gray-100">
<section className="grow flex flex-col text-gray-100">
<div className="grow relative">
<div className={cx('h-full bg-[#2A3236]', error ? 'focus:ring-red-500' : 'focus:ring-slate-800')}>
<div className={cx('h-full bg-[#2A3236]', error ? 'focus:ring-red-500' : 'focus:ring-slate-800')}>
<CodeMirror
value={code}
readOnly={isHot}
options={{
mode,
mode: 'javascript',
theme: 'material',
lineNumbers: true,
}}
onChange={(_: any, __: any, value: any) => {
if (!isHot) {
// setLog((log) => log + `${log ? '\n\n' : ''}✏️ edit\n${code}\n${value}`);
setCode(value);
}
}}
onChange={(_: any, __: any, value: any) => setCode(value)}
/>
<span className="p-4 absolute bottom-0 left-0 text-xs whitespace-pre">
{!cycle.started
? `press ctrl+enter to play\n`
: !isHot && activePattern !== pattern
? `ctrl+enter to update\n`
: 'no changes\n'}
{!isHot && <>{{ pegjs: 'mini' }[mode] || mode} mode</>}
{isHot && '🔥 hot mode: go to hot.js to edit pattern, then save'}
<span className="p-4 absolute top-0 right-0 text-xs whitespace-pre text-right">
{!cycle.started ? `press ctrl+enter to play\n` : dirty ? `ctrl+enter to update\n` : 'no changes\n'}
</span>
</div>
{error && <div className="absolute right-2 bottom-2 text-red-500">{error?.message || 'unknown error'}</div>}
{error && (
<div className={cx('absolute right-2 bottom-2', 'text-red-500')}>{error?.message || 'unknown error'}</div>
)}
</div>
<button
className="flex-none w-full border border-gray-700 p-2 bg-slate-700 hover:bg-slate-500"
onClick={() => cycle.toggle()}
onClick={() => togglePlay()}
>
{cycle.started ? 'pause' : 'play'}
</button>
<textarea
className="grow bg-[#283237] border-0 text-xs"
className="grow bg-[#283237] border-0 text-xs min-h-[200px]"
value={log}
readOnly
ref={logBox}

45
repl/src/evaluate.ts Normal file
View File

@ -0,0 +1,45 @@
import * as strudel from '../../strudel.mjs';
import './tone';
import './midi';
import './voicings';
import './tonal';
import './groove';
import shapeshifter from './shapeshifter';
import { minify } from './parse';
import * as Tone from 'tone';
import * as toneHelpers from './tone';
// this will add all methods from definedMethod to strudel + connect all the partial application stuff
const bootstrapped: any = { ...strudel, ...strudel.Pattern.prototype.bootstrap() };
// console.log('bootstrapped',bootstrapped.transpose(2).transpose);
function hackLiteral(literal, names, func) {
names.forEach((name) => {
Object.defineProperty(literal.prototype, name, {
get: function () {
return func(String(this));
},
});
});
}
// with this, you can do 'c2 [eb2 g2]'.mini.fast(2) or 'c2 [eb2 g2]'.m.fast(2),
hackLiteral(String, ['mini', 'm'], bootstrapped.mini); // comment out this line if you panic
hackLiteral(String, ['pure', 'p'], bootstrapped.pure); // comment out this line if you panic
// this will add everything to global scope, which is accessed by eval
Object.assign(globalThis, bootstrapped, Tone, toneHelpers);
export const evaluate: any = (code: string) => {
const shapeshifted = shapeshifter(code); // transform syntactically correct js code to semantically usable code
let evaluated = eval(shapeshifted);
if (typeof evaluated === 'function') {
evaluated = evaluated();
}
const pattern = minify(evaluated); // eval and minify (if user entered a string)
if (pattern?.constructor?.name !== 'Pattern') {
const message = `got "${typeof pattern}" instead of pattern`;
throw new Error(message + (typeof pattern === 'function' ? ', did you forget to call a function?' : '.'));
}
return { mode: 'javascript', pattern: pattern };
};

10
repl/src/groove.ts Normal file
View File

@ -0,0 +1,10 @@
import { Pattern as _Pattern } from '../../strudel.mjs';
const Pattern = _Pattern as any;
// is this the same as struct?
Pattern.prototype.groove = function (groove) {
return groove.fmap(() => (v) => v).appLeft(this);
};
Pattern.prototype.define('groove', (groove, pat) => pat.groove(groove), { composable: true });

View File

@ -28,6 +28,7 @@ Pattern.prototype.midi = function (output: string, channel = 1) {
return this.fmap((value: any) => ({
...value,
onTrigger: (time: number, event: any) => {
value = value.value || value;
if (!isNote(value)) {
throw new Error('not a note: ' + value);
}
@ -52,7 +53,7 @@ Pattern.prototype.midi = function (output: string, channel = 1) {
// await enableWebMidi()
device.playNote(value, channel, {
time,
duration: event.duration * 1000,
duration: event.duration * 1000 - 5,
// velocity: velocity ?? 0.5,
velocity: 0.9,
});

View File

@ -1,42 +1,8 @@
import * as krill from '../krill-parser';
import * as strudel from '../../strudel.mjs';
import { Scale, Note, Interval } from '@tonaljs/tonal';
import './tone';
import './midi';
import * as toneStuff from './tone';
import shapeshifter from './shapeshifter';
// even if some functions are not used, we need them to be available in eval
const {
pure,
stack,
slowcat,
fastcat,
cat,
sequence,
polymeter,
pm,
polyrhythm,
pr,
/* reify, */ silence,
Fraction,
timeCat,
} = strudel;
const { autofilter, filter, gain } = toneStuff;
function reify(thing: any) {
if (thing?.constructor?.name === 'Pattern') {
return thing;
}
return pure(thing);
}
function minify(thing: any) {
if (typeof thing === 'string') {
return mini(thing);
}
return reify(thing);
}
const { pure, Pattern, Fraction, stack, slowcat, sequence, timeCat, silence } = strudel;
const applyOptions = (parent: any) => (pat: any, i: number) => {
const ast = parent.source_[i];
@ -65,16 +31,59 @@ const applyOptions = (parent: any) => (pat: any, i: number) => {
return pat;
};
function resolveReplications(ast) {
// the general idea here: x!3 = [x*3]@3
// could this be made easier?!
ast.source_ = ast.source_.map((child) => {
const { replicate, ...options } = child.options_ || {};
if (replicate) {
return {
...child,
options_: { ...options, weight: replicate },
source_: {
type_: 'pattern',
arguments_: {
alignment: 'h',
},
source_: [
{
type_: 'element',
source_: child.source_,
options_: {
operator: {
type_: 'stretch',
arguments_: { amount: String(new Fraction(replicate).inverse().valueOf()) },
},
},
},
],
},
};
}
return child;
});
}
export function patternifyAST(ast: any): any {
switch (ast.type_) {
case 'pattern':
resolveReplications(ast);
const children = ast.source_.map(patternifyAST).map(applyOptions(ast));
if (ast.arguments_.alignment === 'v') {
const alignment = ast.arguments_.alignment;
if (alignment === 'v') {
return stack(...children);
}
const weightedChildren = ast.source_.some((child) => !!child.options_?.weight);
if (!weightedChildren && alignment === 't') {
return slowcat(...children);
}
if (weightedChildren) {
return timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
const pat = timeCat(...ast.source_.map((child, i) => [child.options_?.weight || 1, children[i]]));
if (alignment === 't') {
const weightSum = ast.source_.reduce((sum, child) => sum + (child.options_?.weight || 1), 0);
return pat._slow(weightSum); // timecat + slow
}
return pat;
}
return sequence(...children);
case 'element':
@ -117,22 +126,11 @@ export function patternifyAST(ast: any): any {
// 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;
};
// shorthand for mini
const m = mini;
// shorthand for stack, automatically minifying strings
const s = (...strings) => {
const patternified = strings.map((s) => minify(s));
return stack(...patternified);
const pats = strings.map((str) => {
const ast = krill.parse(`"${str}"`);
return patternifyAST(ast);
});
return sequence(...pats);
};
// includes haskell style (raw krill parsing)
@ -142,21 +140,22 @@ export const h = (string: string) => {
return patternifyAST(ast);
};
export const parse: any = (code: string) => {
let _pattern;
let mode;
try {
_pattern = h(code);
mode = 'pegjs';
} catch (err) {
// code is not haskell like
mode = 'javascript';
code = shapeshifter(code);
_pattern = eval(code);
if (_pattern?.constructor?.name !== 'Pattern') {
const message = `got "${typeof _pattern}" instead of pattern`;
throw new Error(message + (typeof _pattern === 'function' ? ', did you forget to call a function?' : '.'));
}
// shorthand for mini
Pattern.prototype.define('mini', mini, { composable: true });
Pattern.prototype.define('m', mini, { composable: true });
Pattern.prototype.define('h', h, { composable: true });
// TODO: move this to strudel?
export function reify(thing: any) {
if (thing?.constructor?.name === 'Pattern') {
return thing;
}
return { mode, pattern: _pattern };
};
return pure(thing);
}
export function minify(thing: any) {
if (typeof thing === 'string') {
return mini(thing);
}
return reify(thing);
}

View File

@ -24,3 +24,7 @@ export default (code) => {
});
return codegen(shifted);
};
// TODO: turn x.groove['[~ x]*2'] into x.groove('[~ x]*2'.m)
// and ['c1*2'].xx into 'c1*2'.m.xx ??
// or just all templated strings?? x.groove(`[~ x]*2`)

99
repl/src/tonal.ts Normal file
View File

@ -0,0 +1,99 @@
import { Note, Interval, Scale } from '@tonaljs/tonal';
import { Pattern as _Pattern } from '../../strudel.mjs';
const Pattern = _Pattern as any;
export declare interface NoteEvent {
value: string;
scale?: string;
}
function toNoteEvent(event: string | NoteEvent): NoteEvent {
if (typeof event === 'string') {
return { value: event };
}
if (event.value) {
return event;
}
throw new Error('not a valid note event: ' + JSON.stringify(event));
}
// modulo that works with negative numbers e.g. mod(-1, 3) = 2
const mod = (n: number, m: number): number => (n < 0 ? mod(n + m, m) : n % m);
export function intervalDirection(from, to, direction = 1) {
const sign = Math.sign(direction);
const interval = sign < 0 ? Interval.distance(to, from) : Interval.distance(from, to);
return (sign < 0 ? '-' : '') + interval;
}
// transpose note inside scale by offset steps
function scaleTranspose(scale: string, offset: number, note: string) {
let [tonic, scaleName] = Scale.tokenize(scale);
const { notes } = Scale.get(`${tonic} ${scaleName}`);
offset = Number(offset);
if (isNaN(offset)) {
throw new Error(`scale offset "${offset}" not a number`);
}
const { pc: fromPc, oct = 3 } = Note.get(note);
const noteIndex = notes.indexOf(fromPc);
if (noteIndex === -1) {
throw new Error(`note "${note}" is not in scale "${scale}"`);
}
let i = noteIndex,
o = oct,
n = fromPc;
const direction = Math.sign(offset);
// TODO: find way to do this smarter
while (Math.abs(i - noteIndex) < Math.abs(offset)) {
i += direction;
const index = mod(i, notes.length);
if (direction < 0 && n === 'C') {
o += direction;
}
n = notes[index];
if (direction > 0 && n === 'C') {
o += direction;
}
}
return n + o;
}
Pattern.prototype._mapNotes = function (func: (note: NoteEvent) => NoteEvent) {
return this.fmap((event: string | NoteEvent) => {
const noteEvent = toNoteEvent(event);
// TODO: generalize? this is practical for any event that is expected to be an object with
return { ...noteEvent, ...func(noteEvent) };
});
};
Pattern.prototype._transpose = function (intervalOrSemitones: string | number) {
return this._mapNotes(({ value, scale }: NoteEvent) => {
const interval = !isNaN(Number(intervalOrSemitones))
? Interval.fromSemitones(intervalOrSemitones as number)
: String(intervalOrSemitones);
return { value: Note.transpose(value, interval), scale };
});
};
// example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2))
// TODO: add Pattern.define(name, function, options) that handles all the meta programming stuff
// TODO: find out how to patternify this function when it's standalone
// e.g. `stack(c3).superimpose(transpose(slowcat(7, 5)))` or
// or even `stack(c3).superimpose(transpose.slowcat(7, 5))` or
Pattern.prototype._scaleTranspose = function (offset: number | string) {
return this._mapNotes(({ value, scale }: NoteEvent) => {
if (!scale) {
throw new Error('can only use scaleOffset after .scale');
}
return { value: scaleTranspose(scale, Number(offset), value), scale };
});
};
Pattern.prototype._scale = function (scale: string) {
return this._mapNotes((value) => ({ ...value, scale }));
};
Pattern.prototype.define('transpose', (a, pat) => pat.transpose(a), { composable: true, patternified: true });
Pattern.prototype.define('scale', (a, pat) => pat.scale(a), { composable: true, patternified: true });
Pattern.prototype.define('scaleTranspose', (a, pat) => pat.scaleTranspose(a), { composable: true, patternified: true });

View File

@ -1,8 +1,101 @@
import { Pattern as _Pattern } from '../../strudel.mjs';
import { AutoFilter, Destination, Filter, Gain, isNote, Synth } from 'tone';
import { AutoFilter, Destination, Filter, Gain, isNote, Synth, PolySynth } from 'tone';
// what about
// https://www.charlie-roberts.com/gibberish/playground/
const Pattern = _Pattern as any;
// with this function, you can play the pattern with any tone synth
Pattern.prototype.tone = function (instrument) {
// instrument.toDestination();
return this.fmap((value: any) => {
value = typeof value !== 'object' && !Array.isArray(value) ? { value } : value;
const onTrigger = (time, event) => {
if (instrument.constructor.name === 'PluckSynth') {
instrument.triggerAttack(value.value, time);
} else if (instrument.constructor.name === 'NoiseSynth') {
instrument.triggerAttackRelease(event.duration, time); // noise has no value
} else {
instrument.triggerAttackRelease(value.value, event.duration, time);
}
};
return { ...value, instrument, onTrigger };
});
};
Pattern.prototype.define('tone', (type, pat) => pat.tone(type), { composable: true, patternified: false });
// helpers
export const vol = (v) => new Gain(v);
export const lowpass = (v) => new Filter(v, 'lowpass');
export const highpass = (v) => new Filter(v, 'highpass');
export const adsr = (a, d = 0.1, s = 0.4, r = 0.01) => ({ envelope: { attack: a, decay: d, sustain: s, release: r } });
export const osc = (type) => ({ oscillator: { type } });
export const out = Destination;
/*
You are entering experimental zone
*/
// the following code is an attempt to minimize tonejs code.. it is still an experiment
const chainable = function (instr) {
const _chain = instr.chain.bind(instr);
let chained: any = [];
instr.chain = (...args) => {
chained = chained.concat(args);
instr.disconnect(); // disconnect from destination / previous chain
return _chain(...chained, Destination);
};
// shortcuts: chaining multiple won't work forn now.. like filter(1000).gain(0.5). use chain + native Tone calls instead
instr.filter = (freq = 1000, type: BiquadFilterType = 'lowpass') =>
instr.chain(
new Filter(freq, type) // .Q.setValueAtTime(q, time);
);
instr.gain = (gain: number = 0.9) => instr.chain(new Gain(gain));
return instr;
};
// helpers
export const poly = (type) => {
const s: any = new PolySynth(Synth, { oscillator: { type } }).toDestination();
return chainable(s);
};
Pattern.prototype._poly = function (type: any = 'triangle') {
const instrumentConfig: any = {
oscillator: { type },
envelope: { attack: 0.01, decay: 0.01, sustain: 0.6, release: 0.01 },
};
if (!this.instrument) {
// create only once to keep the js heap happy
// this.instrument = new PolySynth(Synth, instrumentConfig).toDestination();
this.instrument = poly(type);
}
return this.fmap((value: any) => {
value = typeof value !== 'object' && !Array.isArray(value) ? { value } : value;
const onTrigger = (time, event) => {
this.instrument.set(instrumentConfig);
this.instrument.triggerAttackRelease(value.value, event.duration, time);
};
return { ...value, instrumentConfig, onTrigger };
});
};
Pattern.prototype.define('poly', (type, pat) => pat.poly(type), { composable: true, patternified: true });
/*
You are entering danger zone
*/
// everything below is nice in theory, but not healthy for the JS heap, as nodes get recreated on every call
const getTrigger = (getChain: any, value: any) => (time: number, event: any) => {
const chain = getChain(); // make sure this returns a node that is connected toDestination // time
if (!isNote(value)) {
@ -32,10 +125,6 @@ Pattern.prototype._synth = function (type: any = 'triangle') {
});
};
Pattern.prototype.synth = function (type: any = 'triangle') {
return this._patternify(Pattern.prototype._synth)(type);
};
Pattern.prototype.adsr = function (attack = 0.01, decay = 0.01, sustain = 0.6, release = 0.01) {
return this.fmap((value: any) => {
if (!value?.getInstrument) {
@ -85,16 +174,13 @@ export const gain =
Pattern.prototype._gain = function (g: number) {
return this.chain(gain(g));
};
Pattern.prototype.gain = function (g: number) {
return this._patternify(Pattern.prototype._gain)(g);
};
Pattern.prototype._filter = function (freq: number, q: number, type: BiquadFilterType = 'lowpass') {
return this.chain(filter(freq, q, type));
};
Pattern.prototype.filter = function (freq: number) {
return this._patternify(Pattern.prototype._filter)(freq);
};
Pattern.prototype.autofilter = function (g: number) {
return this.chain(autofilter(g));
};
Pattern.prototype.define('synth', (type, pat) => pat.synth(type), { composable: true, patternified: true });
Pattern.prototype.define('gain', (gain, pat) => pat.synth(gain), { composable: true, patternified: true });
Pattern.prototype.define('filter', (cutoff, pat) => pat.filter(cutoff), { composable: true, patternified: true });

View File

@ -1,7 +1,7 @@
export const timeCatMini = `s(
'c3@3 [eb3, g3, [c4 d4]/2]',
'c2 g2',
m('[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2').slow(8)
export const timeCatMini = `stack(
'c3@3 [eb3, g3, [c4 d4]/2]'.mini,
'c2 g2'.mini,
'[eb4@5 [f4 eb4 d4]@3] [eb4 c4]/2'.mini.slow(8)
)`;
export const timeCat = `stack(
@ -34,9 +34,7 @@ export const shapeShifted = `stack(
b1, b2, b1, b2, e2, e3, e2, e3,
a1, a2, a1, a2, a1, a2, a1, a2,
).rev()
).slow(16).rev()`;
export const tetrisMidi = `${shapeShifted}.midi('IAC-Treiber Bus 1')`;
).slow(16)`;
export const tetrisWithFunctions = `stack(sequence(
'e5', sequence('b4', 'c5'), 'd5', sequence('c5', 'b4'),
@ -57,10 +55,9 @@ export const tetrisWithFunctions = `stack(sequence(
'b1', 'b2', 'b1', 'b2', 'e2', 'e3', 'e2', 'e3',
'a1', 'a2', 'a1', 'a2', 'a1', 'a2', 'a1', 'a2',
)
)._slow(16)`;
).slow(16)`;
export const tetris = `stack(
sequence(
mini(
'e5 [b4 c5] d5 [c5 b4]',
'a4 [a4 c5] e5 [d5 c5]',
@ -70,9 +67,7 @@ export const tetris = `stack(
'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',
@ -82,14 +77,10 @@ export const tetris = `stack(
'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).synth({
oscillator: {type: 'sawtooth'}
})`;
).slow(16)`;
export const tetrisRev = `stack(
sequence(
mini(
'e5 [b4 c5] d5 [c5 b4]',
'a4 [a4 c5] e5 [d5 c5]',
@ -99,9 +90,7 @@ export const tetrisRev = `stack(
'e5 [~ c5] e5 [d5 c5]',
'b4 [b4 c5] d5 e5',
'c5 a4 a4 ~'
).rev()
),
sequence(
).rev(),
mini(
'e2 e3 e2 e3 e2 e3 e2 e3',
'a2 a3 a2 a3 a2 a3 a2 a3',
@ -112,8 +101,7 @@ export const tetrisRev = `stack(
'b1 b2 b1 b2 e2 e3 e2 e3',
'a1 a2 a1 a2 a1 a2 a1 a2'
).rev()
)
).slow(16).synth('sawtooth').filter(1000).gain(0.6)`;
).slow(16)`;
/*
.synth({
@ -123,8 +111,10 @@ export const tetrisRev = `stack(
*/
export const tetrisMini1 = `m\`[[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 = `m\`[[e5 [b4 c5] d5 [c5 b4]]
/* export const tetrisMini1 =
"'[[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]]'.mini.slow(16)";
*/
export const tetrisMini = `\`[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
[c5 a4 a4 ~]
@ -139,10 +129,10 @@ export const tetrisMini = `m\`[[e5 [b4 c5] d5 [c5 b4]]
[[d2 d3]*4]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]\`._slow(16);
[[a1 a2]*4]\`.mini.slow(16)
`;
export const tetrisHaskellH = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
/* export const tetrisHaskellH = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
[c5 a4 a4 ~]
@ -158,8 +148,9 @@ export const tetrisHaskellH = `h(\`slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
[[c2 c3]*4]
[[b1 b2]*2 [e2 e3]*2]
[[a1 a2]*4]"\`)
`;
export const tetrisHaskell = `slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
`; */
// following syntax is not supported anymore
/* export const tetrisHaskell = `slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
[a4 [a4 c5] e5 [d5 c5]]
[b4 [~ c5] d5 e5]
[c5 a4 a4 ~]
@ -175,20 +166,362 @@ export const tetrisHaskell = `slow 16 $ "[[e5 [b4 c5] d5 [c5 b4]]
[[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]"\`)`;
*/
export const spanish = `slowcat(
stack('c4','eb4','g4'),
stack('bb3','d4','f4'),
stack('ab3','c4','eb4'),
stack('g3','b3','d4')
)`;
stack(c4,eb4,g4),
stack(bb3,d4,f4),
stack(ab3,c4,eb4),
stack(g3,b3,d4)
)`;
export const whirlyStrudel = `mini("[e4 [b2 b3] c4]")
.every(4, x => x.fast(2))
.every(3, x => x.slow(1.5))
.fast(slowcat(1.25,1,1.5))
.every(2, _ => mini("e4 ~ e3 d4 ~"))`;
.every(4, fast(2))
.every(3, slow(1.5))
.fast(slowcat(1.25, 1, 1.5))
.every(2, _ => mini("e4 ~ e3 d4 ~"))`;
export const swimming = `stack(
mini(
'~',
'~',
'~',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [F5@2 C6] A5 G5',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [Bb5 A5 G5] F5@2',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [F5@2 C6] A5 G5',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [Bb5 A5 G5] F5@2',
'A5 [F5@2 C5] A5 F5',
'Ab5 [F5@2 Ab5] G5@2',
'A5 [F5@2 C5] A5 F5',
'Ab5 [F5@2 C5] C6@2',
'A5 [F5@2 C5] [D5@2 F5] F5',
'[C5@2 F5] [Bb5 A5 G5] F5@2'
),
mini(
'[F4,Bb4,D5] [[D4,G4,Bb4]@2 [Bb3,D4,F4]] [[G3,C4,E4]@2 [[Ab3,F4] [A3,Gb4]]] [Bb3,E4,G4]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, Bb3, Db3] [F3, Bb3, Db3]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]',
'[~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [A3, C4, E4] [A3, C4, E4]] [~ [Ab3, C4, Eb4] [Ab3, C4, Eb4]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [G3, C4, E4] [G3, C4, E4]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]',
'[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]',
'[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]',
'[~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [Bb3, D3, F4] [Bb3, D3, F4]] [~ [A3, C4, F4] [A3, C4, F4]] [~ [A3, C4, F4] [A3, C4, F4]]',
'[~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [Ab3, B3, F4] [Ab3, B3, F4]] [~ [G3, Bb3, F4] [G3, Bb3, F4]] [~ [G3, Bb3, E4] [G3, Bb3, E4]]',
'[~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, A3, C3] [F3, A3, C3]] [~ [F3, Bb3, D3] [F3, Bb3, D3]] [~ [F3, B3, D3] [F3, B3, D3]]',
'[~ [F3, Bb3, D4] [F3, Bb3, D4]] [~ [F3, Bb3, C4] [F3, Bb3, C4]] [~ [F3, A3, C4] [F3, A3, C4]] [~ [F3, A3, C4] [F3, A3, C4]]'
),
mini(
'[G3 G3 C3 E3]',
'[F2 D2 G2 C2]',
'[F2 D2 G2 C2]',
'[F2 A2 Bb2 B2]',
'[A2 Ab2 G2 C2]',
'[F2 A2 Bb2 B2]',
'[G2 C2 F2 F2]',
'[F2 A2 Bb2 B2]',
'[A2 Ab2 G2 C2]',
'[F2 A2 Bb2 B2]',
'[G2 C2 F2 F2]',
'[Bb2 Bb2 A2 A2]',
'[Ab2 Ab2 G2 [C2 D2 E2]]',
'[Bb2 Bb2 A2 A2]',
'[Ab2 Ab2 G2 [C2 D2 E2]]',
'[F2 A2 Bb2 B2]',
'[G2 C2 F2 F2]'
)
).slow(51);
`;
export const giantSteps = `stack(
// melody
mini(
'[F#5 D5] [B4 G4] Bb4 [B4 A4]',
'[D5 Bb4] [G4 Eb4] F#4 [G4 F4]',
'Bb4 [B4 A4] D5 [D#5 C#5]',
'F#5 [G5 F5] Bb5 [F#5 F#5]',
),
// chords
mini(
'[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]',
'[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]',
'Eb^7 [Am7 D7] G^7 [C#m7 F#7]',
'B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]'
).voicings(['E3', 'G4']),
// bass
mini(
'[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]',
'[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]',
'[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]',
'[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]'
)
).slow(20);`;
export const giantStepsReggae = `stack(
// melody
mini(
'[F#5 D5] [B4 G4] Bb4 [B4 A4]',
'[D5 Bb4] [G4 Eb4] F#4 [G4 F4]',
'Bb4 [B4 A4] D5 [D#5 C#5]',
'F#5 [G5 F5] Bb5 [F#5 [F#5 ~@3]]',
),
// chords
mini(
'[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]',
'[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]',
'Eb^7 [Am7 D7] G^7 [C#m7 F#7]',
'B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]'
)
.groove('~ [x ~]'.m.fast(4*8))
.voicings(['E3', 'G4']),
// bass
mini(
'[B2 D2] [G2 D2] [Eb2 Bb2] [A2 D2]',
'[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]',
'[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]',
'[B2 F#2] [F2 Bb2] [Eb2 Bb2] [C#2 F#2]'
)
.groove('x ~'.m.fast(4*8))
).slow(25)`;
/* export const transposedChords = `stack(
m('c2 eb2 g2'),
m('Cm7').voicings(['g2','c4']).slow(2)
).transpose(
slowcat(1, 2, 3, 2).slow(2)
).transpose(5)`; */
export const transposedChordsHacked = `stack(
'c2 eb2 g2'.mini,
'Cm7'.pure.voicings(['g2','c4']).slow(2)
).transpose(
slowcat(1, 2, 3, 2).slow(2)
).transpose(5)`;
export const scaleTranspose = `stack(f2, f3, c4, ab4)
.scale(sequence('F minor', 'F harmonic minor').slow(4))
.scaleTranspose(sequence(0, -1, -2, -3).slow(4))
.transpose(sequence(0, 1).slow(16))`;
/* export const groove = `stack(
m('c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'),
m('[C^7 A7] [Dm7 G7]')
.groove(m('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'))
.voicings(['G3','A4'])
).slow(4.5)`; */
export const groove = `stack(
'c2 g2 a2 [e2@2 eb2] d2 a2 g2 [d2 ~ db2]'.mini,
'[C^7 A7] [Dm7 G7]'.mini.groove('[x@2 x] [~@2 x] [~ x@2]@2 [x ~@2] ~ [~@2 x@4]@2'.mini)
.voicings(['G3','A4'])
).slow(4)`;
/* export const magicSofa = `stack(
m('[C^7 F^7 ~]/3 [Dm7 G7 A7 ~]/4')
.every(2, fast(2))
.voicings(),
m('[c2 f2 g2]/3 [d2 g2 a2 e2]/4')
).slow(1)
.transpose.slowcat(0, 2, 3, 4)`; */
export const magicSofa = `stack(
'<C^7 F^7 ~> <Dm7 G7 A7 ~>'.m
.every(2, fast(2))
.voicings(),
'<c2 f2 g2> <d2 g2 a2 e2>'.m
).slow(1).transpose.slowcat(0, 2, 3, 4)`;
/* export const confusedPhone = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
.superimpose(
x => transpose(-12,x).late(0),
x => transpose(7,x).late(0.2),
x => transpose(10,x).late(0.4),
x => transpose(12,x).late(0.6),
x => transpose(24,x).late(0.8)
)
.scale(sequence('C dorian', 'C mixolydian').slow(4))
.scaleTranspose(slowcat(0,1,2,1).slow(2))
.synth('triangle').gain(0.5).filter(1500)`; */
export const confusedPhoneDynamic = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2))
.superimpose(
...[-12,7,10,12,24].slice(0,5).map((t,i,{length}) => x => transpose(t,x).late(i/length))
)
.scale(sequence('C dorian', 'C mixolydian').slow(4))
.scaleTranspose(slowcat(0,1,2,1).slow(2))
.synth('triangle').gain(0.5).filter(1500)`;
export const confusedPhone = `'[g2 ~@1.3] [c3 ~@1.3]'.mini
.superimpose(
transpose(-12).late(0),
transpose(7).late(0.1),
transpose(10).late(0.2),
transpose(12).late(0.3),
transpose(24).late(0.4)
)
.scale(slowcat('C dorian', 'C mixolydian'))
.scaleTranspose(slowcat(0,1,2,1))
.slow(2)`;
export const zeldasRescue = `stack(
// melody
\`[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] [D4@2 [C4 B3]] [A3]
[B3@2 D4] [A3@2 [G3 A3]] [B3@2 D4] [A3]
[B3@2 D4] [A4@2 G4] D5@2
[D5@2 [C5 B4]] [[C5 B4] G4@2] [C5@2 [B4 A4]] [[B4 A4] E4@2]
[D5@2 [C5 B4]] [[C5 B4] G4 C5] [G5] [~ ~ B3]\`.mini,
// bass
\`[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[C2 G2] E3@2] [[C2 G2] F#3@2] [[C2 G2] E3@2] [[C2 G2] F#3@2]
[[B1 D3] G3@2] [[Bb1 Db3] G3@2] [[A1 C3] G3@2] [[D2 C3] F#3@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[D2 A2] C3@2] [[C2 G2] B2@2]
[[F2 C3] E3@2] [[E2 B2] D3@2] [[Eb2 Bb2] Db3@2] [[D2 A2] C3 [F3,G2]]\`.mini
).transpose(12).slow(48).tone(
new PolySynth().chain(
new Gain(0.3),
new Chorus(2, 2.5, 0.5).start(),
new Freeverb(),
Destination)
)`;
export const technoDrums = `stack(
'c1*2'.m.tone(new Tone.MembraneSynth().toDestination()),
'~ x'.m.tone(new Tone.NoiseSynth().toDestination()),
'[~ c4]*2'.m.tone(new Tone.MetalSynth().set({envelope:{decay:0.06,sustain:0}}).chain(new Gain(0.5),Destination))
)`;
export const loungerave = `() => {
const delay = new FeedbackDelay(1/8, .2).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(1);
const synths = stack(
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b9>/2'.m.groove('~ [x@0.1 ~]'.m).voicings().edit(thru).every(2, early(1/4)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
)
return stack(
drums,
synths
)
//.bypass('<0 1>*4'.m)
//.early('0.25 0'.m);
}`;
export const caverave = `() => {
const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(-1);
const synths = stack(
'<eb4 d4 c4 b3>/2'.m.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove('[~ x]*2'.m)
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass('<1 0>/16'.m),
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m.fast(2)).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b13>/2'.m.groove('~ [x@0.1 ~]'.m.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
)
return stack(
drums.fast(2),
synths
).slow(2);
}`;
export const caveravefuture = `() => {
const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
\`c1*2\`.tone(kick).bypass(\`<0@7 1>/8\`),
\`~ <x!7 [x@3 x]>\`.tone(snare).bypass(\`<0@7 1>/4\`),
\`[~ c4]*2\`.tone(hihat)
);
const thru = (x) => x.transpose(\`<0 1>/8\`).transpose(-1);
const synths = stack(
\`<eb4 d4 c4 b3>/2\`.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove(\`[~ x]*2\`)
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass(\`<1 0>/16\`),
\`<C2 Bb1 Ab1 [G1 [G2 G1]]>/2\`.groove(\`x [~ x] <[~ [~ x]]!3 [x x]>@2\`).edit(thru).tone(bass),
\`<Cm7 Bb7 Fm7 G7b13>/2\`.groove(\`~ [x@0.5 ~]\`.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass(\`<0@7 1>/8\`.early(1/4)),
)
return stack(
drums.fast(2),
synths
).slow(2);
}`;
export const caveravefuture2 = `const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
"c1*2".tone(kick).bypass("<0@7 1>/8"),
"~ <x!7 [x@3 x]>".tone(snare).bypass("<0@7 1>/4"),
"[~ c4]*2".tone(hihat)
);
const thru = (x) => x.transpose("<0 1>/8").transpose(-1);
const synths = stack(
"<eb4 d4 c4 b3>/2".scale(timeCat([3, 'C minor'], [1, 'C melodic minor']).slow(8)).groove("[~ x]*2")
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass("<1 0>/16"),
"<C2 Bb1 Ab1 [G1 [G2 G1]]>/2".groove("x [~ x] <[~ [~ x]]!3 [x x]>@2").edit(thru).tone(bass),
"<Cm7 Bb7 Fm7 G7b13>/2".groove("~ [x@0.5 ~]".fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass("<0@7 1>/8".early(1/4)),
)
$: stack(
drums.fast(2),
synths
).slow(2);
`;

View File

@ -0,0 +1,54 @@
import React, { useMemo } from 'react';
import * as Tone from 'tone';
import useRepl from '../useRepl';
import CodeMirror from '../CodeMirror';
import cx from '../cx';
const defaultSynth = new Tone.PolySynth().chain(new Tone.Gain(0.5), Tone.Destination).set({
oscillator: { type: 'triangle' },
envelope: {
release: 0.01,
},
});
function MiniRepl({ tune, height = 100 }) {
const { code, setCode, activateCode, activeCode, setPattern, error, cycle, dirty, log, togglePlay } = useRepl({
tune,
defaultSynth,
autolink: false,
});
return (
<div className="flex space-y-0 overflow-auto" style={{ height }}>
<div className="w-16 flex flex-col">
<button
className="grow bg-slate-700 border-b border-slate-500 text-white hover:bg-slate-600 "
onClick={() => togglePlay()}
>
{cycle.started ? 'pause' : 'play'}
</button>
<button
className={cx(
'grow border-slate-500 hover:bg-slate-600',
activeCode && dirty ? 'bg-slate-700 text-white' : 'bg-slate-600 text-slate-400 cursor-not-allowed'
)}
onClick={() => activateCode()}
>
update
</button>
</div>
<CodeMirror
className="w-full"
value={code}
options={{
mode: 'javascript',
theme: 'material',
lineNumbers: true,
}}
onChange={(_: any, __: any, value: any) => setCode(value)}
/>
{/* <textarea className="w-full" value={code} onChange={(e) => setCode(e.target.value)} /> */}
</div>
);
}
export default MiniRepl;

View File

@ -0,0 +1,30 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Tutorial from './tutorial.mdx';
// import logo from '../logo.svg';
ReactDOM.render(
<React.StrictMode>
<div className="min-h-screen">
<header className="flex-none flex justify-center w-full h-16 px-2 items-center border-b border-gray-200 bg-white">
<div className="p-4 w-full max-w-3xl flex justify-between">
<div className="flex items-center space-x-2">
<img src={'https://tidalcycles.org/img/logo.svg'} className="Tidal-logo w-16 h-16" alt="logo" />
<h1 className="text-2xl">Strudel Tutorial</h1>
</div>
{!window.location.href.includes('localhost') && (
<div className="flex space-x-4">
<a href="../">go to REPL</a>
</div>
)}
</div>
</header>
<div className="flex justify-center">
<main className="p-4 max-w-3xl prose">
<Tutorial />
</main>
</div>
</div>
</React.StrictMode>,
document.getElementById('root')
);

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="../../public/favicon.ico" />
<link rel="stylesheet" type="text/css" href="./style.css" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="Strudel REPL" />
<title>Strudel Tutorial</title>
</head>
<body>
<div id="root"></div>
<noscript>You need to enable JavaScript to run this app.</noscript>
<script type="module" src="Tutorial.js"></script>
</body>
</html>

View File

@ -0,0 +1,13 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.react-codemirror2,
.CodeMirror {
width: 100% !important;
height: inherit !important;
}
.justify-center {
justify-content: center;
}

View File

@ -0,0 +1,402 @@
import MiniRepl from './MiniRepl';
# What is Strudel?
With Strudel, you can expressively write dynamic music pieces.
It aims to be [Tidal Cycles](https://tidalcycles.org/) for JavaScript (started by the same author).
You don't need to know JavaScript or Tidal Cycles to make music with Strudel.
This interactive tutorial will guide you through the basics of Strudel.
The best place to actually make music with Strudel is the [Strudel REPL](https://strudel.tidalcycles.org/).
## Show me a Demo
To get a taste of what Strudel can do, check out this track:
<MiniRepl
tune={`() => {
const delay = new FeedbackDelay(1/8, .4).chain(vol(0.5), out);
const kick = new MembraneSynth().chain(vol(.8), out);
const snare = new NoiseSynth().chain(vol(.8), out);
const hihat = new MetalSynth().set(adsr(0, .08, 0, .1)).chain(vol(.3).connect(delay),out);
const bass = new Synth().set({ ...osc('sawtooth'), ...adsr(0, .1, .4) }).chain(lowpass(900), vol(.5), out);
const keys = new PolySynth().set({ ...osc('sawtooth'), ...adsr(0, .5, .2, .7) }).chain(lowpass(1200), vol(.5), out);
const drums = stack(
'c1*2'.m.tone(kick).bypass('<0@7 1>/8'.m),
'~ <x!7 [x@3 x]>'.m.tone(snare).bypass('<0@7 1>/4'.m),
'[~ c4]*2'.m.tone(hihat)
);
const thru = (x) => x.transpose('<0 1>/8'.m).transpose(-1);
const synths = stack(
'<eb4 d4 c4 b3>/2'.m.scale(timeCat([3,'C minor'],[1,'C melodic minor']).slow(8)).groove('[~ x]*2'.m)
.edit(
scaleTranspose(0).early(0),
scaleTranspose(2).early(1/8),
scaleTranspose(7).early(1/4),
scaleTranspose(8).early(3/8)
).edit(thru).tone(keys).bypass('<1 0>/16'.m),
'<C2 Bb1 Ab1 [G1 [G2 G1]]>/2'.m.groove('[x [~ x] <[~ [~ x]]!3 [x x]>@2]/2'.m.fast(2)).edit(thru).tone(bass),
'<Cm7 Bb7 Fm7 G7b13>/2'.m.groove('~ [x@0.1 ~]'.m.fast(2)).voicings().edit(thru).every(2, early(1/8)).tone(keys).bypass('<0@7 1>/8'.m.early(1/4))
)
return stack(
drums.fast(2),
synths
).slow(2);
}`}
height={400}
/>
[Open this track in the REPL](https://strudel.tidalcycles.org/#KCkgPT4gewogIGNvbnN0IGRlbGF5ID0gbmV3IEZlZWRiYWNrRGVsYXkoMS84LCAuNCkuY2hhaW4odm9sKDAuNSksIG91dCk7CiAgY29uc3Qga2ljayA9IG5ldyBNZW1icmFuZVN5bnRoKCkuY2hhaW4odm9sKC44KSwgb3V0KTsKICBjb25zdCBzbmFyZSA9IG5ldyBOb2lzZVN5bnRoKCkuY2hhaW4odm9sKC44KSwgb3V0KTsKICBjb25zdCBoaWhhdCA9IG5ldyBNZXRhbFN5bnRoKCkuc2V0KGFkc3IoMCwgLjA4LCAwLCAuMSkpLmNoYWluKHZvbCguMykuY29ubmVjdChkZWxheSksb3V0KTsKICBjb25zdCBiYXNzID0gbmV3IFN5bnRoKCkuc2V0KHsgLi4ub3NjKCdzYXd0b290aCcpLCAuLi5hZHNyKDAsIC4xLCAuNCkgfSkuY2hhaW4obG93cGFzcyg5MDApLCB2b2woLjUpLCBvdXQpOwogIGNvbnN0IGtleXMgPSBuZXcgUG9seVN5bnRoKCkuc2V0KHsgLi4ub3NjKCdzYXd0b290aCcpLCAuLi5hZHNyKDAsIC41LCAuMiwgLjcpIH0pLmNoYWluKGxvd3Bhc3MoMTIwMCksIHZvbCguNSksIG91dCk7CiAgCiAgY29uc3QgZHJ1bXMgPSBzdGFjaygKICAgICdjMSoyJy5tLnRvbmUoa2ljaykuYnlwYXNzKCc8MEA3IDE%2BLzgnLm0pLAogICAgJ34gPHghNyBbeEAzIHhdPicubS50b25lKHNuYXJlKS5ieXBhc3MoJzwwQDcgMT4vNCcubSksCiAgICAnW34gYzRdKjInLm0udG9uZShoaWhhdCkKICApOwogIAogIGNvbnN0IHRocnUgPSAoeCkgPT4geC50cmFuc3Bvc2UoJzwwIDE%2BLzgnLm0pLnRyYW5zcG9zZSgtMSk7CiAgY29uc3Qgc3ludGhzID0gc3RhY2soCiAgICAnPGViNCBkNCBjNCBiMz4vMicubS5zY2FsZSh0aW1lQ2F0KFszLCdDIG1pbm9yJ10sWzEsJ0MgbWVsb2RpYyBtaW5vciddKS5zbG93KDgpKS5ncm9vdmUoJ1t%2BIHhdKjInLm0pCiAgICAuZWRpdCgKICAgICAgc2NhbGVUcmFuc3Bvc2UoMCkuZWFybHkoMCksCiAgICAgIHNjYWxlVHJhbnNwb3NlKDIpLmVhcmx5KDEvOCksCiAgICAgIHNjYWxlVHJhbnNwb3NlKDcpLmVhcmx5KDEvNCksCiAgICAgIHNjYWxlVHJhbnNwb3NlKDgpLmVhcmx5KDMvOCkKICAgICkuZWRpdCh0aHJ1KS50b25lKGtleXMpLmJ5cGFzcygnPDEgMD4vMTYnLm0pLAogICAgJzxDMiBCYjEgQWIxIFtHMSBbRzIgRzFdXT4vMicubS5ncm9vdmUoJ1t4IFt%2BIHhdIDxbfiBbfiB4XV0hMyBbeCB4XT5AMl0vMicubS5mYXN0KDIpKS5lZGl0KHRocnUpLnRvbmUoYmFzcyksCiAgICAnPENtNyBCYjcgRm03IEc3YjEzPi8yJy5tLmdyb292ZSgnfiBbeEAwLjEgfl0nLm0uZmFzdCgyKSkudm9pY2luZ3MoKS5lZGl0KHRocnUpLmV2ZXJ5KDIsIGVhcmx5KDEvOCkpLnRvbmUoa2V5cykuYnlwYXNzKCc8MEA3IDE%2BLzgnLm0uZWFybHkoMS80KSkKICApCiAgcmV0dXJuIHN0YWNrKAogICAgZHJ1bXMuZmFzdCgyKSwgCiAgICBzeW50aHMKICApLnNsb3coMik7Cn0%3D)
## Disclaimer
- This project is still in its experimental state. In the future, parts of it might change significantly.
- This tutorial is far from complete.
<br />
# Mini Notation
Similar to Tidal Cycles, Strudel has an embedded mini language that is designed to write rhythmic patterns in a short manner.
Before diving deeper into the details, here is a flavor of how the mini language looks like:
<MiniRepl
tune={`\`[
[
[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]
]
]/16\``}
height={600}
/>
The snippet above is enclosed in backticks (`), which allows you to write multi-line strings.
You can also use double quotes (") for single line mini notation.
## Notes
Notes are notated with the note letter, followed by the octave number. You can notate flats with `b` and sharps with `#`.
<MiniRepl tune={`"e5"`} />
Here, the same note is played over and over again, once a second. This one second is the default length of one so called "cycle".
By the way, you can edit the contents of the player, and press "update" to hear your change!
You can also press "play" on the next player without needing to stop the last one.
## Sequences
We can play more notes by seperating them with spaces:
<MiniRepl tune={`"e5 b4 d5 c5"`} />
Here, those four notes are squashed into one cycle, so each note is a quarter second long.
## Division
We can slow the sequence down by enclosing it in brackets and dividing it by a number:
<MiniRepl tune={`"[e5 b4 d5 c5]/2"`} />
The division by two means that the sequence will be played over the course of two cycles.
You can also use decimal numbers for any tempo you like.
## Angle Brackets
Using angle brackets, we can define the sequence length based on the number of children:
<MiniRepl tune={`"<e5 b4 d5 c5>"`} />
The above snippet is the same as:
<MiniRepl tune={`"[e5 b4 d5 c5]/4"`} />
The advantage of the angle brackets, is that we can add more children without needing to change the number at the end.
## Multiplication
Contrary to division, a sequence can be sped up by multiplying it by a number:
<MiniRepl tune={`"[e5 b4 d5 c5]*2"`} />
The multiplication by 2 here means that the sequence will play twice a cycle.
## Bracket Nesting
To create more interesting rhythms, you can nest sequences with brackets, like this:
<MiniRepl tune={`"e5 [b4 c5] d5 [c5 b4]"`} />
## Rests
The "~" represents a rest:
<MiniRepl tune={`"[b4 [~ c5] d5 e5]"`} />
## Parallel
Using commas, we can play chords:
<MiniRepl tune={`"g3,b3,e4"`} />
To play multiple chords in a sequence, we have to wrap them in brackets:
<MiniRepl tune={`"<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>"`} />
## Elongation
With the "@" symbol, we can specify temporal "weight" of a sequence child:
<MiniRepl tune={`"<[g3,b3,e4]@2 [a3,c3,e4] [b3,d3,f#4]>"`} />
Here, the first chord has a weight of 2, making it twice the length of the other chords. The default weight is 1.
## Replication
Using "!" we can repeat without speeding up:
<MiniRepl tune={`"<[g3,b3,e4]!2 [a3,c3,e4] [b3,d3,f#4]>"`} />
In essence, the `x!n` is like a shortcut for `[x*n]@n`.
## Mini Notation TODO
Compared to [tidal mini notation](https://tidalcycles.org/docs/patternlib/tutorials/mini_notation/), the following mini notation features are missing from Strudel:
- Tie symbols "\_"
- Euclidean algorithm "c3(3,2,1)"
- feet marking "."
- random choice "|"
- Random removal "?"
- Polymetric sequences "{ ... }"
- Fixed steps using "%"
<br />
# Core API
While the mini notation is powerful on its own, there is much more to discover.
Internally, the mini notation will expand to use the actual functional JavaScript API.
## Notes
Notes are automatically available as variables:
<MiniRepl tune={`e4`} />
An important difference to the mini notation:
For sharp notes, the letter "s" is used instead of "#", because JavaScript does not support "#" in a variable name.
The above is the same as:
<MiniRepl tune={`"e4"`} />
Using strings, you can also use "#".
## Functions that create Patterns
The following functions will return a pattern. We will see later what that means.
## pure(value)
To create a pattern from a value, you can wrap the value in pure:
<MiniRepl tune={`pure(e4)`} />
Most of the time, you won't need that function as input values of pattern creating functions are purified by default.
### cat(...values)
The given items are con**cat**enated spread evenly over one cycle:
<MiniRepl tune={`cat(e5, b4, d5, c5)`} />
The function **fastcat** does the same as **cat**.
### sequence(...values)
Like **cat**, but allows nesting with arrays:
<MiniRepl tune={`sequence(e5, [b4, c5], d5, [c5, b4])`} />
### stack(...values)
The given items are played at the same time at the same length:
<MiniRepl tune={`stack(g3,b3,e4)`} />
### slowcat(...values)
Like cat, but each item has the length of one cycle:
<MiniRepl tune={`slowcat(e5, b4, d5, c5)`} />
<!-- ## slowcatPrime ? -->
### Nesting functions
You can nest functions inside one another:
<MiniRepl
tune={`slowcat(
stack(g3,b3,e4),
stack(a3,c3,e4),
stack(b3,d3,fs4),
stack(b3,e4,g4)
)`}
height={200}
/>
The above is equivalent to
<MiniRepl tune={`"<[g3,b3,e4] [a3,c3,e4] [b3,d3,f#4] [b3,e4,g4]>"`} />
### timeCat(...[weight,value])
Like with "@" in mini notation, we can specify weights to the items in a sequence:
<MiniRepl tune={`timeCat([3,e3],[1, g3])`} />
<!-- ## polymeter
how to use?
<MiniRepl tune={`polymeter(3, e3, g3, b3)`} /> -->
### polyrhythm(...[...values])
Plays the given items at the same time, within the same length:
<MiniRepl tune={`polyrhythm([e3, g3], [e4, g4, b4])`} />
We can write the same with **stack** and **cat**:
<MiniRepl tune={`stack(cat(e3, g3), cat(e4, g4, b4))`} />
You can also use the shorthand **pr** instead of **polyrhythm**.
## Pattern modifier functions
The following functions modify a pattern.
### slow(factor)
Like "/" in mini notation, **slow** will slow down a pattern over the given number of cycles:
<MiniRepl tune={`cat(e5, b4, d5, c5).slow(2)`} />
The same in mini notation:
<MiniRepl tune={`"[e5 b4 d5 c5]/2"`} />
### fast(factor)
Like "\*" in mini notation, **fast** will play a pattern times the given number in one cycle:
<MiniRepl tune={`cat(e5, b4, d5, c5).fast(2)`} />
### early(cycles)
With early, you can nudge a pattern to start earlier in time:
<MiniRepl tune={`cat(e5, pure(b4).late(0.5))`} />
Note that we have to wrap b4 in **pure** to be able to call a the pattern modifier **early**.
There is the shorthand **p** for this:
<MiniRepl tune={`cat(e5, b4.p.late(0.5))`} />
### late(cycles)
Like early, but in the other direction:
<MiniRepl tune={`cat(e5, b4.p.late(0.5))`} />
TODO: shouldn't it sound different?
### rev()
Will reverse the pattern:
<MiniRepl tune={`cat(c3,d3,e3,f3).rev()`} />
### every(n, func)
Will apply the given function every n cycles:
<MiniRepl tune={`cat(e5, b4.p.every(4, late(0.5)))`} />
Note that late is called directly. This is a shortcut for:
<MiniRepl tune={`cat(e5, b4.p.every(4, x => x.late(0.5)))`} />
TODO: should the function really run the first cycle?
### Functions not documented yet
- add
- sub
- sub
- mul
- div
- union
- every
- when
- off
- jux
- append
- superimpose
- internal Pattern functions?
- groove, TODO move to core from https://github.com/tidalcycles/strudel/blob/main/repl/src/groove.ts
## Tone API
TODO, see https://github.com/tidalcycles/strudel/blob/main/repl/src/tone.ts
## Tonal API
TODO, see
- https://github.com/tidalcycles/strudel/blob/main/repl/src/tonal.ts
- https://github.com/tidalcycles/strudel/blob/main/repl/src/voicings.ts
## MIDI API
TODO, see https://github.com/tidalcycles/strudel/blob/main/repl/src/midi.ts
# Contributing
Contributions of any sort are very welcome! You can contribute by editing [this file](https://github.com/tidalcycles/strudel/blob/main/repl/src/tutorial/tutorial.mdx).
All you need is a github account.
If you want to run the tutorial locally, you can clone the and run:
```sh
cd repl && npm i && npm run tutorial
```
If you want to contribute in another way, either
- [fork strudel repo on GitHub](https://github.com/tidalcycles/strudel)
- [Join the Discord Channel](https://discord.gg/remJ6gQA)
- [play with the Strudel REPL](https://strudel.tidalcycles.org/)

View File

@ -1,8 +1,9 @@
import { useEffect, useRef, useState } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import type { ToneEventCallback } from 'tone';
import * as Tone from 'tone';
import { TimeSpan } from '../../strudel.mjs';
import type { Hap } from './types';
import usePostMessage from './usePostMessage';
export declare interface UseCycleProps {
onEvent: ToneEventCallback<any>;
@ -21,31 +22,22 @@ function useCycle(props: UseCycleProps) {
// pull events with onQuery + count up to next cycle
const query = (cycle = activeCycle()) => {
const timespan = new TimeSpan(cycle, cycle + 1);
const _events = onQuery?.(timespan) || [];
onSchedule?.(_events, cycle);
schedule(_events, cycle);
};
const schedule = (events: any[], cycle = activeCycle()) => {
const events = onQuery?.(timespan) || [];
onSchedule?.(events, cycle);
// cancel events after current query. makes sure no old events are player for rescheduled cycles
// console.log('schedule', cycle);
const timespan = new TimeSpan(cycle, cycle + 1);
// query next cycle in the middle of the current
const cancelFrom = timespan.begin.valueOf();
Tone.Transport.cancel(cancelFrom);
const queryNextTime = (cycle + 1) * cycleDuration - 0.1;
const delta = queryNextTime - Tone.Transport.seconds;
if (delta < 0.2) {
// if calling Tone.Transport.schedule barely before the scheduled time, it sometimes happen that the event is swallowed
// i think this has something to do with the fact that Tone.Transport.schedule is called with a time that is slightly before the scheduled time
// so, if the delta is too small (using 0.2 for no specific reason), just schedule directly
// this if branch should only be entered if the user triggers the scheduling, to make sure no endless recursion is happening
// const queryNextTime = (cycle + 1) * cycleDuration - 0.1;
const queryNextTime = (cycle + 1) * cycleDuration - 0.5;
// if queryNextTime would be before current time, execute directly (+0.1 for safety that it won't miss)
const t = Math.max(Tone.Transport.seconds, queryNextTime) + 0.1;
Tone.Transport.schedule(() => {
query(cycle + 1);
} else {
Tone.Transport.schedule(() => {
query(cycle + 1);
}, queryNextTime);
}
}, t);
// schedule events for next cycle
events
?.filter((event) => event.part.begin.valueOf() === event.whole.begin.valueOf())
@ -53,7 +45,7 @@ function useCycle(props: UseCycleProps) {
Tone.Transport.schedule((time) => {
const toneEvent = {
time: event.part.begin.valueOf(),
duration: event.whole.end.valueOf() - event.whole.begin.valueOf(),
duration: event.whole.end.sub(event.whole.begin).valueOf(),
value: event.value,
};
onEvent(time, toneEvent);
@ -63,10 +55,9 @@ function useCycle(props: UseCycleProps) {
useEffect(() => {
ready && query();
}, [onEvent, onSchedule, onQuery]);
}, [onEvent, onSchedule, onQuery, ready]);
const start = async () => {
console.log('start');
setStarted(true);
await Tone.start();
Tone.Transport.start('+0.1');
@ -77,7 +68,7 @@ function useCycle(props: UseCycleProps) {
Tone.Transport.pause();
};
const toggle = () => (started ? stop() : start());
return { start, stop, onEvent, started, toggle, schedule, query, activeCycle };
return { start, stop, setStarted, onEvent, started, toggle, query, activeCycle };
}
export default useCycle;

View File

@ -0,0 +1,11 @@
import { useEffect } from 'react';
function usePostMessage(listener) {
useEffect(() => {
window.addEventListener('message', listener);
return () => window.removeEventListener('message', listener);
}, [listener]);
return (data) => window.postMessage(data, '*');
}
export default usePostMessage;

153
repl/src/useRepl.ts Normal file
View File

@ -0,0 +1,153 @@
import { useCallback, useLayoutEffect, useState, useMemo, useEffect } from 'react';
import { isNote } from 'tone';
import { evaluate } from './evaluate';
import { useWebMidi } from './midi';
import type { Pattern } from './types';
import useCycle from './useCycle';
import usePostMessage from './usePostMessage';
let s4 = () => {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
};
function useRepl({ tune, defaultSynth, autolink = true }) {
const id = useMemo(() => s4(), []);
const [code, setCode] = useState<string>(tune);
const [activeCode, setActiveCode] = useState<string>();
const [log, setLog] = useState('');
const [error, setError] = useState<Error>();
const [pattern, setPattern] = useState<Pattern>();
const dirty = code !== activeCode;
const activateCode = (_code = code) => {
!cycle.started && cycle.start();
broadcast({ type: 'start', from: id });
if (activeCode && !dirty) {
setError(undefined);
return;
}
try {
const parsed = evaluate(_code);
setPattern(() => parsed.pattern);
if (autolink) {
window.location.hash = '#' + encodeURIComponent(btoa(code));
}
setError(undefined);
setActiveCode(_code);
} catch (err: any) {
setError(err);
}
};
const pushLog = (message: string) => setLog((log) => log + `${log ? '\n\n' : ''}${message}`);
// logs events of cycle
const logCycle = (_events: any, cycle: any) => {
if (_events.length) {
pushLog(`# cycle ${cycle}\n` + _events.map((e: any) => e.show()).join('\n'));
}
};
// cycle hook to control scheduling
const cycle = useCycle({
onEvent: useCallback((time, event) => {
try {
if (!event.value?.onTrigger) {
const note = event.value?.value || event.value;
if (!isNote(note)) {
throw new Error('not a note: ' + note);
}
if (defaultSynth) {
defaultSynth.triggerAttackRelease(note, event.duration, time);
} else {
throw new Error('no defaultSynth passed to useRepl.');
}
/* console.warn('no instrument chosen', event);
throw new Error(`no instrument chosen for ${JSON.stringify(event)}`); */
} else {
const { onTrigger } = event.value;
onTrigger(time, event);
}
} catch (err: any) {
console.warn(err);
err.message = 'unplayable event: ' + err?.message;
pushLog(err.message); // not with setError, because then we would have to setError(undefined) on next playable event
}
}, []),
onQuery: useCallback(
(span) => {
try {
return pattern?.query(span) || [];
} catch (err: any) {
setError(err);
return [];
}
},
[pattern]
),
onSchedule: useCallback((_events, cycle) => logCycle(_events, cycle), [pattern]),
ready: !!pattern,
});
const broadcast = usePostMessage(({ data: { from, type } }) => {
if (type === 'start' && from !== id) {
// console.log('message', from, type);
cycle.setStarted(false);
setActiveCode(undefined);
}
});
// set active pattern on ctrl+enter
/* useLayoutEffect(() => {
// TODO: make sure this is only fired when editor has focus
const handleKeyPress = (e: any) => {
if (e.ctrlKey || e.altKey) {
switch (e.code) {
case 'Enter':
activateCode();
!cycle.started && cycle.start();
break;
case 'Period':
cycle.stop();
}
}
};
document.addEventListener('keypress', handleKeyPress);
return () => document.removeEventListener('keypress', handleKeyPress);
}, [pattern, code]); */
/* useWebMidi({
ready: useCallback(({ outputs }) => {
pushLog(`WebMidi ready! Just add .midi(${outputs.map((o) => `"${o.name}"`).join(' | ')}) to the pattern. `);
}, []),
connected: useCallback(({ outputs }) => {
pushLog(`Midi device connected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
}, []),
disconnected: useCallback(({ outputs }) => {
pushLog(`Midi device disconnected! Available: ${outputs.map((o) => `"${o.name}"`).join(', ')}`);
}, []),
}); */
const togglePlay = () => {
if (!cycle.started) {
activateCode();
} else {
cycle.stop();
}
};
return {
code,
setCode,
pattern,
error,
cycle,
setPattern,
dirty,
log,
togglePlay,
activateCode,
activeCode,
pushLog,
};
}
export default useRepl;

53
repl/src/voicings.ts Normal file
View File

@ -0,0 +1,53 @@
import { Pattern as _Pattern, stack, Hap, reify } from '../../strudel.mjs';
import _voicings from 'chord-voicings';
const { dictionaryVoicing, minTopNoteDiff, lefthand } = _voicings;
const getVoicing = (chord, lastVoicing, range = ['F3', 'A4']) =>
dictionaryVoicing({
chord,
dictionary: lefthand,
range,
picker: minTopNoteDiff,
lastVoicing,
});
const Pattern = _Pattern as any;
Pattern.prototype.fmapNested = function (func) {
return new Pattern((span) =>
this.query(span)
.map((event) =>
reify(func(event))
.query(span)
.map((hap) => new Hap(event.whole, event.part, hap.value))
)
.flat()
);
};
Pattern.prototype.voicings = function (range) {
let lastVoicing;
if (!range?.length) {
// allows to pass empty array, if too lazy to specify range
range = ['F3', 'A4'];
}
return this.fmapNested((event) => {
lastVoicing = getVoicing(event.value, lastVoicing, range);
return stack(...lastVoicing);
});
};
Pattern.prototype.chordBass = function () { // range = ['G1', 'C3']
return this._mapNotes((value) => {
console.log('value',value);
const [_, root] = value.value.match(/^([a-gC-G])[b#]?.*$/);
const bassNote = root + '2';
return { ...value, value: bassNote };
});
};
Pattern.prototype.define('voicings', (range, pat) => pat.voicings(range), { composable: true });
Pattern.prototype.define('chordBass', (pat) => {
console.log('call chordBass ...', pat);
return pat.chordBass()
}, { composable: true });

View File

@ -1,8 +1,8 @@
module.exports = {
content: ['./public/**/*.html', './src/**/*.{js,jsx,ts,tsx}'],
content: ['./public/**/*.html', './src/**/*.{js,jsx,ts,tsx,mdx}'],
// specify other options here
theme: {
extend: {},
},
plugins: [require('@tailwindcss/forms')],
plugins: [require('@tailwindcss/forms'), require('@tailwindcss/typography')],
};

View File

@ -1,5 +1,5 @@
{
"include": ["src", "types", "public/hot.js"],
"include": ["src", "types", "public/hot.js", "tutorial"],
"compilerOptions": {
"allowJs": true,
"module": "esnext",

View File

@ -1,4 +1,5 @@
import Fraction from 'fraction.js'
import { compose } from 'ramda'; // will remove this as soon as compose is implemented here
// Removes 'None' values from given list
const removeUndefineds = xs => xs.filter(x => x != undefined)
@ -7,17 +8,25 @@ const flatten = arr => [].concat(...arr)
const id = a => a
function curry(func) {
return function curried(...args) {
export function curry(func, overload) {
const fn = function curried(...args) {
if (args.length >= func.length) {
return func.apply(this, args)
}
else {
return function(...args2) {
const partial = function(...args2) {
return curried.apply(this, args.concat(args2))
}
if (overload) {
overload(partial, args);
}
return partial;
}
}
if (overload) { // overload function without args... needed for chordBass.transpose(2)
overload(fn, []);
}
return fn;
}
// Returns the start of the cycle.
@ -100,8 +109,12 @@ class TimeSpan {
}
withTime(func_time) {
// Applies given function to both the begin and end time value of the timespan"""
return(new TimeSpan(func_time(this.begin), func_time(this.end)))
// Applies given function to both the begin and end time value of the timespan"""
return(new TimeSpan(func_time(this.begin), func_time(this.end)))
}
withEnd(func_time) {
// Applies given function to both the begin and end time value of the timespan"""
return(new TimeSpan(this.begin, func_time(this.end)))
}
intersection(other) {
@ -204,8 +217,26 @@ class Hap {
}
class Pattern {
// the following functions will get patternFactories as nested functions:
constructor(query) {
this.query = query
this.query = query;
// the following code will assign `patternFactories` as child functions to all methods of Pattern that don't start with '_'
const proto = Object.getPrototypeOf(this);
// proto.patternified is defined below Pattern class. You can add more patternified functions from outside.
proto.patternified.forEach((prop) => {
// patternify function
this[prop] = (...args) => this._patternify(Pattern.prototype['_' + prop])(...args);
// with the following, you can do, e.g. `stack(c3).fast.slowcat(1, 2, 4, 8)` instead of `stack(c3).fast(slowcat(1, 2, 4, 8))`
Object.assign(
this[prop],
Object.fromEntries(
Object.entries(Pattern.prototype.factories).map(([type, func]) => [
type,
(...args) => this[prop](func(...args)),
])
)
);
});
}
_splitQueries() {
@ -275,10 +306,10 @@ class Pattern {
// resolve wholes, applies a given pattern of values to that
// pattern of functions.
const pat_func = this
query = function(span) {
const query = function(span) {
const event_funcs = pat_func.query(span)
const event_vals = pat_val.query(span)
apply = function(event_func, event_val) {
const apply = function(event_func, event_val) {
const s = event_func.part.intersection(event_val.part)
if (s == undefined) {
return undefined
@ -293,7 +324,7 @@ class Pattern {
appBoth(pat_val) {
// Tidal's <*>
const whole_func = function(span_a, span_b) {
if (span_a == undefined || span_B == undefined) {
if (span_a == undefined || span_b == undefined) {
return undefined
}
return span_a.intersection_e(span_b)
@ -461,38 +492,21 @@ class Pattern {
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) {
// Equivalent of Tidal's <~ operator
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) {
// Equivalent of Tidal's ~> operator
return this._early(0-offset)
}
late(...factor) {
return this._patternify(Pattern.prototype._late)(...factor)
}
when(binary_pat, func) {
//binary_pat = sequence(binary_pat)
const true_pat = binary_pat._filterValues(id)
@ -549,8 +563,55 @@ class Pattern {
return stack([left,func(right)])
}
// is there a different name for those in tidal?
stack(...pats) {
return stack(this, ...pats)
}
sequence(...pats) {
return sequence(this, ...pats)
}
superimpose(...funcs) {
return this.stack(...funcs.map((func) => func(this)));
}
edit(...funcs) {
return stack(...funcs.map(func => func(this)));
}
_bypass(on) {
on = Boolean(parseInt(on));
return on ? silence : this;
}
hush() {
return silence;
}
/*
_resolveTies() {
return this._withEvents((events)=>{
return events.reduce((tied, event, i) => {
const value = event.value?.value || event.value;
if (value !== '_') {
return tied.concat([event]);
}
console.log('tie!', lastEvent);
tied[i - 1] = tied[i - 1].withSpan((span) => span.withEnd((_) => event.part.end));
// above only works if the tie is not across a cycle boundary... how to do that???
// TODO: handle case that there is a gap between tied[i-1].part.end and event.part.begin => tie would make no sense
return tied;
}, []);
})
} */
}
// methods of Pattern that get callable factories
Pattern.prototype.patternified = ['fast', 'slow', 'early', 'late'];
// methods that create patterns, which are added to patternified Pattern methods
Pattern.prototype.factories = { pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr};
// the magic happens in Pattern constructor. Keeping this in prototype enables adding methods from the outside (e.g. see tonal.ts)
const silence = new Pattern(_ => [])
function pure(value) {
@ -566,7 +627,7 @@ function steady(value) {
}
function reify(thing) {
if (thing.constructor.name == "Pattern") {
if (thing?.constructor?.name == "Pattern") {
return thing
}
return pure(thing)
@ -583,8 +644,12 @@ function slowcat(...pats) {
// successively, one per cycle.
pats = pats.map(reify)
const query = function(span) {
const pat_n = Math.floor(span.begin) % pats.length
const pat_n = Math.floor(span.begin) % pats.length;
const pat = pats[pat_n]
if (!pat) {
// pat_n can be negative, if the span is in the past..
return [];
}
// A bit of maths to make sure that cycles from constituent patterns aren't skipped.
// For example if three patterns are slowcat-ed, the fourth cycle of the result should
// be the second (rather than fourth) cycle from the first pattern.
@ -702,10 +767,61 @@ const when = curry((binary, f, pat) => pat.when(binary, f))
const off = curry((t, f, pat) => pat.off(t,f))
const jux = curry((f, pat) => pat.jux(f))
const append = curry((a, pat) => pat.append(a))
const superimpose = curry((array, pat) => pat.superimpose(...array))
// problem: curried functions with spread arguments must have pat at the beginning
// with this, we cannot keep the pattern open at the end.. solution for now: use array to keep using pat as last arg
// these are the core composable functions. they are extended with Pattern.prototype.define below
Pattern.prototype.composable = { fast, slow, early, late, superimpose }
// adds Pattern.prototype.composable to given function as child functions
// then you can do transpose(2).late(0.2) instead of x => x.transpose(2).late(0.2)
export function makeComposable(func) {
Object.entries(Pattern.prototype.composable).forEach(([functionName, composable]) => {
// compose with dot
func[functionName] = (...args) => {
// console.log(`called ${functionName}(${args.join(',')})`);
const composition = compose(func, composable(...args));
// the composition itself must be composable too :)
// then you can do endless chaining transpose(2).late(0.2).fast(2) ...
return makeComposable(composition);
};
});
return func;
}
// this will add func as name to list of composable / patternified functions.
// those lists will be used in bootstrap to curry and compose everything, to support various call patterns
Pattern.prototype.define = (name, func, options = {}) => {
if (options.composable) {
Pattern.prototype.composable[name] = func;
}
if(options.patternified) {
Pattern.prototype.patternified = Pattern.prototype.patternified.concat([name]);
}
}
// Pattern.prototype.define('early', (a, pat) => pat.early(a), { patternified: true, composable: true });
Pattern.prototype.define('hush', (pat) => pat.hush(), { patternified: false, composable: true });
Pattern.prototype.define('bypass', (pat) => pat.bypass(on), { patternified: true, composable: true });
// call this after all Patter.prototype.define calls have been executed! (right before evaluate)
Pattern.prototype.bootstrap = () => {
// makeComposable(Pattern.prototype);
const bootstrapped = Object.fromEntries(Object.entries(Pattern.prototype.composable).map(([functionName, composable]) => {
if(Pattern.prototype[functionName]) {
// without this, 'C^7'.m.chordBass.transpose(2) will throw "C^7".m.chordBass.transpose is not a function
Pattern.prototype[functionName] = makeComposable(Pattern.prototype[functionName]); // is this needed?
}
return [functionName, curry(composable, makeComposable)];
}));
return bootstrapped;
}
export {Fraction, TimeSpan, Hap, Pattern,
pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr, reify, silence,
fast, slow, early, late, rev,
add, sub, mul, div, union, every, when, off, jux, append
add, sub, mul, div, union, every, when, off, jux, append, superimpose
}