Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move stuff to new register function #295

Merged
merged 16 commits into from
Dec 11, 2022
4 changes: 2 additions & 2 deletions packages/core/evaluate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Copyright (C) 2022 Strudel contributors - see <https://github.com/tidalcycles/st
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

import { isPattern, Pattern } from './index.mjs';
import { isPattern } from './index.mjs';

let scoped = false;
export const evalScope = async (...args) => {
Expand All @@ -19,7 +19,7 @@ export const evalScope = async (...args) => {
console.warn(`evalScope: module with index ${i} could not be loaded:`, result.reason);
}
});
Object.assign(globalThis, ...modules, Pattern.prototype.bootstrap());
Object.assign(globalThis, ...modules);
};

function safeEval(str, options = {}) {
Expand Down
101 changes: 3 additions & 98 deletions packages/core/pattern.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -581,21 +581,6 @@ export class Pattern {
);
}

patternify(join, func) {
const pat = this;
return function (...args) {
// the problem here: args could be a pattern that has been
// turned into an object to add location to avoid object
// checking for every pattern method, we can remove it here...
// in the future, patternified args should be marked as well +
// some better object handling
args = args.map((arg) => (isPattern(arg) ? arg.fmap((value) => value.value || value) : arg));
const pat_arg = sequence(...args);
// arg.locations has to go somewhere..
return join(pat_arg.fmap((arg) => func.call(pat, arg)));
};
}

asNumber() {
return this.fmap(parseNumeral);
}
Expand Down Expand Up @@ -1277,9 +1262,9 @@ export function register(name, func) {
args = args.map(reify);
// For methods that take a single argument (plus 'this'), allow
// multiple arguments but sequence them
if (arity == 2 && args.length != 1) {
if (arity === 2 && args.length !== 1) {
args = [sequence(...args)];
} else if (arity != args.length + 1) {
} else if (arity !== args.length + 1) {
throw new Error(`.${name}() expects ${arity - 1} inputs but got ${args.length}.`);
}
return pfunc(...args, this);
Expand Down Expand Up @@ -1533,7 +1518,7 @@ export const { firstOf, every } = register(['firstOf', 'every'], function (n, fu

/**
* Like layer, but with a single function:
* @name _apply
* @name apply
* @memberof Pattern
* @example
* "<c3 eb3 g3>".scale('C minor').apply(scaleTranspose("0,2,4")).note()
Expand Down Expand Up @@ -1921,83 +1906,3 @@ const { loopAt, loopat } = register(['loopAt', 'loopat'], function (factor, pat)
const { loopAtCps, loopatcps } = register(['loopAtCps', 'loopatcps'], function (factor, cps, pat) {
return _loopAt(factor, pat, cps);
});

// 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 = { 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;
}

export const patternify = (f) => (pata, pat) => pata.fmap((a) => f.call(pat, a)).innerJoin();
export const patternify2 = (f) =>
function (pata, patb, pat) {
return pata
.fmap((a) => (b) => f.call(pat, a, b))
.appLeft(patb)
.innerJoin();
};
export const patternify3 = (f) => (pata, patb, patc, pat) =>
pata
.fmap((a) => (b) => (c) => f.call(pat, a, b, c))
.appLeft(patb)
.appLeft(patc)
.innerJoin();
export const patternify4 = (f) => (pata, patb, patc, patd, pat) =>
pata
.fmap((a) => (b) => (c) => (d) => f.call(pat, a, b, c, d))
.appLeft(patb)
.appLeft(patc)
.appLeft(patd)
.innerJoin();

Pattern.prototype.patternified = [];

// call this after all Pattern.prototype.define calls have been executed! (right before evaluate)
Pattern.prototype.bootstrap = function () {
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)];
}),
);

this.patternified.forEach((prop) => {
// the following will patternify all functions in Pattern.prototype.patternified
Pattern.prototype[prop] = function (...args) {
return this.patternify((x) => x.innerJoin(), Pattern.prototype['_' + prop])(...args);
};
});
return bootstrapped;
};

// 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.bootstrap(); // automatically bootstrap after new definition
};
130 changes: 48 additions & 82 deletions packages/core/signal.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/

import { Hap } from './hap.mjs';
import { Pattern, fastcat, reify, silence, stack, isPattern } from './pattern.mjs';
import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs';
import Fraction from './fraction.mjs';
import { id } from './util.mjs';

Expand Down Expand Up @@ -258,9 +258,9 @@ export const perlinWith = (pat) => {
*/
export const perlin = perlinWith(time.fmap((v) => Number(v)));

Pattern.prototype._degradeByWith = function (withPat, x) {
return this.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x));
};
export const degradeByWith = register('degradeByWith', (withPat, x, pat) =>
pat.fmap((a) => (_) => a).appLeft(withPat.filterValues((v) => v > x)),
);

/**
* Randomly removes events from the pattern by a given amount.
Expand All @@ -276,9 +276,9 @@ Pattern.prototype._degradeByWith = function (withPat, x) {
* @example
* s("[hh?0.2]*8")
*/
Pattern.prototype._degradeBy = function (x) {
return this._degradeByWith(rand, x);
};
export const degradeBy = register('degradeBy', function (x, pat) {
return pat._degradeByWith(rand, x);
});

/**
*
Expand All @@ -292,9 +292,7 @@ Pattern.prototype._degradeBy = function (x) {
* @example
* s("[hh?]*8")
*/
Pattern.prototype.degrade = function () {
return this._degradeBy(0.5);
};
export const degrade = register('degrade', (pat) => pat._degradeBy(0.5));

/**
* Inverse of {@link Pattern#degradeBy}: Randomly removes events from the pattern by a given amount.
Expand All @@ -309,26 +307,14 @@ Pattern.prototype.degrade = function () {
* @example
* s("hh*8").undegradeBy(0.2)
*/
Pattern.prototype._undegradeBy = function (x) {
return this._degradeByWith(
export const undegradeBy = register('undegradeBy', function (x, pat) {
return pat._degradeByWith(
rand.fmap((r) => 1 - r),
x,
);
};

Pattern.prototype.undegrade = function () {
return this._undegradeBy(0.5);
};

Pattern.prototype._sometimesBy = function (x, func) {
return stack(this._degradeBy(x), func(this._undegradeBy(1 - x)));
};
});

// https://github.com/tidalcycles/strudel/discussions/198
/* Pattern.prototype._sometimesBy = function (x, other) {
other = typeof other === 'function' ? other(this._undegradeBy(1 - x)) : reify(other)._undegradeBy(1 - x);
return stack(this._degradeBy(x), other);
}; */
export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5));

/**
*
Expand All @@ -343,24 +329,12 @@ Pattern.prototype._sometimesBy = function (x, func) {
* @example
* s("hh(3,8)").sometimesBy(.4, x=>x.speed("0.5"))
*/
Pattern.prototype.sometimesBy = function (patx, func) {
const pat = this;
return reify(patx)
.fmap((x) => pat._sometimesBy(x, func))
.innerJoin();
};

// why does this exist? it is identical to sometimesBy
Pattern.prototype._sometimesByPre = function (x, func) {
return stack(this._degradeBy(x), func(this).undegradeBy(1 - x));
};

Pattern.prototype.sometimesByPre = function (patx, func) {
const pat = this;
export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
return reify(patx)
.fmap((x) => pat._sometimesByPre(x, func))
.fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x))))
.innerJoin();
};
});

/**
*
Expand All @@ -373,20 +347,9 @@ Pattern.prototype.sometimesByPre = function (patx, func) {
* @example
* s("hh*4").sometimes(x=>x.speed("0.5"))
*/
Pattern.prototype.sometimes = function (func) {
return this._sometimesBy(0.5, func);
};

Pattern.prototype.sometimesPre = function (func) {
return this._sometimesByPre(0.5, func);
};

Pattern.prototype._someCyclesBy = function (x, func) {
return stack(
this._degradeByWith(rand._segment(1), x),
func(this._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
);
};
export const sometimes = register('sometimes', function (func, pat) {
return pat._sometimesBy(0.5, func);
});

/**
*
Expand All @@ -401,12 +364,17 @@ Pattern.prototype._someCyclesBy = function (x, func) {
* @example
* s("hh(3,8)").someCyclesBy(.3, x=>x.speed("0.5"))
*/
Pattern.prototype.someCyclesBy = function (patx, func) {
const pat = this;

export const someCyclesBy = register('someCyclesBy', function (patx, func, pat) {
return reify(patx)
.fmap((x) => pat._someCyclesBy(x, func))
.fmap((x) =>
stack(
pat._degradeByWith(rand._segment(1), x),
func(pat._degradeByWith(rand.fmap((r) => 1 - r)._segment(1), 1 - x)),
),
)
.innerJoin();
};
});

/**
*
Expand All @@ -418,9 +386,9 @@ Pattern.prototype.someCyclesBy = function (patx, func) {
* @example
* s("hh(3,8)").someCycles(x=>x.speed("0.5"))
*/
Pattern.prototype.someCycles = function (func) {
return this._someCyclesBy(0.5, func);
};
export const someCycles = register('someCycles', function (func, pat) {
return pat._someCyclesBy(0.5, func);
});

/**
*
Expand All @@ -432,9 +400,9 @@ Pattern.prototype.someCycles = function (func) {
* @example
* s("hh*8").often(x=>x.speed("0.5"))
*/
Pattern.prototype.often = function (func) {
return this.sometimesBy(0.75, func);
};
export const often = register('often', function (func, pat) {
return pat.sometimesBy(0.75, func);
});

/**
*
Expand All @@ -446,9 +414,9 @@ Pattern.prototype.often = function (func) {
* @example
* s("hh*8").rarely(x=>x.speed("0.5"))
*/
Pattern.prototype.rarely = function (func) {
return this.sometimesBy(0.25, func);
};
export const rarely = register('rarely', function (func, pat) {
return pat.sometimesBy(0.25, func);
});

/**
*
Expand All @@ -460,9 +428,9 @@ Pattern.prototype.rarely = function (func) {
* @example
* s("hh*8").almostNever(x=>x.speed("0.5"))
*/
Pattern.prototype.almostNever = function (func) {
return this.sometimesBy(0.1, func);
};
export const almostNever = register('almostNever', function (func, pat) {
return pat.sometimesBy(0.1, func);
});

/**
*
Expand All @@ -474,9 +442,9 @@ Pattern.prototype.almostNever = function (func) {
* @example
* s("hh*8").almostAlways(x=>x.speed("0.5"))
*/
Pattern.prototype.almostAlways = function (func) {
return this.sometimesBy(0.9, func);
};
export const almostAlways = register('almostAlways', function (func, pat) {
return pat.sometimesBy(0.9, func);
});

/**
*
Expand All @@ -488,9 +456,9 @@ Pattern.prototype.almostAlways = function (func) {
* @example
* s("hh*8").never(x=>x.speed("0.5"))
*/
Pattern.prototype.never = function (func) {
return this;
};
export const never = register('never', function (_, pat) {
return pat;
});

/**
*
Expand All @@ -502,8 +470,6 @@ Pattern.prototype.never = function (func) {
* @example
* s("hh*8").always(x=>x.speed("0.5"))
*/
Pattern.prototype.always = function (func) {
return func(this);
};

Pattern.prototype.patternified.push('degradeBy', 'undegradeBy');
export const always = register('always', function (func, pat) {
return func(pat);
});