From 24328ee8aeef979f2337d4ec46595a5cdc973a05 Mon Sep 17 00:00:00 2001 From: Alex McLean Date: Sat, 29 Oct 2022 23:23:10 +0100 Subject: [PATCH] Fix zero length queries WIP (#234) * failing tests for zero-width queries * support zero width timespans in splitArcs. Fixes one test, breaks a load more.. * fix fastGap --- packages/core/pattern.mjs | 26 +++++++++++++++++++++----- packages/core/test/pattern.test.mjs | 13 +++++++++++++ packages/core/timespan.mjs | 5 +++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs index e98c69e1..0e3b7fb0 100644 --- a/packages/core/pattern.mjs +++ b/packages/core/pattern.mjs @@ -63,6 +63,17 @@ export class Pattern { return new Pattern((state) => this.query(state.withSpan(func))); } + withQuerySpanMaybe(func) { + const pat = this; + return new Pattern((state) => { + const newState = state.withSpan(func); + if (!newState.span) { + return []; + } + return pat.query(newState); + }); + } + /** * As with {@link Pattern#withQuerySpan}, but the function is applied to both the * begin and end time of the query timespan. @@ -744,12 +755,17 @@ export class Pattern { // // there is no gap.. so maybe revert to _fast? // return this._fast(factor) // } + // A bit fiddly, to drop zero-width queries at the start of the next cycle const qf = function (span) { const cycle = span.begin.sam(); - const begin = cycle.add(span.begin.sub(cycle).mul(factor).min(1)); - const end = cycle.add(span.end.sub(cycle).mul(factor).min(1)); - return new TimeSpan(begin, end); + const bpos = span.begin.sub(cycle).mul(factor).min(1); + const epos = span.end.sub(cycle).mul(factor).min(1); + if (bpos >= 1) { + return undefined; + } + return new TimeSpan(cycle.add(bpos), cycle.add(epos)); }; + // Also fiddly, to maintain the right 'whole' relative to the part const ef = function (hap) { const begin = hap.part.begin; const end = hap.part.end; @@ -765,9 +781,9 @@ export class Pattern { ); return new Hap(newWhole, newPart, hap.value, hap.context); }; - return this.withQuerySpan(qf)._withHap(ef)._splitQueries(); + return this.withQuerySpanMaybe(qf)._withHap(ef)._splitQueries(); } - + // Compress each cycle into the given timespan, leaving a gap _compress(b, e) { if (b.gt(e) || b.gt(1) || e.gt(1) || b.lt(0) || e.lt(0)) { diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs index 18670bd7..bbf4f92f 100644 --- a/packages/core/test/pattern.test.mjs +++ b/packages/core/test/pattern.test.mjs @@ -137,6 +137,9 @@ describe('Pattern', () => { it('Can make a pattern', () => { expect(pure('hello').query(st(0.5, 2.5)).length).toBe(3); }); + it('Supports zero-width queries', () => { + expect(pure('hello').queryArc(0,0).length).toBe(1); + }); }); describe('fmap()', () => { it('Can add things', () => { @@ -430,6 +433,16 @@ describe('Pattern', () => { // mini('[c3 g3]/2 eb3') always plays [c3 eb3] // mini('eb3 [c3 g3]/2 ') always plays [c3 g3] }); + it('Supports zero-length queries', () => { + expect(steady('a')._slow(1).queryArc(0,0) + ).toStrictEqual(steady('a').queryArc(0,0)) + }); + }); + describe('slow()', () => { + it('Supports zero-length queries', () => { + expect(steady('a').slow(1)._setContext({}).queryArc(0,0) + ).toStrictEqual(steady('a')._setContext({}).queryArc(0,0)) + }); }); describe('inside', () => { it('can rev inside a cycle', () => { diff --git a/packages/core/timespan.mjs b/packages/core/timespan.mjs index 6cc69bdf..9ae22299 100644 --- a/packages/core/timespan.mjs +++ b/packages/core/timespan.mjs @@ -18,6 +18,11 @@ export class TimeSpan { const end = this.end; const end_sam = end.sam(); + // Support zero-width timespans + if (begin.equals(end)) { + return([new TimeSpan(begin, end)]); + } + while (end.gt(begin)) { // If begin and end are in the same cycle, we're done. if (begin.sam().equals(end_sam)) {