breaking: refactor controls

- multiple args wont be interpreted as sequence anymore
- you can now pass value, pat to set value to pat
This commit is contained in:
Felix Roos
2025-03-16 10:48:38 +01:00
parent e782dc09dd
commit 11196fb1e6
6 changed files with 40 additions and 38 deletions
+18 -8
View File
@@ -4,13 +4,14 @@ 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 { Pattern, register, sequence } from './pattern.mjs';
import { Pattern, register, reify } from './pattern.mjs';
export function createParam(names) {
let isMulti = Array.isArray(names);
names = !isMulti ? [names] : names;
const name = names[0];
// todo: make this less confusing
const withVal = (xs) => {
let bag;
// check if we have an object with an unnamed control (.value)
@@ -35,25 +36,34 @@ export function createParam(names) {
}
};
const func = (...pats) => sequence(...pats).withValue(withVal);
const setter = function (...pats) {
if (!pats.length) {
return this.fmap(withVal);
// todo: make this less confusing
const func = function (value, pat) {
if (!pat) {
return reify(value).withValue(withVal);
}
return this.set(func(...pats));
if (typeof value === 'undefined') {
return pat.fmap(withVal);
}
return pat.set(reify(value).withValue(withVal));
};
Pattern.prototype[name] = function (value) {
return func(value, this);
};
Pattern.prototype[name] = setter;
return func;
}
// maps control alias names to the "main" control name
const controlAlias = new Map();
export function isControlName(name) {
return controlAlias.has(name);
}
export function registerControl(names, ...aliases) {
const name = Array.isArray(names) ? names[0] : names;
let bag = {};
bag[name] = createParam(names);
controlAlias.set(name, name);
aliases.forEach((alias) => {
bag[alias] = bag[name];
controlAlias.set(alias, name);
+4 -4
View File
@@ -279,26 +279,26 @@ const _rearrangeWith = (ipat, n, pat) => {
};
/**
* @name shuffle
* Slices a pattern into the given number of parts, then plays those parts in random order.
* Each part will be played exactly once per cycle.
* @name shuffle
* @example
* note("c d e f").sound("piano").shuffle(4)
* @example
* note("c d e f".shuffle(4), "g").sound("piano")
* seq("c d e f".shuffle(4), "g").note().sound("piano")
*/
export const shuffle = register('shuffle', (n, pat) => {
return _rearrangeWith(randrun(n), n, pat);
});
/**
* @name scramble
* Slices a pattern into the given number of parts, then plays those parts at random. Similar to `shuffle`,
* but parts might be played more than once, or not at all, per cycle.
* @name scramble
* @example
* note("c d e f").sound("piano").scramble(4)
* @example
* note("c d e f".scramble(4), "g").sound("piano")
* seq("c d e f".scramble(4), "g").note().sound("piano")
*/
export const scramble = register('scramble', (n, pat) => {
return _rearrangeWith(_irand(n)._segment(n), n, pat);
+1 -1
View File
@@ -1001,7 +1001,7 @@ describe('Pattern', () => {
});
describe('hurry', () => {
it('Can speed up patterns and sounds', () => {
sameFirst(s('a', 'b').hurry(2), s('a', 'b').fast(2).speed(2));
sameFirst(s(sequence('a', 'b')).hurry(2), s(sequence('a', 'b')).fast(2).speed(2));
});
});
/*describe('composable functions', () => {
+7 -7
View File
@@ -25,28 +25,28 @@ describe('tonal', () => {
});
it('scale with n values', () => {
expect(
n(0, 1, 2)
n(seq(0, 1, 2))
.scale('C major')
.firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']);
});
it('scale with colon', () => {
expect(
n(0, 1, 2)
n(seq(0, 1, 2))
.scale('C:major')
.firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']);
});
it('scale with mininotation colon', () => {
expect(
n(0, 1, 2)
n(seq(0, 1, 2))
.scale(mini('C:major'))
.firstCycleValues.map((h) => h.note),
).toEqual(['C3', 'D3', 'E3']);
});
it('transposes note numbers with interval numbers', () => {
expect(
note(40, 40, 40)
note(seq(40, 40, 40))
.transpose(0, 1, 2)
.firstCycleValues.map((h) => h.note),
).toEqual([40, 41, 42]);
@@ -54,7 +54,7 @@ describe('tonal', () => {
});
it('transposes note numbers with interval strings', () => {
expect(
note(40, 40, 40)
note(seq(40, 40, 40))
.transpose('1P', '2M', '3m')
.firstCycleValues.map((h) => h.note),
).toEqual([40, 42, 43]);
@@ -62,7 +62,7 @@ describe('tonal', () => {
});
it('transposes note strings with interval numbers', () => {
expect(
note('c', 'c', 'c')
note(seq('c', 'c', 'c'))
.transpose(0, 1, 2)
.firstCycleValues.map((h) => h.note),
).toEqual(['C', 'Db', 'D']);
@@ -70,7 +70,7 @@ describe('tonal', () => {
});
it('transposes note strings with interval strings', () => {
expect(
note('c', 'c', 'c')
note(seq('c', 'c', 'c'))
.transpose('1P', '2M', '3m')
.firstCycleValues.map((h) => h.note),
).toEqual(['C', 'D', 'Eb']);
+4 -12
View File
@@ -7401,9 +7401,7 @@ exports[`runs examples > example "scope" example index 0 1`] = `
]
`;
exports[`runs examples > example "scramble
Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`,
but parts might be played more than once, or not at all, per cycle." example index 0 1`] = `
exports[`runs examples > example "scramble" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c s:piano ]",
"[ 1/4 → 1/2 | note:d s:piano ]",
@@ -7424,9 +7422,7 @@ but parts might be played more than once, or not at all, per cycle." example ind
]
`;
exports[`runs examples > example "scramble
Slices a pattern into the given number of parts, then plays those parts at random. Similar to \`shuffle\`,
but parts might be played more than once, or not at all, per cycle." example index 1 1`] = `
exports[`runs examples > example "scramble" example index 1 1`] = `
[
"[ 0/1 → 1/8 | note:c s:piano ]",
"[ 1/8 → 1/4 | note:d s:piano ]",
@@ -7835,9 +7831,7 @@ exports[`runs examples > example "shrink" example index 3 1`] = `
]
`;
exports[`runs examples > example "shuffle
Slices a pattern into the given number of parts, then plays those parts in random order.
Each part will be played exactly once per cycle." example index 0 1`] = `
exports[`runs examples > example "shuffle" example index 0 1`] = `
[
"[ 0/1 → 1/4 | note:c s:piano ]",
"[ 1/4 → 1/2 | note:d s:piano ]",
@@ -7858,9 +7852,7 @@ Each part will be played exactly once per cycle." example index 0 1`] = `
]
`;
exports[`runs examples > example "shuffle
Slices a pattern into the given number of parts, then plays those parts in random order.
Each part will be played exactly once per cycle." example index 1 1`] = `
exports[`runs examples > example "shuffle" example index 1 1`] = `
[
"[ 0/1 → 1/8 | note:c s:piano ]",
"[ 1/8 → 1/4 | note:d s:piano ]",
+6 -6
View File
@@ -69,32 +69,32 @@ stack(
export const giantSteps = `// John Coltrane - Giant Steps
let melody = note(
let melody = seq(
"[F#5 D5] [B4 G4] Bb4 [B4 A4]",
"[D5 Bb4] [G4 Eb4] F#4 [G4 F4]",
"Bb4 [B4 A4] D5 [D#5 C#5]",
"F#5 [G5 F5] Bb5 [F#5 F#5]",
)
).note()
stack(
// melody
melody.color('#F8E71C'),
// chords
chord(
seq(
"[B^7 D7] [G^7 Bb7] Eb^7 [Am7 D7]",
"[G^7 Bb7] [Eb^7 F#7] B^7 [Fm7 Bb7]",
"Eb^7 [Am7 D7] G^7 [C#m7 F#7]",
"B^7 [Fm7 Bb7] Eb^7 [C#m7 F#7]"
).dict('lefthand')
).chord().dict('lefthand')
.anchor(melody).mode('duck')
.voicing().color('#7ED321'),
// bass
note(
seq(
"[B2 D2] [G2 Bb2] [Eb2 Bb3] [A2 D2]",
"[G2 Bb2] [Eb2 F#2] [B2 F#2] [F2 Bb2]",
"[Eb2 Bb2] [A2 D2] [G2 D2] [C#2 F#2]",
"[B2 F#2] [F2 Bb2] [Eb2 Bb3] [C#2 F#2]"
).color('#00B8D4')
).note().color('#00B8D4')
).slow(20)
.pianoroll({fold:1})`;