diff options
Diffstat (limited to 'src/unheard/cycles.clj')
| -rw-r--r-- | src/unheard/cycles.clj | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/src/unheard/cycles.clj b/src/unheard/cycles.clj new file mode 100644 index 0000000..513b090 --- /dev/null +++ b/src/unheard/cycles.clj @@ -0,0 +1,111 @@ +(ns unheard.cycles) + +(defn l + "List combinator: subdivides time evenly among children, advancing in lockstep. + + Each child receives an equal portion of the parent's time duration. + All children advance synchronously through their patterns. + + Equivalent to TidalCycles/Strudel 'fastcat' operator. + + Example: + (l :a :b :c) with cycle-length 1 + => [[0 1/3 :a] [1/3 2/3 :b] [2/3 1 :c]]" + [& args] + {:v (vec args) :type :l}) + +(defn f + "Fork combinator: cycles through children sequentially across iterations. + + Each iteration selects one child in round-robin fashion. + The selected child gets the full time duration for that iteration. + Forks extend the total pattern duration by (num-children × child-cycles). + + Equivalent to TidalCycles/Strudel 'slowcat' operator. + + Example: + (f :a :b :c) with cycle-length 1 + => [[0 1 :a] [1 2 :b] [2 3 :c]]" + [& args] + {:v (vec args) :type :f}) + +(defn scalar? [x] + (not (and (map? x) (:type x)))) + +(defn gcd [a b] + (if (zero? b) a (recur b (mod a b)))) + +(defn lcm [a b] + (/ (* a b) (gcd a b))) + +(defn compute-cycle [node] + (cond + (scalar? node) 1 + + (= :f (:type node)) + (let [children (:v node) + n (count children)] + (* n (reduce lcm 1 (map compute-cycle children)))) + + (= :l (:type node)) + (let [children (:v node)] + (reduce lcm 1 (map compute-cycle children))))) + +(defn unfold-node [node start end iteration] + (let [duration (- end start)] + (cond + (scalar? node) + [[start end node]] + + (= :l (:type node)) + (let [children (:v node) + n (count children) + slice-size (/ duration n)] + (mapcat (fn [i child] + (let [child-start (+ start (* i slice-size)) + child-end (+ start (* (inc i) slice-size))] + (unfold-node child child-start child-end iteration))) + (range n) + children)) + + (= :f (:type node)) + (let [children (:v node) + n (count children) + child-idx (mod iteration n)] + (unfold-node (nth children child-idx) start end (quot iteration n)))))) + +(defn unfold + "Unfolds a pattern tree into concrete time intervals. + + Takes a cycle-length and a pattern node (scalar, list, or fork), + and returns a vector of [start end value] intervals representing + when each scalar value is active. + + Args: + cycle-length - Duration of each iteration (can be any number) + node - Pattern tree built from scalars, (l ...), and (f ...) + + Returns: + Vector of [start end value] tuples, where start and end are rational + numbers representing time positions. + + The total duration of the result is (* cycle-length (compute-cycle node)). + + Examples: + (unfold 1 :a) + => [[0 1 :a]] + + (unfold 1 (l :a :b)) + => [[0 1/2 :a] [1/2 1 :b]] + + (unfold 1 (f :a :b)) + => [[0 1 :a] [1 2 :b]]" + [cycle-length node] + (let [cycle-count (compute-cycle node)] + (vec (mapcat (fn [i] + (unfold-node node + (* i cycle-length) + (* (inc i) cycle-length) + i)) + (range cycle-count))))) + |
