Skip to content

Commit

Permalink
Merge pull request #83 from tidalcycles/pattern-composers
Browse files Browse the repository at this point in the history
Add pattern composers, implements #82
  • Loading branch information
yaxu committed Apr 22, 2022
2 parents 87cfa30 + 8e441ec commit 8007ea6
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 29 deletions.
6 changes: 3 additions & 3 deletions packages/core/controls.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,14 @@ const generic_params = [

const _name = (name, ...pats) => sequence(...pats).withValue((x) => ({ [name]: x }));

const _unionise = (func) =>
const _setter = (func) =>
function (...pats) {
return this.union(func(...pats));
return this.set(func(...pats));
};

generic_params.forEach(([type, name, description]) => {
controls[name] = (...pats) => _name(name, ...pats);
Pattern.prototype[name] = _unionise(controls[name]);
Pattern.prototype[name] = _setter(controls[name]);
});

export default controls;
84 changes: 61 additions & 23 deletions packages/core/pattern.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,24 @@ export class Pattern {
);
}

_opleft(other, func) {
_opLeft(other, func) {
return this.fmap(func).appLeft(reify(other));
}
_opRight(other, func) {
return this.fmap(func).appRight(reify(other));
}
_opBoth(other, func) {
return this.fmap(func).appBoth(reify(other));
}
_opSqueeze(other, func) {
const otherPat = reify(other);
return this.fmap((a) => otherPat.fmap((b) => func(a)(b)))._squeezeJoin();
}
_opSqueezeFlip(other, func) {
const thisPat = this;
const otherPat = reify(other);
return otherPat.fmap((a) => thisPat.fmap((b) => func(b)(a)))._squeezeJoin();
}

_asNumber(silent = false) {
return this._withEvent((event) => {
Expand All @@ -275,22 +290,6 @@ export class Pattern {
})._removeUndefineds();
}

add(other) {
return this._asNumber()._opleft(other, (a) => (b) => a + b);
}

sub(other) {
return this._asNumber()._opleft(other, (a) => (b) => a - b);
}

mul(other) {
return this._asNumber()._opleft(other, (a) => (b) => a * b);
}

div(other) {
return this._asNumber()._opleft(other, (a) => (b) => a / b);
}

round() {
return this._asNumber().fmap((v) => Math.round(v));
}
Expand Down Expand Up @@ -324,10 +323,6 @@ export class Pattern {
return this._fromBipolar().range(min, max);
}

union(other) {
return this._opleft(other, (a) => (b) => Object.assign({}, a, b));
}

_bindWhole(choose_whole, func) {
const pat_val = this;
const query = function (state) {
Expand Down Expand Up @@ -504,7 +499,7 @@ export class Pattern {
const slices = Array.from({ length: n }, (x, i) => i);
const slice_objects = slices.map((i) => ({ begin: i / n, end: (i + 1) / n }));
const slicePat = slowcat(...slice_objects);
return this.union(slicePat)._fast(n);
return this.set(slicePat)._fast(n);
}

// cpm = cycles per minute
Expand Down Expand Up @@ -728,6 +723,49 @@ export class Pattern {
}
}

// pattern composers
const composers = {
set: [
(a) => (b) => {
// If an object is involved, do a union, discarding matching keys from a.
// Otherwise, just return b.
if (a instanceof Object || b instanceof Object) {
if (!a instanceof Object) {
a = { value: a };
}
if (!b instanceof Object) {
b = { value: b };
}
return Object.assign({}, a, b);
}
return b;
},
id,
],
add: [(a) => (b) => a + b, (x) => x._asNumber()],
sub: [(a) => (b) => a - b, (x) => x._asNumber()],
mul: [(a) => (b) => a * b, (x) => x._asNumber()],
div: [(a) => (b) => a / b, (x) => x._asNumber()],
};

for (const [name, op] of Object.entries(composers)) {
Pattern.prototype[name] = function (...other) {
return op[1](this)._opLeft(sequence(other), op[0]);
};
Pattern.prototype[name + 'Flip'] = function (...other) {
return op[1](this)._opRight(sequence(other), op[0]);
};
Pattern.prototype[name + 'Sect'] = function (...other) {
return op[1](this)._opBoth(sequence(other), op[0]);
};
Pattern.prototype[name + 'Squeeze'] = function (...other) {
return op[1](this)._opSqueeze(sequence(other), op[0]);
};
Pattern.prototype[name + 'SqueezeFlip'] = function (...other) {
return op[1](this)._opSqueezeFlip(sequence(other), op[0]);
};
}

// methods of Pattern that get callable factories
Pattern.prototype.patternified = [
'apply',
Expand Down Expand Up @@ -930,7 +968,7 @@ 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 union = curry((a, pat) => pat.union(a));
export const set = curry((a, pat) => pat.set(a));
export const when = curry((binary, f, pat) => pat.when(binary, f));

// problem: curried functions with spread arguments must have pat at the beginning
Expand Down
81 changes: 78 additions & 3 deletions packages/core/test/pattern.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,10 @@ const hap = (whole, part, value, context = {}) => new Hap(whole, part, value, co
const third = Fraction(1, 3);
const twothirds = Fraction(2, 3);

const sameFirst = (a, b) => {
return assert.deepStrictEqual(a._sortEventsByPart().firstCycle(), b._sortEventsByPart().firstCycle());
};

describe('TimeSpan', function () {
describe('equals()', function () {
it('Should be equal to the same value', function () {
Expand Down Expand Up @@ -146,6 +150,33 @@ describe('Pattern', function () {
assert.equal(pure(3).add(pure(4)).query(st(0, 1))[0].value, 7);
});
});
describe('addFlip()', () => {
it('Can add things with structure from second pattern', () => {
sameFirst(sequence(1, 2).addFlip(4), sequence(5, 6).struct(true));
});
});
describe('addSqueeze()', () => {
it('Can add while squeezing the second pattern inside the events of the first', () => {
sameFirst(
sequence(1, [2, 3]).addSqueeze(sequence(10, 20, 30)),
sequence(
[11, 21, 31],
[
[12, 22, 32],
[13, 23, 33],
],
),
);
});
});
describe('addSqueezeFlip()', () => {
it('Can add while squeezing the first pattern inside the events of the second', () => {
sameFirst(
sequence(1, [2, 3]).addSqueezeFlip(10, 20, 30),
sequence([11, [12, 13]], [21, [22, 23]], [31, [32, 33]]),
);
});
});
describe('sub()', function () {
it('Can subtract things', function () {
assert.equal(pure(3).sub(pure(4)).query(st(0, 1))[0].value, -1);
Expand All @@ -161,14 +192,50 @@ describe('Pattern', function () {
assert.equal(pure(3).div(pure(2)).firstCycle()[0].value, 1.5);
});
});
describe('union()', function () {
it('Can union things', function () {
describe('set()', function () {
it('Can set things in objects', function () {
assert.deepStrictEqual(
pure({ a: 4, b: 6 })
.union(pure({ c: 7 }))
.set(pure({ c: 7 }))
.firstCycle()[0].value,
{ a: 4, b: 6, c: 7 },
);
sameFirst(
sequence({ a: 1, b: 2 }, { a: 2, b: 2 }, { a: 3, b: 2 }).set({ a: 4, c: 5 }),
sequence({ a: 4, b: 2, c: 5 }).fast(3),
);
});
it('Can set things with plain values', function () {
sameFirst(sequence(1, 2, 3).set(4), sequence(4).fast(3));
});
describe('setFlip()', () => {
it('Can set things with structure from second pattern', () => {
sameFirst(sequence(1, 2).setFlip(4), pure(4).mask(true, true));
});
});
describe('setSqueeze()', () => {
it('Can squeeze one pattern inside the events of another', () => {
sameFirst(
sequence(1, [2, 3]).setSqueeze(sequence('a', 'b', 'c')),
sequence(
['a', 'b', 'c'],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
],
),
);
sameFirst(
sequence(1, [2, 3]).setSqueeze('a', 'b', 'c'),
sequence(
['a', 'b', 'c'],
[
['a', 'b', 'c'],
['a', 'b', 'c'],
],
),
);
});
});
});
describe('stack()', function () {
Expand Down Expand Up @@ -642,6 +709,14 @@ describe('Pattern', function () {
assert.equal(sequence(1, 2, 3).ply(2).early(8).firstCycle().length, 6);
});
});
describe('striate', () => {
it('Can striate(2)', () => {
sameFirst(
sequence({ sound: 'a' }).striate(2),
sequence({ sound: 'a', begin: 0, end: 0.5 }, { sound: 'a', begin: 0.5, end: 1 }),
);
});
});
describe('chop', () => {
it('Can _chop(2)', () => {
assert.deepStrictEqual(
Expand Down

0 comments on commit 8007ea6

Please sign in to comment.