diff --git a/repl/package-lock.json b/repl/package-lock.json index 460f0e86..ff3ac9f4 100644 --- a/repl/package-lock.json +++ b/repl/package-lock.json @@ -10,6 +10,7 @@ "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", @@ -6861,6 +6862,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ramda": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", + "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, "node_modules/raw-body": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", @@ -14071,6 +14081,11 @@ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", "dev": true }, + "ramda": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.28.0.tgz", + "integrity": "sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==" + }, "raw-body": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.2.tgz", diff --git a/repl/package.json b/repl/package.json index 4a669878..17f794ad 100644 --- a/repl/package.json +++ b/repl/package.json @@ -14,6 +14,7 @@ "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", diff --git a/repl/src/tonal.ts b/repl/src/tonal.ts index cf6bb683..47fe8421 100644 --- a/repl/src/tonal.ts +++ b/repl/src/tonal.ts @@ -1,5 +1,5 @@ import { Note, Interval, Scale } from '@tonaljs/tonal'; -import { Pattern as _Pattern, curry } from '../../strudel.mjs'; +import { Pattern as _Pattern, curry, makeComposable } from '../../strudel.mjs'; const Pattern = _Pattern as any; @@ -75,10 +75,15 @@ Pattern.prototype._transpose = function (intervalOrSemitones: string | number) { }); }; -// TODO: find out how to patternify this function when it's standalone +// example: transpose(3).late(0.2) will be equivalent to compose(transpose(3), late(0.2)) +export const transpose = curry( + (a, pat) => pat.transpose(a), + (partial) => makeComposable(partial) +); +// 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 -export const transpose = curry((a, pat) => pat.transpose(a)) Pattern.prototype._scaleTranspose = function (offset: number | string) { return this._mapNotes(({ value, scale }: NoteEvent) => { @@ -93,4 +98,4 @@ Pattern.prototype._scale = function (scale: string) { }; Pattern.prototype.patternified = Pattern.prototype.patternified.concat(['transpose', 'scaleTranspose', 'scale']); -// Object.assign(Pattern.prototype.modifiers, { transpose }) +Object.assign(Pattern.prototype.composable, { transpose }); diff --git a/repl/src/tunes.ts b/repl/src/tunes.ts index d44d1f91..be653cd5 100644 --- a/repl/src/tunes.ts +++ b/repl/src/tunes.ts @@ -363,4 +363,16 @@ export const confusedPhoneDynamic = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2)) .scaleTranspose(slowcat(0,1,2,1).slow(2)) .synth('triangle').gain(0.2).filter(1500)`; +export const confusedPhonePartial = `stack('[g2 ~@1.3] [c3 ~@1.3]'.mini.slow(2)) +.superimpose( + transpose(-12).late(0), + transpose(7).late(0.2), + transpose(10).late(0.4), + transpose(12).late(0.6), + transpose(24).late(0.8) +) +.scale(sequence('C dorian', 'C mixolydian').slow(4)) +.scaleTranspose(slowcat(0,1,2,1).slow(2)) +.synth('triangle').gain(0.2).filter(1500)`; + export default swimming; diff --git a/strudel.mjs b/strudel.mjs index aef9b8af..a6aed80d 100644 --- a/strudel.mjs +++ b/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,15 +8,19 @@ const flatten = arr => [].concat(...arr) const id = a => a -export function curry(func) { +export function curry(func, overload) { return 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; } } } @@ -740,6 +745,22 @@ 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)) +Pattern.prototype.composable = { fast, slow, early, late } + +// 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(',')})`); + return compose(func, composable(...args)); + }; + }); +} +// TODO: automatically make curried functions curried functions composable +// see tonal.ts#transpose for an example + export {Fraction, TimeSpan, Hap, Pattern, pure, stack, slowcat, fastcat, cat, timeCat, sequence, polymeter, pm, polyrhythm, pr, reify, silence, fast, slow, early, late, rev,