diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index dd6850037..98b98d5d4 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -3537,7 +3537,7 @@ export const morph = (frompat, topat, bypat) => { export const TIMELINES = { currentOffsets: {}, - previousOffsets: {}, + polarity: {}, }; /** @@ -3547,9 +3547,10 @@ export const TIMELINES = { * 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 a negative ID is supplied, the timeline will reset at the start of the next cycle. + * If the sign of the id changes (positive to negative or vice versa), we reset the timeline at the + * beginning of the next cycle. * - * @param {number | Pattern} id Timeline id. Must be a number. Set to negative to 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) @@ -3560,18 +3561,16 @@ export const TIMELINES = { * // and it will always start on note 0 * // $: n("[0 .. 6]/4").scale("F:minor").timeline(2) */ -const timeline = register('timeline', (id, pat) => { - // TODO: This hard-coding of `behavior` and the switch below are only included to demonstrate - // the possible reset options. We should pick one before merging - const behavior = 0; - if (typeof id !== 'number') { - logger(`[query] ${id} is not a valid timeline id. Please ensure it is a number. Defaulting to timeline 1.`); +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 { currentOffsets: state, previousOffsets: prev } = TIMELINES; + const { currentOffsets: state, polarity } = TIMELINES; const key = Math.abs(id); - // For negative id we just let the pattern run without resetting, hence tracking `prev` - let t = id < 0 ? prev[key] : state[key]; + let t = state[key]; // We default here instead of updating `state` to prevent the `draw` functions from // interfering with `state` when querying t ??= Math.ceil(getTime()); @@ -3582,31 +3581,14 @@ const timeline = register('timeline', (id, pat) => { const T = Number(hap.part.begin); // Set state on the first trigger state[key] ??= T; - prev[key] ??= T; - if (id < 0) { + const p = id > 0 ? 1 : -1; + polarity[key] ??= p; + // TODO: decide if we want to put `p === -1 &&` in this conditional + if (polarity[key] !== p) { // Restart on next cycle - const nextCycle = Math.floor(T) + 1; - switch (behavior) { - case 0: { - // Restart at start of next cycle - state[key] = nextCycle; - break; - } - case 1: { - // Restart immediately - state[key] = T; - break; - } - case 2: { - // Restart at next hap or next cycle, whichever comes first - const haps = pat.queryArc(T, nextCycle).filter((h) => Number(h.part.begin) !== T); - state[key] = haps.length ? Number(haps[0].part.begin) : nextCycle; - break; - } - } - } else { - prev[key] = state[key]; + state[key] = Math.floor(T) + 1; } + polarity[key] = p; }, false) // Add labels .withValue((v) => ({ ...v, timeline: id, offset: t })) diff --git a/packages/core/repl.mjs b/packages/core/repl.mjs index a9b972ab2..b2d5fadfc 100644 --- a/packages/core/repl.mjs +++ b/packages/core/repl.mjs @@ -55,7 +55,7 @@ export function repl({ if (!started) { // Reset timeline state TIMELINES.currentOffsets = {}; - TIMELINES.previousOffsets = {}; + TIMELINES.polarity = {}; } }, setInterval, diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 1df5c8776..625f40756 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -796,7 +796,7 @@ describe('Pattern', () => { }); }); describe('apply', () => { - it('Can apply a function', () => { + (it('Can apply a function', () => { expect(sequence('a', 'b').apply(fast(2)).firstCycle()).toStrictEqual(sequence('a', 'b').fast(2).firstCycle()); }), it('Can apply a pattern of functions', () => { @@ -804,7 +804,7 @@ describe('Pattern', () => { expect(sequence('a', 'b').apply(fast(2), fast(3)).firstCycle()).toStrictEqual( sequence('a', 'b').fast(2, 3).firstCycle(), ); - }); + })); }); describe('layer', () => { it('Can layer up multiple functions', () => { diff --git a/website/src/components/Header/Search.css b/website/src/components/Header/Search.css index 456ef9f6b..b14146cbd 100644 --- a/website/src/components/Header/Search.css +++ b/website/src/components/Header/Search.css @@ -18,8 +18,8 @@ --docsearch-modal-background: var(--background); --docsearch-muted-color: color-mix(in srgb, var(--foreground), #fff 30%); --docsearch-key-gradient: var(--foreground); - --docsearch-key-shadow: inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground), - 0 1px 2px 1px var(--gutterBackground); + --docsearch-key-shadow: + inset 0 -2px 0 0 var(--gutterForeground), inset 0 0 1px 1px var(--foreground), 0 1px 2px 1px var(--gutterBackground); } .dark { --docsearch-muted-color: color-mix(in srgb, var(--foreground), #000 30%);