diff --git a/packages/core/fraction.mjs b/packages/core/fraction.mjs index f95ee90a..9d6605fc 100644 --- a/packages/core/fraction.mjs +++ b/packages/core/fraction.mjs @@ -16,6 +16,11 @@ Fraction.prototype.wholeCycle = function () { return new TimeSpan(this.sam(), this.nextSam()); }; +// The position of a time value relative to the start of its cycle. +Fraction.prototype.cyclePos = function () { + return this.sub(this.sam()); +}; + Fraction.prototype.lt = function (other) { return this.compare(other) < 0; }; diff --git a/packages/core/strudel.mjs b/packages/core/strudel.mjs index 4f98eb5e..42d974ec 100644 --- a/packages/core/strudel.mjs +++ b/packages/core/strudel.mjs @@ -59,6 +59,15 @@ class TimeSpan { return spans; } + cycleArc() { + // Shifts a timespan to one of equal duration that starts within cycle zero. + // (Note that the output timespan probably does not start *at* Time 0 -- + // that only happens when the input Arc starts at an integral Time.) + const b = this.begin.cyclePos(); + const e = b + (this.end - this.begin); + return new TimeSpan(b, e); + } + withTime(func_time) { // Applies given function to both the begin and end time value of the timespan""" return new TimeSpan(func_time(this.begin), func_time(this.end)); @@ -140,6 +149,10 @@ class Hap { return this.whole.end.sub(this.whole.begin).valueOf(); } + wholeOrPart() { + return this.whole ? this.whole : this.part; + } + withSpan(func) { // Returns a new event with the function f applies to the event timespan. const whole = this.whole ? func(this.whole) : undefined; @@ -186,6 +199,11 @@ class Hap { ); } + combineContext(b) { + const a = this; + return { ...a.context, ...b.context, locations: (a.context.locations || []).concat(b.context.locations || []) }; + } + setContext(context) { return new Hap(this.whole, this.part, this.value, context); } @@ -351,12 +369,11 @@ class Pattern { if (s == undefined) { return undefined; } - // TODO: is it right to add event_val.context here? return new Hap( whole_func(event_func.whole, event_val.whole), s, event_func.value(event_val.value), - event_val.context, + event_val.combineContext(event_func), ); }; return flatten( @@ -388,11 +405,8 @@ class Pattern { const new_whole = hap_func.whole; const new_part = hap_func.part.intersection_e(hap_val.part); const new_value = hap_func.value(hap_val.value); - const hap = new Hap(new_whole, new_part, new_value, { - ...hap_val.context, - ...hap_func.context, - locations: (hap_val.context.locations || []).concat(hap_func.context.locations || []), - }); + const new_context = hap_val.combineContext(hap_func); + const hap = new Hap(new_whole, new_part, new_value, new_context); haps.push(hap); } } @@ -412,11 +426,8 @@ class Pattern { const new_whole = hap_val.whole; const new_part = hap_func.part.intersection_e(hap_val.part); const new_value = hap_func.value(hap_val.value); - const hap = new Hap(new_whole, new_part, new_value, { - ...hap_func.context, - ...hap_val.context, - locations: (hap_val.context.locations || []).concat(hap_func.context.locations || []), - }); + const new_context = hap_val.combineContext(hap_func); + const hap = new Hap(new_whole, new_part, new_value, new_context); haps.push(hap); } } @@ -572,6 +583,49 @@ class Pattern { return this.outerBind(id); } + squeezeJoin() { + const pat_of_pats = this; + function query(state) { + const haps = pat_of_pats.query(state); + function flatHap(outerHap) { + const pat = outerHap.value.compressArc(outerHap.wholeOrPart().cycleArc()); + const innerHaps = pat.query(state.setSpan(outerHap.part)); + function munge(outer, inner) { + let whole = undefined; + if (inner.whole && outer.whole) { + whole = inner.whole.intersection(outer.whole); + if (!whole) { + // The wholes are present, but don't intersect + return undefined; + } + } + const part = inner.part.intersection(outer_part); + if (!part) { + // The parts don't intersect + return undefined; + } + const context = inner.combineContext(outer.context); + return new Hap(whole, part, inner.value, context); + } + innerHaps.map(innerHap => munge(outerHap,innerHap)) + } + const flattened = haps.map((x) => x.withEvent(flatHap)); + return flattened.filter(x => x); + } + } + + // squeezeJoin :: Pattern (Pattern a) -> Pattern a + // squeezeJoin pp = pp {query = q} + // where q st = concatMap + // (\e@(Event c w p v) -> + // mapMaybe (munge c w p) $ query (compressArc (cycleArc $ wholeOrPart e) v) st {arc = p} + // ) + // (query pp st) + // munge oContext oWhole oPart (Event iContext iWhole iPart v) = + // do w' <- subMaybeArc oWhole iWhole + // p' <- subArc oPart iPart + // return (Event (combineContexts [iContext, oContext]) w' p' v) + _apply(func) { return func(this); } @@ -995,7 +1049,6 @@ function polymeterSteps(steps, ...args) { if (steps == seq[1]) { pats.push(seq[0]); } else { - console.log("aha"); pats.push(seq[0]._fast(Fraction(steps).div(Fraction(seq[1])))); } }