mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-13 06:19:33 -04:00
Merge pull request 'euclidish' (#1482) from euclidish into main
Reviewed-on: https://codeberg.org/uzu/strudel/pulls/1482
This commit is contained in:
@@ -10,7 +10,7 @@ https://rohandrape.net/?t=hmt
|
||||
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 { timeCat, register, silence } from './pattern.mjs';
|
||||
import { timeCat, register, silence, stack, pure, _morph } from './pattern.mjs';
|
||||
import { rotate, flatten, splitAt, zipWith } from './util.mjs';
|
||||
import Fraction, { lcm } from './fraction.mjs';
|
||||
|
||||
@@ -196,3 +196,26 @@ export const euclidLegato = register(['euclidLegato'], function (pulses, steps,
|
||||
export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, steps, rotation, pat) {
|
||||
return _euclidLegato(pulses, steps, rotation, pat);
|
||||
});
|
||||
|
||||
/**
|
||||
* A 'euclid' variant with an additional parameter that morphs the resulting
|
||||
* rhythm from 0 (no morphing) to 1 (completely 'even'). For example
|
||||
* `sound("bd").euclidish(3,8,0)` would be the same as
|
||||
* `sound("bd").euclid(3,8)`, and `sound("bd").euclidish(3,8,1)` would be the
|
||||
* same as `sound("bd bd bd")`. `sound("bd").euclidish(3,8,0.5)` would have a
|
||||
* groove somewhere between.
|
||||
* Inspired by the work of Malcom Braff.
|
||||
* @name euclidish
|
||||
* @synonyms eish
|
||||
* @memberof Pattern
|
||||
* @param {number} pulses the number of onsets
|
||||
* @param {number} steps the number of steps to fill
|
||||
* @param {number} groove exists between the extremes of 0 (straight euclidian) and 1 (straight pulse)
|
||||
* @example
|
||||
* sound("hh").euclidish(7,12,sine.slow(8))
|
||||
* .pan(sine.slow(8))
|
||||
*/
|
||||
export const { euclidish, eish } = register(['euclidish', 'eish'], function (pulses, steps, perc, pat) {
|
||||
const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc);
|
||||
return pat.struct(morphed).setSteps(steps);
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
numeralArgs,
|
||||
parseNumeral,
|
||||
pairs,
|
||||
zipWith,
|
||||
stringifyValues,
|
||||
} from './util.mjs';
|
||||
import drawLine from './drawLine.mjs';
|
||||
@@ -3416,3 +3417,69 @@ export const { beat } = register(
|
||||
['beat'],
|
||||
__beat((x) => x.innerJoin()),
|
||||
);
|
||||
|
||||
export const _morph = (from, to, by) => {
|
||||
by = Fraction(by);
|
||||
const dur = Fraction(1).div(from.length);
|
||||
const positions = (list) => {
|
||||
const result = [];
|
||||
for (const [pos, value] of list.entries()) {
|
||||
if (value) {
|
||||
result.push([Fraction(pos).div(list.length), value]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const arcs = zipWith(
|
||||
([posa, valuea], [posb, valueb]) => {
|
||||
const b = by.mul(posb - posa).add(posa);
|
||||
const e = b.add(dur);
|
||||
return new TimeSpan(b, e);
|
||||
},
|
||||
positions(from),
|
||||
positions(to),
|
||||
);
|
||||
function query(state) {
|
||||
const cycle = state.span.begin.sam();
|
||||
const cycleArc = state.span.cycleArc();
|
||||
const result = [];
|
||||
for (const whole of arcs) {
|
||||
const part = whole.intersection(cycleArc);
|
||||
if (part !== undefined) {
|
||||
result.push(
|
||||
new Hap(
|
||||
whole.withTime((x) => x.add(cycle)),
|
||||
part.withTime((x) => x.add(cycle)),
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return new Pattern(query).splitQueries();
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes two binary rhythms represented as lists of 1s and 0s, and a number
|
||||
* between 0 and 1 that morphs between them. The two lists should contain the same
|
||||
* number of true values.
|
||||
* @example
|
||||
* sound("hh").struct(morph([1,0,1,0,1,0,1,0], // straight rhythm
|
||||
* [1,1,0,1,0,1,0], // wonky rhythm
|
||||
* 0.25 // creates a slightly wonky rhythm
|
||||
* )
|
||||
* )
|
||||
* @example
|
||||
* sound("hh").struct(morph("1:0:1:0:1:0:1:0", // straight rhythm
|
||||
* "1:1:0:1:0:1:0", // wonky rhythm
|
||||
* sine.slow(8) // slowly morph between the rhythms
|
||||
* )
|
||||
* )
|
||||
*/
|
||||
export const morph = (frompat, topat, bypat) => {
|
||||
frompat = reify(frompat);
|
||||
topat = reify(topat);
|
||||
bypat = reify(bypat);
|
||||
return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by))));
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ export class TimeSpan {
|
||||
}
|
||||
|
||||
intersection(other) {
|
||||
// Intersection of two timespans, returns None if they don't intersect.
|
||||
// Intersection of two timespans, returns undefined if they don't intersect.
|
||||
const intersect_begin = this.begin.max(other.begin);
|
||||
const intersect_end = this.end.min(other.end);
|
||||
|
||||
|
||||
@@ -3410,6 +3410,39 @@ exports[`runs examples > example "euclidRot" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "euclidish" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/12 | s:hh pan:0.5 ]",
|
||||
"[ 13/84 → 5/21 | s:hh pan:0.5606253170575308 ]",
|
||||
"[ 15/56 → 59/168 | s:hh pan:0.604413082836085 ]",
|
||||
"[ 71/168 → 85/168 | s:hh pan:0.6629314122869361 ]",
|
||||
"[ 97/168 → 37/56 | s:hh pan:0.7190455010067492 ]",
|
||||
"[ 29/42 → 65/84 | s:hh pan:0.7580531369037533 ]",
|
||||
"[ 71/84 → 13/14 | s:hh pan:0.8080762739548087 ]",
|
||||
"[ 1/1 → 13/12 | s:hh pan:0.8535533905932737 ]",
|
||||
"[ 451099417/393511398 → 322594689/262340932 | s:hh pan:0.891768001805729 ]",
|
||||
"[ 335923379/262340932 → 536677685/393511398 | s:hh pan:0.9222657853371297 ]",
|
||||
"[ 1122946175/787022796 → 99044284/65585233 | s:hh pan:0.9501869591788796 ]",
|
||||
"[ 1238122213/787022796 → 651853723/393511398 | s:hh pan:0.9721673436944069 ]",
|
||||
"[ 335923379/196755699 → 469759583/262340932 | s:hh pan:0.9868472639237561 ]",
|
||||
"[ 729434777/393511398 → 1524454787/787022796 | s:hh pan:0.9967009321321423 ]",
|
||||
"[ 2/1 → 25/12 | s:hh pan:1 ]",
|
||||
"[ 15/7 → 187/84 | s:hh pan:0.9968561049466214 ]",
|
||||
"[ 16/7 → 199/84 | s:hh pan:0.9874639560909118 ]",
|
||||
"[ 17/7 → 211/84 | s:hh pan:0.9719416651541839 ]",
|
||||
"[ 18/7 → 223/84 | s:hh pan:0.9504844339512095 ]",
|
||||
"[ 19/7 → 235/84 | s:hh pan:0.9233620996141421 ]",
|
||||
"[ 20/7 → 247/84 | s:hh pan:0.890915741234015 ]",
|
||||
"[ 3/1 → 37/12 | s:hh pan:0.8535533905932737 ]",
|
||||
"[ 1238122213/393511398 → 847276553/262340932 | s:hh pan:0.8106731928589048 ]",
|
||||
"[ 860605243/262340932 → 1323700481/393511398 | s:hh pan:0.7677528833339 ]",
|
||||
"[ 2696991767/787022796 → 230214750/65585233 | s:hh pan:0.7175585019834292 ]",
|
||||
"[ 2812167805/787022796 → 1438876519/393511398 | s:hh pan:0.6644931595798675 ]",
|
||||
"[ 729434777/196755699 → 994441447/262340932 | s:hh pan:0.6139286689554151 ]",
|
||||
"[ 1516457573/393511398 → 3098500379/787022796 | s:hh pan:0.557342689325327 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "every" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:g3 ]",
|
||||
@@ -6137,6 +6170,48 @@ exports[`runs examples > example "miditouch" example index 0 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "morph" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh ]",
|
||||
"[ 25/112 → 39/112 | s:hh ]",
|
||||
"[ 27/56 → 17/28 | s:hh ]",
|
||||
"[ 83/112 → 97/112 | s:hh ]",
|
||||
"[ 1/1 → 9/8 | s:hh ]",
|
||||
"[ 137/112 → 151/112 | s:hh ]",
|
||||
"[ 83/56 → 45/28 | s:hh ]",
|
||||
"[ 195/112 → 209/112 | s:hh ]",
|
||||
"[ 2/1 → 17/8 | s:hh ]",
|
||||
"[ 249/112 → 263/112 | s:hh ]",
|
||||
"[ 139/56 → 73/28 | s:hh ]",
|
||||
"[ 307/112 → 321/112 | s:hh ]",
|
||||
"[ 3/1 → 25/8 | s:hh ]",
|
||||
"[ 361/112 → 375/112 | s:hh ]",
|
||||
"[ 195/56 → 101/28 | s:hh ]",
|
||||
"[ 419/112 → 433/112 | s:hh ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "morph" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | s:hh ]",
|
||||
"[ 11/56 → 9/28 | s:hh ]",
|
||||
"[ 13/28 → 33/56 | s:hh ]",
|
||||
"[ 41/56 → 6/7 | s:hh ]",
|
||||
"[ 1/1 → 9/8 | s:hh ]",
|
||||
"[ 303934523/262340932 → 673454279/524681864 | s:hh ]",
|
||||
"[ 188758485/131170466 → 820619173/524681864 | s:hh ]",
|
||||
"[ 451099417/262340932 → 967784067/524681864 | s:hh ]",
|
||||
"[ 2/1 → 17/8 | s:hh ]",
|
||||
"[ 15/7 → 127/56 | s:hh ]",
|
||||
"[ 17/7 → 143/56 | s:hh ]",
|
||||
"[ 19/7 → 159/56 | s:hh ]",
|
||||
"[ 3/1 → 25/8 | s:hh ]",
|
||||
"[ 828616387/262340932 → 1722818007/524681864 | s:hh ]",
|
||||
"[ 451099417/131170466 → 1869982901/524681864 | s:hh ]",
|
||||
"[ 975781281/262340932 → 2017147795/524681864 | s:hh ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "mousex" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/4 | note:C3 ]",
|
||||
|
||||
Reference in New Issue
Block a user