diff --git a/packages/core/controls.mjs b/packages/core/controls.mjs index 8dfeb5b6..4d23daf5 100644 --- a/packages/core/controls.mjs +++ b/packages/core/controls.mjs @@ -400,7 +400,7 @@ const generic_params = [ * @example * freq("220 110 440 110").s("superzow").osc() * @example - * freq("110".mulOut(".5 1.5 .6 [2 3]")).s("superzow").osc() + * freq("110".mul.out(".5 1.5 .6 [2 3]")).s("superzow").osc() * */ ['f', 'freq', ''], diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index c5f7a6be..254e1d7f 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -10,7 +10,7 @@ import Hap from './hap.mjs'; import State from './state.mjs'; import { unionWithObj } from './value.mjs'; -import { compose, removeUndefineds, flatten, id, listRange, curry, mod, numeralArgs, parseNumeral } from './util.mjs'; +import { compose, removeUndefineds, flatten, id, listRange, curry, _mod, numeralArgs, parseNumeral } from './util.mjs'; import drawLine from './drawLine.mjs'; import { logger } from './logger.mjs'; @@ -1473,7 +1473,7 @@ function _composeOp(a, b, func) { * @memberof Pattern */ div: [numeralArgs((a, b) => a / b)], - mod: [numeralArgs(mod)], + mod: [numeralArgs(_mod)], pow: [numeralArgs(Math.pow)], _and: [numeralArgs((a, b) => a & b)], _or: [numeralArgs((a, b) => a | b)], @@ -1501,52 +1501,45 @@ function _composeOp(a, b, func) { // generate methods to do what and how for (const [what, [op, preprocess]] of Object.entries(composers)) { - for (const how of hows) { - Pattern.prototype[what + how] = function (...other) { - var pat = this; - other = sequence(other); - if (preprocess) { - pat = preprocess(pat); - other = preprocess(other); - } - var result; - // hack to remove undefs when doing 'keepif' - if (what === 'keepif') { - // avoid union, as we want to throw away the value of 'b' completely - result = pat['_op' + how](other, (a) => (b) => op(a, b)); - result = result.removeUndefineds(); - } else { - result = pat['_op' + how](other, (a) => (b) => _composeOp(a, b, op)); - } - return result; - }; - if (how === 'Squeeze') { - // support 'squeezeIn' longhand - Pattern.prototype[what + 'SqueezeIn'] = Pattern.prototype[what + how]; - } - if (how === 'In') { - // set 'in' to default, but with magic properties to pick a different 'how' - Object.defineProperty(Pattern.prototype, what, { - // a getter that returns a function, so 'pat' can be - // accessed by closures that are methods of that function.. - get: function () { - const pat = this; - // wrap the 'in' function as default behaviour - const wrapper = (...other) => pat[what + 'In'](...other); - // add methods to that function to pick alternative behaviours - for (const wraphow of hows) { - wrapper[wraphow.toLowerCase()] = (...other) => pat[what + wraphow](...other); - } + Object.defineProperty(Pattern.prototype, what, { + // a getter that returns a function, so 'pat' can be + // accessed by closures that are methods of that function.. + get: function() { + const pat = this; - return wrapper; - }, - }); - } else { - // default what to 'set', e.g. squeeze = setSqueeze - if (what === 'set') { - Pattern.prototype[how.toLowerCase()] = Pattern.prototype[what + how]; + // wrap the 'in' function as default behaviour + const wrapper = (...other) => pat[what]['in'](...other); + + // add methods to that function for each behaviour + for (const how of hows) { + wrapper[how.toLowerCase()] = function (...other) { + var howpat = pat; + other = sequence(other); + if (preprocess) { + howpat = preprocess(howpat); + other = preprocess(other); + } + var result; + // hack to remove undefs when doing 'keepif' + if (what === 'keepif') { + // avoid union, as we want to throw away the value of 'b' completely + result = howpat['_op' + how](other, (a) => (b) => op(a, b)); + result = result.removeUndefineds(); + } else { + result = howpat['_op' + how](other, (a) => (b) => _composeOp(a, b, op)); + } + return result; + } } + wrapper.squeezein = wrapper.squeeze + + return wrapper; } + }); + + // Default op to 'set', e.g. pat.squeeze(pat2) = pat.set.squeeze(pat2) + for (const how of hows) { + Pattern.prototype[how.toLowerCase()] = function (...args) { return this.set[how.toLowerCase()](args) }; } } @@ -1562,14 +1555,14 @@ function _composeOp(a, b, func) { * .struct("x ~ x ~ ~ x ~ x ~ ~ ~ x ~ x ~ ~") * .slow(4) */ - Pattern.prototype.struct = Pattern.prototype.keepifOut; - Pattern.prototype.structAll = Pattern.prototype.keepOut; - Pattern.prototype.mask = Pattern.prototype.keepifIn; - Pattern.prototype.maskAll = Pattern.prototype.keepIn; - Pattern.prototype.reset = Pattern.prototype.keepifTrig; - Pattern.prototype.resetAll = Pattern.prototype.keepTrig; - Pattern.prototype.restart = Pattern.prototype.keepifTrigzero; - Pattern.prototype.restartAll = Pattern.prototype.keepTrigzero; + Pattern.prototype.struct = function(...args) { return this.keepif.out(...args) } + Pattern.prototype.structAll = function(...args) { return this.keep.out(...args) } + Pattern.prototype.mask = function(...args) { return this.keepif.in(...args) } + Pattern.prototype.maskAll = function(...args) { return this.keep.in(...args) } + Pattern.prototype.reset = function(...args) { return this.keepif.trig(...args) } + Pattern.prototype.resetAll = function(...args) { return this.keep.trig(...args) } + Pattern.prototype.restart = function(...args) { return this.keepif.trigzero(...args) } + Pattern.prototype.restartAll = function(...args) { return this.keep.trigzero(...args) } })(); // methods of Pattern that get callable factories @@ -1688,7 +1681,7 @@ export function slowcat(...pats) { const query = function (state) { const span = state.span; - const pat_n = mod(span.begin.sam(), pats.length); + const pat_n = _mod(span.begin.sam(), pats.length); const pat = pats[pat_n]; if (!pat) { // pat_n can be negative, if the span is in the past.. @@ -1819,11 +1812,9 @@ export function pm(...args) { polymeter(...args); } -export const add = curry((a, pat) => pat.add(a)); export const chop = curry((a, pat) => pat.chop(a)); export const chunk = curry((a, pat) => pat.chunk(a)); export const chunkBack = curry((a, pat) => pat.chunkBack(a)); -export const div = curry((a, pat) => pat.div(a)); export const early = curry((a, pat) => pat.early(a)); export const echo = curry((a, b, c, pat) => pat.echo(a, b, c)); export const every = curry((i, f, pat) => pat.every(i, f)); @@ -1837,7 +1828,6 @@ export const juxBy = curry((by, f, pat) => pat.juxBy(by, f)); export const late = curry((a, pat) => pat.late(a)); export const linger = curry((a, pat) => pat.linger(a)); export const mask = curry((a, pat) => pat.mask(a)); -export const mul = curry((a, pat) => pat.mul(a)); export const off = curry((t, f, pat) => pat.off(t, f)); export const ply = curry((a, pat) => pat.ply(a)); export const range = curry((a, b, pat) => pat.range(a, b)); @@ -1846,11 +1836,36 @@ export const range2 = curry((a, b, pat) => pat.range2(a, b)); export const rev = (pat) => pat.rev(); export const slow = curry((a, pat) => pat.slow(a)); export const struct = curry((a, pat) => pat.struct(a)); -export const sub = curry((a, pat) => pat.sub(a)); export const superimpose = curry((array, pat) => pat.superimpose(...array)); -export const set = curry((a, pat) => pat.set(a)); export const when = curry((binary, f, pat) => pat.when(binary, f)); +// operators +export const set = curry((a, pat) => pat.set(a)); +export const keep = curry((a, pat) => pat.keep(a)); +export const keepif = curry((a, pat) => pat.keepif(a)); +export const add = curry((a, pat) => pat.add(a)); +export const sub = curry((a, pat) => pat.sub(a)); +export const mul = curry((a, pat) => pat.mul(a)); +export const div = curry((a, pat) => pat.div(a)); +//export const mod = curry((a, pat) => pat.mod(a)); +export const pow = curry((a, pat) => pat.pow(a)); +export const _and = curry((a, pat) => pat._and(a)); +export const _or = curry((a, pat) => pat._or(a)); +export const _xor = curry((a, pat) => pat._xor(a)); +export const _lshift = curry((a, pat) => pat._lshift(a)); +export const _rshift = curry((a, pat) => pat._rshift(a)); +export const lt = curry((a, pat) => pat.lt(a)); +export const gt = curry((a, pat) => pat.gt(a)); +export const lte = curry((a, pat) => pat.lte(a)); +export const gte = curry((a, pat) => pat.gte(a)); +export const eq = curry((a, pat) => pat.eq(a)); +export const eqt = curry((a, pat) => pat.eqt(a)); +export const ne = curry((a, pat) => pat.ne(a)); +export const net = curry((a, pat) => pat.net(a)); +export const and = curry((a, pat) => pat.and(a)); +export const or = curry((a, pat) => pat.or(a)); +export const func = curry((a, pat) => pat.func(a)); + // 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 diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 6571080e..ba5a3aa3 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -156,32 +156,32 @@ describe('Pattern', () => { describe('add()', () => { it('can structure In()', () => { expect(pure(3).add(pure(4)).query(st(0, 1))[0].value).toBe(7); - expect(pure(3).addIn(pure(4)).query(st(0, 1))[0].value).toBe(7); + expect(pure(3).add.in(pure(4)).query(st(0, 1))[0].value).toBe(7); }); it('can structure Out()', () => { - sameFirst(sequence(1, 2).addOut(4), sequence(5, 6).struct(true)); + sameFirst(sequence(1, 2).add.out(4), sequence(5, 6).struct(true)); }); it('can Mix() structure', () => { - expect(sequence(1, 2).addMix(silence, 5, silence).firstCycle()).toStrictEqual([ + expect(sequence(1, 2).add.mix(silence, 5, silence).firstCycle()).toStrictEqual([ new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 6), new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 7), ]); }); it('can Trig() structure', () => { sameFirst( - slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).addTrig(20, 30).early(2), + slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).add.trig(20, 30).early(2), sequence(26, 27, 36, 37), ); }); it('can Trigzero() structure', () => { sameFirst( - slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).addTrigzero(20, 30).early(2), + slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).add.trigzero(20, 30).early(2), sequence(21, 22, 31, 32), ); }); it('can Squeeze() structure', () => { sameFirst( - sequence(1, [2, 3]).addSqueeze(sequence(10, 20, 30)), + sequence(1, [2, 3]).add.squeeze(sequence(10, 20, 30)), sequence( [11, 21, 31], [ @@ -193,7 +193,7 @@ describe('Pattern', () => { }); it('can SqueezeOut() structure', () => { sameFirst( - sequence(1, [2, 3]).addSqueezeOut(10, 20, 30), + sequence(1, [2, 3]).add.squeezeout(10, 20, 30), sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]), ); }); @@ -204,32 +204,32 @@ describe('Pattern', () => { describe('keep()', () => { it('can structure In()', () => { expect(pure(3).keep(pure(4)).query(st(0, 1))[0].value).toBe(3); - expect(pure(3).keepIn(pure(4)).query(st(0, 1))[0].value).toBe(3); + expect(pure(3).keep.in(pure(4)).query(st(0, 1))[0].value).toBe(3); }); it('can structure Out()', () => { - sameFirst(sequence(1, 2).keepOut(4), sequence(1, 2).struct(true)); + sameFirst(sequence(1, 2).keep.out(4), sequence(1, 2).struct(true)); }); it('can Mix() structure', () => { - expect(sequence(1, 2).keepMix(silence, 5, silence).firstCycle()).toStrictEqual([ + expect(sequence(1, 2).keep.mix(silence, 5, silence).firstCycle()).toStrictEqual([ new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1), new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2), ]); }); it('can Trig() structure', () => { sameFirst( - slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepTrig(20, 30).early(2), + slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keep.trig(20, 30).early(2), sequence(6, 7, 6, 7), ); }); it('can Trigzero() structure', () => { sameFirst( - slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepTrigzero(20, 30).early(2), + slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keep.trigzero(20, 30).early(2), sequence(1, 2, 1, 2), ); }); it('can Squeeze() structure', () => { sameFirst( - sequence(1, [2, 3]).keepSqueeze(sequence(10, 20, 30)), + sequence(1, [2, 3]).keep.squeeze(sequence(10, 20, 30)), sequence( [1, 1, 1], [ @@ -240,38 +240,38 @@ describe('Pattern', () => { ); }); it('can SqueezeOut() structure', () => { - sameFirst(sequence(1, [2, 3]).keepSqueezeOut(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]])); + sameFirst(sequence(1, [2, 3]).keep.squeezeout(10, 20, 30), sequence([1, [2, 3]], [1, [2, 3]], [1, [2, 3]])); }); }); describe('keepif()', () => { it('can structure In()', () => { sameFirst(sequence(3, 4).keepif(true, false), sequence(3, silence)); - sameFirst(sequence(3, 4).keepifIn(true, false), sequence(3, silence)); + sameFirst(sequence(3, 4).keepif.in(true, false), sequence(3, silence)); }); it('can structure Out()', () => { - sameFirst(pure(1).keepifOut(true, false), sequence(1, silence)); + sameFirst(pure(1).keepif.out(true, false), sequence(1, silence)); }); it('can Mix() structure', () => { - expect(sequence(1, 2).keepifMix(false, true, false).firstCycle()).toStrictEqual([ + expect(sequence(1, 2).keepif.mix(false, true, false).firstCycle()).toStrictEqual([ new Hap(ts(1 / 3, 1 / 2), ts(1 / 3, 1 / 2), 1), new Hap(ts(1 / 2, 2 / 3), ts(1 / 2, 2 / 3), 2), ]); }); it('can Trig() structure', () => { sameFirst( - slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepifTrig(false, true).early(2), + slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepif.trig(false, true).early(2), sequence(silence, silence, 6, 7), ); }); it('can Trigzero() structure', () => { sameFirst( - slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepifTrigzero(false, true).early(2), + slowcat(sequence(1, 2, 3, 4), 5, sequence(6, 7, 8, 9), 10).keepif.trigzero(false, true).early(2), sequence(silence, silence, 1, 2), ); }); it('can Squeeze() structure', () => { sameFirst( - sequence(1, [2, 3]).keepifSqueeze(sequence(true, true, false)), + sequence(1, [2, 3]).keepif.squeeze(sequence(true, true, false)), sequence( [1, 1, silence], [ @@ -282,7 +282,7 @@ describe('Pattern', () => { ); }); it('can SqueezeOut() structure', () => { - sameFirst(sequence(1, [2, 3]).keepifSqueezeOut(true, true, false), sequence([1, [2, 3]], [1, [2, 3]], silence)); + sameFirst(sequence(1, [2, 3]).keepif.squeezeout(true, true, false), sequence([1, [2, 3]], [1, [2, 3]], silence)); }); }); describe('sub()', () => { @@ -322,13 +322,13 @@ describe('Pattern', () => { }); describe('setOut()', () => { it('Can set things with structure from second pattern', () => { - sameFirst(sequence(1, 2).setOut(4), pure(4).mask(true, true)); + sameFirst(sequence(1, 2).set.out(4), pure(4).mask(true, true)); }); }); describe('setSqueeze()', () => { it('Can squeeze one pattern inside the haps of another', () => { sameFirst( - sequence(1, [2, 3]).setSqueeze(sequence('a', 'b', 'c')), + sequence(1, [2, 3]).set.squeeze(sequence('a', 'b', 'c')), sequence( ['a', 'b', 'c'], [ @@ -338,7 +338,7 @@ describe('Pattern', () => { ), ); sameFirst( - sequence(1, [2, 3]).setSqueeze('a', 'b', 'c'), + sequence(1, [2, 3]).set.squeeze('a', 'b', 'c'), sequence( ['a', 'b', 'c'], [ diff --git a/packages/core/test/util.test.mjs b/packages/core/test/util.test.mjs index 303a3645..17dceb81 100644 --- a/packages/core/test/util.test.mjs +++ b/packages/core/test/util.test.mjs @@ -10,7 +10,7 @@ import { tokenizeNote, toMidi, fromMidi, - mod, + _mod, compose, getFrequency, getPlayableNoteValue, @@ -118,22 +118,22 @@ describe('getFrequency', () => { }); }); -describe('mod', () => { +describe('_mod', () => { it('should work like regular modulo with positive numbers', () => { - expect(mod(0, 3)).toEqual(0); - expect(mod(1, 3)).toEqual(1); - expect(mod(2, 3)).toEqual(2); - expect(mod(3, 3)).toEqual(0); - expect(mod(4, 3)).toEqual(1); - expect(mod(4, 2)).toEqual(0); + expect(_mod(0, 3)).toEqual(0); + expect(_mod(1, 3)).toEqual(1); + expect(_mod(2, 3)).toEqual(2); + expect(_mod(3, 3)).toEqual(0); + expect(_mod(4, 3)).toEqual(1); + expect(_mod(4, 2)).toEqual(0); }); it('should work with negative numbers', () => { - expect(mod(-1, 3)).toEqual(2); - expect(mod(-2, 3)).toEqual(1); - expect(mod(-3, 3)).toEqual(0); - expect(mod(-4, 3)).toEqual(2); - expect(mod(-5, 3)).toEqual(1); - expect(mod(-3, 2)).toEqual(1); + expect(_mod(-1, 3)).toEqual(2); + expect(_mod(-2, 3)).toEqual(1); + expect(_mod(-3, 3)).toEqual(0); + expect(_mod(-4, 3)).toEqual(2); + expect(_mod(-5, 3)).toEqual(1); + expect(_mod(-3, 2)).toEqual(1); }); }); diff --git a/packages/core/util.mjs b/packages/core/util.mjs index f6b7d8b8..12f2276b 100644 --- a/packages/core/util.mjs +++ b/packages/core/util.mjs @@ -50,9 +50,8 @@ export const midi2note = (n) => { return pc + oct; }; -// 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 const mod = (n, m) => ((n % m) + m) % m; +// modulo that works with negative numbers e.g. _mod(-1, 3) = 2. Works on numbers (rather than patterns of numbers, as @mod@ from pattern.mjs does) +export const _mod = (n, m) => ((n % m) + m) % m; export const getPlayableNoteValue = (hap) => { let { value, context } = hap; diff --git a/packages/tonal/tonal.mjs b/packages/tonal/tonal.mjs index 4d8e0d82..a222b64b 100644 --- a/packages/tonal/tonal.mjs +++ b/packages/tonal/tonal.mjs @@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th */ import { Note, Interval, Scale } from '@tonaljs/tonal'; -import { Pattern, mod } from '@strudel.cycles/core'; +import { Pattern, _mod } from '@strudel.cycles/core'; // transpose note inside scale by offset steps // function scaleOffset(scale: string, offset: number, note: string) { @@ -29,7 +29,7 @@ function scaleOffset(scale, offset, note) { // TODO: find way to do this smarter while (Math.abs(i - noteIndex) < Math.abs(offset)) { i += direction; - const index = mod(i, notes.length); + const index = _mod(i, notes.length); if (direction < 0 && n[0] === 'C') { o += direction; }