mirror of
https://codeberg.org/uzu/strudel
synced 2026-10-12 02:59:37 -04:00
Merge pull request 'Add cyclestruct function' (#2107) from tzwaan/cyclestruct into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/2107
This commit is contained in:
@@ -2567,6 +2567,74 @@ export const bite = register(
|
||||
false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Similar to `struct`, but every hap in the structure pattern plays the next
|
||||
* cycle of the main pattern.
|
||||
* With a different number of cycles in the main pattern and haps in the
|
||||
* structure pattern, this creates an isorhythm.
|
||||
*
|
||||
* Note: This function works on the assumption that the structure pattern
|
||||
* repeats every cycle. If the structure pattern only repeats after multiple
|
||||
* cycles, it may skip or repeat certain parts of the melody.
|
||||
* @tags temporal
|
||||
* @name isobite
|
||||
* @synonyms cycleStruct, cyclestruct
|
||||
* @memberof Pattern
|
||||
* @param {Pattern} structure structure of the pattern
|
||||
* @example
|
||||
* "<0 1 2 3 4 5>".isobite("[x - x*2]*2").n().scale("c:major")
|
||||
* // the same as "0 - [1 2] 3 - [4 5]".n().scale("c:major")
|
||||
*
|
||||
* @example
|
||||
* "<0 1 2 3 4>".isobite("x - x").n().scale("c:major")
|
||||
* // the same as "<[0 - 1] [2 - 3] [4 - 0] [1 - 2] [3 - 4]>".n().scale("c:major")
|
||||
*
|
||||
* @example
|
||||
* // Here the structure pattern repeats after 2 cycles,
|
||||
* // so certain haps from the melody are skipped.
|
||||
* "<0 1 2 3>".isobite("x x*2".slow(2)).n().scale("c:major")
|
||||
* // the same as "<[0] [2 3] [2] [2 3]>".n().scale("c:major")
|
||||
*
|
||||
* @example
|
||||
* // This can be avoided by making the struct pattern 1 cycle,
|
||||
* // and slowing the whole pattern down as follows:
|
||||
* "<0 1 2 3>".isobite("x x*2").slow(2).n().scale("c:major")
|
||||
* // the same as "<[0] [1 2] [3] [0 1] [2] [3 0] [1] [2 3]>".n().scale("c:major")
|
||||
*/
|
||||
export const { isobite, cyclestruct, cycleStruct } = register(
|
||||
['isobite', 'cyclestruct', 'cycleStruct'],
|
||||
(structure, pat) => {
|
||||
const structPat = new Pattern((state) => {
|
||||
const haps_cycle = structure
|
||||
.withQuerySpan((span) => span.begin.wholeCycle())
|
||||
.query(state)
|
||||
.filter((hap) => hap.value != 0)
|
||||
.sort((a, b) => a.whole.begin - b.whole.begin)
|
||||
.map((hap, i) => hap.withValue((_) => i));
|
||||
|
||||
const haps = [];
|
||||
for (const hap of haps_cycle) {
|
||||
const new_part = state.span.intersection(hap.part);
|
||||
if (new_part) {
|
||||
haps.push(new Hap(hap.whole, new_part, hap.value, hap.context));
|
||||
}
|
||||
}
|
||||
return haps;
|
||||
}).splitQueries();
|
||||
|
||||
const stepsPattern = new Pattern((state) => {
|
||||
const nrHaps = structure
|
||||
.withQuerySpan((span) => span.begin.wholeCycle())
|
||||
.query(state)
|
||||
.filter((hap) => hap.value != 0).length;
|
||||
return [new Hap(state.span.begin.wholeCycle(), state.span, nrHaps, {})];
|
||||
}).splitQueries();
|
||||
|
||||
return pat.fast(stepsPattern).bite(stepsPattern, structPat);
|
||||
},
|
||||
false,
|
||||
);
|
||||
|
||||
/**
|
||||
* Selects the given fraction of the pattern and repeats that part to fill the remainder of the cycle.
|
||||
* @tags temporal
|
||||
|
||||
@@ -75,6 +75,10 @@ const sameFirst = (a, b) => {
|
||||
return expect(a.sortHapsByPart().firstCycle()).toStrictEqual(b.sortHapsByPart().firstCycle());
|
||||
};
|
||||
|
||||
const sameFirstX = (x, a, b) => {
|
||||
return expect(a.fast(x).sortHapsByPart().firstCycle()).toStrictEqual(b.fast(x).sortHapsByPart().firstCycle());
|
||||
};
|
||||
|
||||
describe('TimeSpan', () => {
|
||||
describe('equals()', () => {
|
||||
it('Should be equal to the same value', () => {
|
||||
@@ -1321,6 +1325,21 @@ describe('Pattern', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('isobite', () => {
|
||||
it('can play a melody with a structure', () => {
|
||||
sameFirst(
|
||||
slowcat(0, 1, 2, 3, 4, 5).isobite(fastcat(true, silence, [true, true]).fast(2)),
|
||||
fastcat(0, silence, [1, 2], 3, silence, [4, 5]),
|
||||
);
|
||||
});
|
||||
it('syncopates over multiple cycles if the number of haps in the patterns differ', () => {
|
||||
sameFirstX(
|
||||
4,
|
||||
slowcat(0, 1, 2, 3).isobite(fastcat(true, [true, true])),
|
||||
slowcat(0, [1, 2], 3, [0, 1], 2, [3, 0], 1, [2, 3]).fast(2),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('unjoin', () => {
|
||||
it('destructures a pattern into subcycles', () => {
|
||||
sameFirst(
|
||||
|
||||
@@ -6418,6 +6418,70 @@ exports[`runs examples > example "isaw" example index 1 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "isobite" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/6 | note:C3 ]",
|
||||
"[ 1/3 → 5/12 | note:D3 ]",
|
||||
"[ 5/12 → 1/2 | note:E3 ]",
|
||||
"[ 1/2 → 2/3 | note:F3 ]",
|
||||
"[ 5/6 → 11/12 | note:G3 ]",
|
||||
"[ 11/12 → 1/1 | note:A3 ]",
|
||||
"[ 1/1 → 7/6 | note:C3 ]",
|
||||
"[ 4/3 → 17/12 | note:D3 ]",
|
||||
"[ 17/12 → 3/2 | note:E3 ]",
|
||||
"[ 3/2 → 5/3 | note:F3 ]",
|
||||
"[ 11/6 → 23/12 | note:G3 ]",
|
||||
"[ 23/12 → 2/1 | note:A3 ]",
|
||||
"[ 2/1 → 13/6 | note:C3 ]",
|
||||
"[ 7/3 → 29/12 | note:D3 ]",
|
||||
"[ 29/12 → 5/2 | note:E3 ]",
|
||||
"[ 5/2 → 8/3 | note:F3 ]",
|
||||
"[ 17/6 → 35/12 | note:G3 ]",
|
||||
"[ 35/12 → 3/1 | note:A3 ]",
|
||||
"[ 3/1 → 19/6 | note:C3 ]",
|
||||
"[ 10/3 → 41/12 | note:D3 ]",
|
||||
"[ 41/12 → 7/2 | note:E3 ]",
|
||||
"[ 7/2 → 11/3 | note:F3 ]",
|
||||
"[ 23/6 → 47/12 | note:G3 ]",
|
||||
"[ 47/12 → 4/1 | note:A3 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "isobite" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/3 | note:C3 ]",
|
||||
"[ 2/3 → 1/1 | note:D3 ]",
|
||||
"[ 1/1 → 4/3 | note:E3 ]",
|
||||
"[ 5/3 → 2/1 | note:F3 ]",
|
||||
"[ 2/1 → 7/3 | note:G3 ]",
|
||||
"[ 8/3 → 3/1 | note:C3 ]",
|
||||
"[ 3/1 → 10/3 | note:D3 ]",
|
||||
"[ 11/3 → 4/1 | note:E3 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "isobite" example index 2 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:C3 ]",
|
||||
"[ 1/1 → 3/2 | note:E3 ]",
|
||||
"[ 3/2 → 2/1 | note:F3 ]",
|
||||
"[ 2/1 → 3/1 | note:E3 ]",
|
||||
"[ 3/1 → 7/2 | note:E3 ]",
|
||||
"[ 7/2 → 4/1 | note:F3 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "isobite" example index 3 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/1 | note:C3 ]",
|
||||
"[ 1/1 → 3/2 | note:D3 ]",
|
||||
"[ 3/2 → 2/1 | note:E3 ]",
|
||||
"[ 2/1 → 3/1 | note:F3 ]",
|
||||
"[ 3/1 → 7/2 | note:C3 ]",
|
||||
"[ 7/2 → 4/1 | note:D3 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "iter" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:A3 ]",
|
||||
|
||||
Reference in New Issue
Block a user