diff --git a/repl/krill-parser.js b/repl/krill-parser.js index 56fc1820..2b307417 100644 --- a/repl/krill-parser.js +++ b/repl/krill-parser.js @@ -262,7 +262,7 @@ function peg$parse(input, options) { var peg$f3 = function(sc) { sc.arguments_.alignment = "t"; return sc;}; var peg$f4 = function(a) { return { weight: a} }; 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$f6 = function(p, s, r) { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r || 0 } } } }; 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 } } } }; @@ -273,7 +273,7 @@ function peg$parse(input, options) { 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$f17 = function(p, s, r) { 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}}}; @@ -1022,7 +1022,7 @@ function peg$parse(input, options) { } function peg$parseslice_bjorklund() { - var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9; + var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13; s0 = peg$currPos; if (input.charCodeAt(peg$currPos) === 40) { @@ -1043,16 +1043,26 @@ function peg$parse(input, options) { s7 = peg$parsenumber(); if (s7 !== peg$FAILED) { s8 = peg$parsews(); + s9 = peg$parsecomma(); + if (s9 === peg$FAILED) { + s9 = null; + } + s10 = peg$parsews(); + s11 = peg$parsenumber(); + if (s11 === peg$FAILED) { + s11 = null; + } + s12 = peg$parsews(); if (input.charCodeAt(peg$currPos) === 41) { - s9 = peg$c17; + s13 = peg$c17; peg$currPos++; } else { - s9 = peg$FAILED; + s13 = peg$FAILED; if (peg$silentFails === 0) { peg$fail(peg$e24); } } - if (s9 !== peg$FAILED) { + if (s13 !== peg$FAILED) { peg$savedPos = s0; - s0 = peg$f6(s3, s7); + s0 = peg$f6(s3, s7, s11); } else { peg$currPos = s0; s0 = peg$FAILED; @@ -1383,7 +1393,7 @@ function peg$parse(input, options) { } function peg$parsebjorklund() { - var s0, s1, s2, s3, s4, s5; + var s0, s1, s2, s3, s4, s5, s6, s7; s0 = peg$currPos; if (input.substr(peg$currPos, 6) === peg$c23) { @@ -1400,8 +1410,13 @@ function peg$parse(input, options) { s4 = peg$parsews(); s5 = peg$parseint(); if (s5 !== peg$FAILED) { + s6 = peg$parsews(); + s7 = peg$parseint(); + if (s7 === peg$FAILED) { + s7 = null; + } peg$savedPos = s0; - s0 = peg$f17(s3, s5); + s0 = peg$f17(s3, s5, s7); } else { peg$currPos = s0; s0 = peg$FAILED; diff --git a/repl/krill.pegjs b/repl/krill.pegjs index 84e7e8df..c24be8fb 100644 --- a/repl/krill.pegjs +++ b/repl/krill.pegjs @@ -104,8 +104,8 @@ slice_weight = "@" a:number 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 } } } } +slice_bjorklund = "(" ws p:number ws comma ws s:number ws comma? ws r:number? ws ")" + { return { operator : { type_: "bjorklund", arguments_ :{ pulse: p, step:s, rotation:r || 0 } } } } slice_slow = "/"a:number { return { operator : { type_: "stretch", arguments_ :{ amount:a } } } } @@ -145,7 +145,7 @@ struct = "struct" ws s:sequence_or_operator target = "target" ws quote s:step quote { return { name: "target", args : { name:s}}} -bjorklund = "euclid" ws p:int ws s:int +bjorklund = "euclid" ws p:int ws s:int ws r:int? { return { name: "bjorklund", args :{ pulse: parseInt(p), step:parseInt(s) }}} slow = "slow" ws a:number diff --git a/repl/package-lock.json b/repl/package-lock.json index cebf10b5..b8747fba 100644 --- a/repl/package-lock.json +++ b/repl/package-lock.json @@ -7,6 +7,7 @@ "dependencies": { "@tonaljs/tonal": "^4.6.5", "@tonejs/piano": "^0.2.1", + "bjork": "^0.0.1", "chord-voicings": "^0.0.1", "codemirror": "^5.65.1", "estraverse": "^5.3.0", @@ -4195,6 +4196,11 @@ "node": ">=8" } }, + "node_modules/bjork": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/bjork/-/bjork-0.0.1.tgz", + "integrity": "sha1-br1a3pkWSwvgMIeI1kaRQ9XJrZw=" + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -15511,6 +15517,11 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bjork": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/bjork/-/bjork-0.0.1.tgz", + "integrity": "sha1-br1a3pkWSwvgMIeI1kaRQ9XJrZw=" + }, "bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", diff --git a/repl/package.json b/repl/package.json index f7d79c6e..5e3d1e26 100644 --- a/repl/package.json +++ b/repl/package.json @@ -13,6 +13,7 @@ "dependencies": { "@tonaljs/tonal": "^4.6.5", "@tonejs/piano": "^0.2.1", + "bjork": "^0.0.1", "chord-voicings": "^0.0.1", "codemirror": "^5.65.1", "estraverse": "^5.3.0", diff --git a/repl/src/euclid.mjs b/repl/src/euclid.mjs new file mode 100644 index 00000000..c05a64ee --- /dev/null +++ b/repl/src/euclid.mjs @@ -0,0 +1,17 @@ +import { Pattern } from '../../strudel.mjs'; +import bjork from 'bjork'; +import { rotate } from '../../util.mjs'; + +const euclid = (pulses, steps, rotation = 0) => { + const b = bjork(steps, pulses); + if (rotation) { + return rotate(b, -rotation); + } + return b; +}; + +Pattern.prototype.euclid = function (pulses, steps, rotation = 0) { + return this.struct(euclid(pulses, steps, rotation)); +}; + +export default euclid; diff --git a/repl/src/evaluate.ts b/repl/src/evaluate.ts index dc59383f..07388ded 100644 --- a/repl/src/evaluate.ts +++ b/repl/src/evaluate.ts @@ -5,7 +5,8 @@ import './voicings'; import './tonal.mjs'; import './xen.mjs'; import './tune.mjs'; -import './tune.mjs'; +import './euclid.mjs'; +import euclid from './euclid.mjs'; import './pianoroll.mjs'; import './draw.mjs'; import * as uiHelpers from './ui.mjs'; @@ -36,7 +37,7 @@ hackLiteral(String, ['mini', 'm'], bootstrapped.mini); // comment out this line hackLiteral(String, ['pure', 'p'], bootstrapped.pure); // comment out this line if you panic // this will add everything to global scope, which is accessed by eval -Object.assign(globalThis, bootstrapped, Tone, toneHelpers, voicingHelpers, drawHelpers, uiHelpers, { gist }); +Object.assign(globalThis, bootstrapped, Tone, toneHelpers, voicingHelpers, drawHelpers, uiHelpers, { gist, euclid }); export const evaluate: any = async (code: string) => { const shapeshifted = shapeshifter(code); // transform syntactically correct js code to semantically usable code diff --git a/repl/src/parse.ts b/repl/src/parse.ts index 37a5c4dd..cc12b267 100644 --- a/repl/src/parse.ts +++ b/repl/src/parse.ts @@ -14,6 +14,8 @@ const applyOptions = (parent: any) => (pat: any, i: number) => { case 'stretch': const speed = new Fraction(operator.arguments_.amount).inverse().valueOf(); return reify(pat).fast(speed); + case 'bjorklund': + return pat.euclid(operator.arguments_.pulse, operator.arguments_.step, operator.arguments_.rotation); // TODO: case 'fixed-step': "%" } console.warn(`operator "${operator.type_}" not implemented`); diff --git a/repl/src/tunes.ts b/repl/src/tunes.ts index d333898a..cddee2ee 100644 --- a/repl/src/tunes.ts +++ b/repl/src/tunes.ts @@ -543,3 +543,32 @@ stack( .fast(2 / 3) .tone(p.toDestination()) )`; + +export const festivalOfFingers = `const chords = ""; +piano().then(p=>stack( + chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), + chords.rootNotes(2).struct("x(4,8,-2)"), + chords.rootNotes(4) + .scale(slowcat('C minor','F dorian','G dorian','F# mixolydian')) + .struct("x(3,8,-2)".fast(2)) + .scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/4")) +).slow(2) + //.pianoroll() + .velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8)) + .tone(p.chain(out())))`; + +export const festivalOfFingers2 = `const chords = ""; + const scales = slowcat('C minor','F dorian','G dorian','F# mixolydian') + piano().then(p=>stack( + chords.voicings().struct("x(3,8,-1)").velocity(.5).off(1/7,x=>x.transpose(12).velocity(.2)), + chords.rootNotes(2).struct("x(4,8)"), + chords.rootNotes(4) + .scale(scales) + .struct("x(3,8,-2)".fast(2)) + .scaleTranspose("0 4 0 6".early(".125 .5")).layer(scaleTranspose("0,<2 [4,6] [5,7]>/3")) + ).slow(2).transpose(-1) + .legato(cosine.struct("x*8").add(4/5).mul(4/5).fast(8)) + .velocity(sine.struct("x*8").add(3/5).mul(2/5).fast(8)) + // .pianoroll() + .tone(p.chain(out())).fast(3/4) + )`; diff --git a/repl/src/tutorial/tutorial.mdx b/repl/src/tutorial/tutorial.mdx index 05dc1012..c0f7d6e8 100644 --- a/repl/src/tutorial/tutorial.mdx +++ b/repl/src/tutorial/tutorial.mdx @@ -180,13 +180,13 @@ In essence, the `x!n` is like a shortcut for `[x*n]@n`. 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 "%" +- [x] Euclidean algorithm "c3(3,2,1)" TODO: document +- [ ] Tie symbols "\_" +- [ ] feet marking "." +- [ ] random choice "|" +- [ ] Random removal "?" +- [ ] Polymetric sequences "{ ... }" +- [ ] Fixed steps using "%"
diff --git a/util.mjs b/util.mjs index 0ecd4306..4cfea99b 100644 --- a/util.mjs +++ b/util.mjs @@ -39,3 +39,6 @@ export const getPlayableNoteValue = (event) => { } return note; }; + +// rotate array by n steps (to the left) +export const rotate = (arr, n) => arr.slice(n).concat(arr.slice(0, n));