mirror of
https://codeberg.org/uzu/strudel
synced 2026-09-13 09:25:11 -04:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bcd5897da | |||
| a7adebe036 | |||
| 62379269f0 | |||
| 1162a42218 | |||
| 32d11d0332 | |||
| e5eb3b8d7b |
+72
-17
@@ -8,6 +8,7 @@ import TimeSpan from './timespan.mjs';
|
||||
import Fraction, { isFraction, lcm } from './fraction.mjs';
|
||||
import Hap from './hap.mjs';
|
||||
import State from './state.mjs';
|
||||
import { getTime } from './time.mjs';
|
||||
import { unionWithObj } from './value.mjs';
|
||||
|
||||
import {
|
||||
@@ -963,13 +964,26 @@ export const arp = register(
|
||||
false,
|
||||
);
|
||||
|
||||
/*
|
||||
* Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are
|
||||
* distributed equally over the given time duration. They are then combined with the pattern 'weave' is called on, after it has been stretched out (i.e. slowed down by) the time duration.
|
||||
* @name weave
|
||||
* @memberof Pattern
|
||||
* @example pan(saw).weave(4, s("bd(3,8)"), s("~ sd"))
|
||||
* @example n("0 1 2 3 4 5 6 7").weave(8, s("bd(3,8)"), s("~ sd"))
|
||||
|
||||
addToPrototype('weave', function (t, ...pats) {
|
||||
return this.weaveWith(t, ...pats.map((x) => set.out(x)));
|
||||
});
|
||||
|
||||
*/
|
||||
/*
|
||||
* Like 'weave', but accepts functions rather than patterns, which are applied to the pattern.
|
||||
* @name weaveWith
|
||||
* @memberof Pattern
|
||||
*/
|
||||
|
||||
export const weaveWith = register('weaveWith', function (t, funcs, pat) {
|
||||
addToPrototype('weaveWith', function (t, ...funcs) {
|
||||
const pat = this;
|
||||
const l = funcs.length;
|
||||
t = Fraction(t);
|
||||
if (l == 0) {
|
||||
@@ -977,21 +991,7 @@ export const weaveWith = register('weaveWith', function (t, funcs, pat) {
|
||||
}
|
||||
return stack(...funcs.map((func, i) => pat.inside(t, func).early(Fraction(i).div(l))))._slow(t);
|
||||
});
|
||||
|
||||
/*
|
||||
* Takes a time duration followed by one or more patterns, and shifts the given patterns in time, so they are
|
||||
* distributed equally over the given time duration. They are then combined with the pattern 'weave' is called on, after it has been stretched out (i.e. slowed down by) the time duration.
|
||||
* @name weave
|
||||
* @memberof Pattern
|
||||
* @example pan(saw).weave(4, [s("bd(3,8)"), s("~ sd")])
|
||||
* @example n("0 1 2 3 4 5 6 7").weave(8, [s("bd(3,8)"), s("~ sd")])
|
||||
*/
|
||||
export const weave = register('weave', function (t, pats, pat) {
|
||||
return pat.weaveWith(
|
||||
t,
|
||||
pats.map((x) => (y) => y.set.out(x)),
|
||||
);
|
||||
});
|
||||
*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// compose matrix functions
|
||||
@@ -3534,3 +3534,58 @@ export const morph = (frompat, topat, bypat) => {
|
||||
bypat = reify(bypat);
|
||||
return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by))));
|
||||
};
|
||||
|
||||
export const TIMELINES = {
|
||||
state: {},
|
||||
polarities: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* Aligns the pattern with the specified `timeline` by ID.
|
||||
*
|
||||
* More specifically, if a timeline ID is encountered for the first time, that moment
|
||||
* in time is marked as the "start" of that pattern. Thereafter, any other patterns aligned
|
||||
* with that same id will begin at that same moment in time.
|
||||
*
|
||||
* If the sign of the id changes (positive to negative or vice versa), we reset the timeline.
|
||||
*
|
||||
* @param {number | Pattern} id Timeline id. Must be a non-zero number. Switch signs to reset.
|
||||
* @returns Pattern
|
||||
* @example
|
||||
* s("tri").seg(8).n(irand(12)).scale("G#:minor").lpf(400).room(2)
|
||||
* .timeline("<1 -1 1 2>")
|
||||
* @example
|
||||
* $: n("[0 .. 6]/4").scale("F:minor")
|
||||
* // Execute the following line at any point in the session
|
||||
* // and it will always start on note 0
|
||||
* // $: n("[0 .. 6]/4").scale("F:minor").timeline(2)
|
||||
*/
|
||||
export const timeline = register('timeline', (id, pat) => {
|
||||
if (typeof id !== 'number' || id === 0) {
|
||||
logger(
|
||||
`[query] ${id} is not a valid timeline id. Please ensure it is a non-zero number. Defaulting to timeline 1.`,
|
||||
);
|
||||
id = 1;
|
||||
}
|
||||
const { state, polarities } = TIMELINES;
|
||||
const key = Math.abs(id);
|
||||
// We let the pattern run until the sign switches
|
||||
const p = id > 0 ? 1 : -1;
|
||||
let t = polarities[key] === p ? state[key] : Math.ceil(getTime());
|
||||
return (
|
||||
pat
|
||||
.late(t)
|
||||
.onTrigger((hap) => {
|
||||
const T = Number(hap.part.begin);
|
||||
// Set state on the first trigger
|
||||
state[key] ??= T;
|
||||
polarities[key] ??= p;
|
||||
if (polarities[key] !== p) {
|
||||
state[key] = T;
|
||||
}
|
||||
polarities[key] = p;
|
||||
}, false)
|
||||
// Add labels
|
||||
.withValue((v) => ({ ...v, timeline: id, offset: t }))
|
||||
);
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@ import { evaluate as _evaluate } from './evaluate.mjs';
|
||||
import { errorLogger, logger } from './logger.mjs';
|
||||
import { setTime } from './time.mjs';
|
||||
import { evalScope } from './evaluate.mjs';
|
||||
import { register, Pattern, isPattern, silence, stack } from './pattern.mjs';
|
||||
import { register, Pattern, isPattern, silence, stack, TIMELINES } from './pattern.mjs';
|
||||
|
||||
export function repl({
|
||||
defaultOutput,
|
||||
@@ -52,6 +52,11 @@ export function repl({
|
||||
onToggle: (started) => {
|
||||
updateState({ started });
|
||||
onToggle?.(started);
|
||||
if (!started) {
|
||||
// Reset timeline state
|
||||
TIMELINES.state = {};
|
||||
TIMELINES.polarities = {};
|
||||
}
|
||||
},
|
||||
setInterval,
|
||||
clearInterval,
|
||||
|
||||
@@ -10844,6 +10844,23 @@ exports[`runs examples > example "take" example index 2 1`] = `
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "timeline" example index 0 1`] = `[]`;
|
||||
|
||||
exports[`runs examples > example "timeline" example index 1 1`] = `
|
||||
[
|
||||
"[ 0/1 → 4/7 | note:F3 ]",
|
||||
"[ (4/7 → 1/1) ⇝ 8/7 | note:G3 ]",
|
||||
"[ 4/7 ⇜ (1/1 → 8/7) | note:G3 ]",
|
||||
"[ 8/7 → 12/7 | note:Ab3 ]",
|
||||
"[ (12/7 → 2/1) ⇝ 16/7 | note:Bb3 ]",
|
||||
"[ 12/7 ⇜ (2/1 → 16/7) | note:Bb3 ]",
|
||||
"[ 16/7 → 20/7 | note:C4 ]",
|
||||
"[ (20/7 → 3/1) ⇝ 24/7 | note:Db4 ]",
|
||||
"[ 20/7 ⇜ (3/1 → 24/7) | note:Db4 ]",
|
||||
"[ 24/7 → 4/1 | note:Eb4 ]",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`runs examples > example "tour" example index 0 1`] = `
|
||||
[
|
||||
"[ 0/1 → 1/8 | note:e s:folkharp ]",
|
||||
|
||||
@@ -381,7 +381,7 @@ Sampler effects are functions that can be used to change the behaviour of sample
|
||||
|
||||
### scrub
|
||||
|
||||
<JsDoc client:idle name="Pattern.scrub" h={0} />{' '}
|
||||
<JsDoc client:idle name="Pattern.scrub" h={0} />
|
||||
|
||||
### speed
|
||||
|
||||
|
||||
Reference in New Issue
Block a user