optimise euclidish, creating new morph function in the process

This commit is contained in:
alex
2025-08-17 22:13:26 +01:00
parent 482ebb695a
commit b717615558
4 changed files with 143 additions and 46 deletions
+5 -17
View File
@@ -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, stack, pure } 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';
@@ -212,22 +212,10 @@ export const euclidLegatoRot = register(['euclidLegatoRot'], function (pulses, s
* @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,tri.slow(8))
* .pan(tri.slow(8))
* 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 b = bjork(pulses, steps);
let trues = 0;
const offs = [];
for (const [pos, step] of b.entries()) {
if (step) {
offs.push([trues++, pos]);
}
}
const tweened = offs.map(([n, pos]) =>
Fraction(pos)
.div(steps)
.add(Fraction(n).div(pulses).sub(Fraction(pos).div(steps)).mul(perc)),
);
return pat.struct(stack(...tweened.map((pos) => pure(true)._fastGap(steps)._late(pos)))).setSteps(steps);
const morphed = _morph(bjork(pulses, steps), new Array(pulses).fill(1), perc);
return pat.struct(morphed).setSteps(steps);
});
+67
View File
@@ -21,6 +21,7 @@ import {
numeralArgs,
parseNumeral,
pairs,
zipWith,
} from './util.mjs';
import drawLine from './drawLine.mjs';
import { logger } from './logger.mjs';
@@ -3400,3 +3401,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))));
};
+1 -1
View File
@@ -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);
+70 -28
View File
@@ -3273,34 +3273,34 @@ 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 ]",
"[ 1/6 → 1/4 | s:hh pan:0.041666666666666664 ]",
"[ 1/4 → 1/3 | s:hh pan:0.0625 ]",
"[ 5/12 → 1/2 | s:hh pan:0.10416666666666667 ]",
"[ 7/122/3 | s:hh pan:0.14583333333333334 ]",
"[ 2/3 → 3/4 | s:hh pan:0.16666666666666666 ]",
"[ 5/6 → 11/12 | s:hh pan:0.20833333333333334 ]",
"[ 1/1 → 13/12 | s:hh pan:0.25 ]",
"[ 65/56 → 209/168 | s:hh pan:0.29017857142857145 ]",
"[ 141/112 → 451/336 | s:hh pan:0.31473214285714285 ]",
"[ 159/112 → 505/336 | s:hh pan:0.3549107142857143 ]",
"[ 177/112 → 559/336 | s:hh pan:0.3950892857142857 ]",
"[ 47/28 → 37/21 | s:hh pan:0.41964285714285715 ]",
"[ 103/56 → 323/168 | s:hh pan:0.45982142857142855 ]",
"[ 2/1 → 25/12 | s:hh pan:0.5 ]",
"[ 181/84 → 47/21 | s:hh pan:0.5386904761904762 ]",
"[ 127/56 → 395/168 | s:hh pan:0.5669642857142857 ]",
"[ 407/168421/168 | s:hh pan:0.6056547619047619 ]",
"[ 433/168 → 149/56 | s:hh pan:0.6443452380952381 ]",
"[ 113/42 → 233/84 | s:hh pan:0.6726190476190477 ]",
"[ 239/84 → 41/14 | s:hh pan:0.7113095238095238 ]",
"[ 3/1 → 37/12 | s:hh pan:0.75 ]",
"[ 529/168 → 181/56 | s:hh pan:0.7872023809523809 ]",
"[ 367/112 → 1129/336 | s:hh pan:0.8191964285714286 ]",
"[ 1151/336 → 393/112 | s:hh pan:0.8563988095238095 ]",
"[ 1201/336 → 1229/336 | s:hh pan:0.8936011904761905 ]",
"[ 311/84 → 53/14 | s:hh pan:0.9255952380952381 ]",
"[ 647/168 → 661/168 | s:hh pan:0.9627976190476191 ]",
"[ 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/16837/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 ]",
]
`;
@@ -5997,6 +5997,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 ]",