mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-14 07:08:30 +00:00
Merge branch 'main' into bug-when
This commit is contained in:
commit
9e05da552f
@ -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;
|
||||
|
||||
|
||||
@ -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
828
docs/_snowpack/pkg/chord-voicings.js
Normal file
828
docs/_snowpack/pkg/chord-voicings.js
Normal 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 Fisher–Yates 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__;
|
||||
2364
docs/_snowpack/pkg/common/index.es-d2606df1.js
Normal file
2364
docs/_snowpack/pkg/common/index.es-d2606df1.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
606
docs/_snowpack/pkg/ramda.js
Normal 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 };
|
||||
3
docs/_snowpack/pkg/react.js
vendored
3
docs/_snowpack/pkg/react.js
vendored
@ -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
174
docs/dist/App.js
vendored
@ -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
36
docs/dist/evaluate.js
vendored
Normal 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
6
docs/dist/groove.js
vendored
Normal 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
3
docs/dist/midi.js
vendored
@ -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
120
docs/dist/parse.js
vendored
@ -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);
|
||||
}
|
||||
|
||||
4
docs/dist/shapeshifter.js
vendored
4
docs/dist/shapeshifter.js
vendored
@ -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
71
docs/dist/tonal.js
vendored
Normal 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
70
docs/dist/tone.js
vendored
@ -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
381
docs/dist/tunes.js
vendored
@ -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
27
docs/dist/useCycle.js
vendored
@ -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
9
docs/dist/usePostMessage.js
vendored
Normal 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
105
docs/dist/useRepl.js
vendored
Normal 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
37
docs/dist/voicings.js
vendored
Normal 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});
|
||||
418
docs/global.css
418
docs/global.css
@ -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));
|
||||
|
||||
BIN
docs/tutorial/favicon.e3ab9dd9.ico
Normal file
BIN
docs/tutorial/favicon.e3ab9dd9.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
1312
docs/tutorial/index.1e09ac22.css
Normal file
1312
docs/tutorial/index.1e09ac22.css
Normal file
File diff suppressed because it is too large
Load Diff
1
docs/tutorial/index.1e09ac22.css.map
Normal file
1
docs/tutorial/index.1e09ac22.css.map
Normal file
File diff suppressed because one or more lines are too long
108738
docs/tutorial/index.a1b5cf57.js
Normal file
108738
docs/tutorial/index.a1b5cf57.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/tutorial/index.a1b5cf57.js.map
Normal file
1
docs/tutorial/index.a1b5cf57.js.map
Normal file
File diff suppressed because one or more lines are too long
16
docs/tutorial/index.html
Normal file
16
docs/tutorial/index.html
Normal 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
3
repl/.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
.snowpack
|
||||
build
|
||||
node_modules
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
.parcel-cache
|
||||
@ -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).
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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
6047
repl/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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",
|
||||
|
||||
@ -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,
|
||||
|
||||
213
repl/src/App.tsx
213
repl/src/App.tsx
@ -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
45
repl/src/evaluate.ts
Normal 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
10
repl/src/groove.ts
Normal 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 });
|
||||
@ -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,
|
||||
});
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
99
repl/src/tonal.ts
Normal 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 });
|
||||
110
repl/src/tone.ts
110
repl/src/tone.ts
@ -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 });
|
||||
|
||||
@ -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);
|
||||
`;
|
||||
|
||||
54
repl/src/tutorial/MiniRepl.tsx
Normal file
54
repl/src/tutorial/MiniRepl.tsx
Normal 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;
|
||||
30
repl/src/tutorial/Tutorial.js
Normal file
30
repl/src/tutorial/Tutorial.js
Normal 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')
|
||||
);
|
||||
16
repl/src/tutorial/index.html
Normal file
16
repl/src/tutorial/index.html
Normal 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>
|
||||
13
repl/src/tutorial/style.css
Normal file
13
repl/src/tutorial/style.css
Normal 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;
|
||||
}
|
||||
402
repl/src/tutorial/tutorial.mdx
Normal file
402
repl/src/tutorial/tutorial.mdx
Normal 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/)
|
||||
@ -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;
|
||||
|
||||
11
repl/src/usePostMessage.ts
Normal file
11
repl/src/usePostMessage.ts
Normal 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
153
repl/src/useRepl.ts
Normal 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
53
repl/src/voicings.ts
Normal 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 });
|
||||
@ -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')],
|
||||
};
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"include": ["src", "types", "public/hot.js"],
|
||||
"include": ["src", "types", "public/hot.js", "tutorial"],
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"module": "esnext",
|
||||
|
||||
174
strudel.mjs
174
strudel.mjs
@ -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
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user