mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-22 13:13:10 -04:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7bcd5897da | |||
| a7adebe036 | |||
| 62379269f0 | |||
| 1162a42218 | |||
| 32d11d0332 | |||
| e5eb3b8d7b |
@@ -8,6 +8,7 @@ import TimeSpan from './timespan.mjs';
|
|||||||
import Fraction, { isFraction, lcm } from './fraction.mjs';
|
import Fraction, { isFraction, lcm } from './fraction.mjs';
|
||||||
import Hap from './hap.mjs';
|
import Hap from './hap.mjs';
|
||||||
import State from './state.mjs';
|
import State from './state.mjs';
|
||||||
|
import { getTime } from './time.mjs';
|
||||||
import { unionWithObj } from './value.mjs';
|
import { unionWithObj } from './value.mjs';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -3533,3 +3534,58 @@ export const morph = (frompat, topat, bypat) => {
|
|||||||
bypat = reify(bypat);
|
bypat = reify(bypat);
|
||||||
return frompat.innerBind((from) => topat.innerBind((to) => bypat.innerBind((by) => _morph(from, to, by))));
|
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 { errorLogger, logger } from './logger.mjs';
|
||||||
import { setTime } from './time.mjs';
|
import { setTime } from './time.mjs';
|
||||||
import { evalScope } from './evaluate.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({
|
export function repl({
|
||||||
defaultOutput,
|
defaultOutput,
|
||||||
@@ -52,6 +52,11 @@ export function repl({
|
|||||||
onToggle: (started) => {
|
onToggle: (started) => {
|
||||||
updateState({ started });
|
updateState({ started });
|
||||||
onToggle?.(started);
|
onToggle?.(started);
|
||||||
|
if (!started) {
|
||||||
|
// Reset timeline state
|
||||||
|
TIMELINES.state = {};
|
||||||
|
TIMELINES.polarities = {};
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setInterval,
|
setInterval,
|
||||||
clearInterval,
|
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`] = `
|
exports[`runs examples > example "tour" example index 0 1`] = `
|
||||||
[
|
[
|
||||||
"[ 0/1 → 1/8 | note:e s:folkharp ]",
|
"[ 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
|
### scrub
|
||||||
|
|
||||||
<JsDoc client:idle name="Pattern.scrub" h={0} />{' '}
|
<JsDoc client:idle name="Pattern.scrub" h={0} />
|
||||||
|
|
||||||
### speed
|
### speed
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user