(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 p "Parallel combinator: all children occur simultaneously. Each child gets the full time duration of the parent. All children are active at the same time, producing overlapping intervals. Equivalent to TidalCycles/Strudel 'stack' operator. Example: (p :a :b :c) with cycle-length 1 => [[0 1 :a] [0 1 :b] [0 1 :c]]" [& args] {:v (vec args) :type :p}) (defn rate "Rate modifier: scales the speed of a pattern by a given ratio. A ratio > 1 speeds up the pattern (fits more cycles in the same time). A ratio < 1 slows down the pattern (stretches it over more time). A ratio of 2 means the pattern runs twice as fast. A ratio of 1/2 means the pattern runs at half speed. Equivalent to TidalCycles/Strudel '*' and '/' operators. Examples: (rate 2 (l :a :b)) - runs the list twice as fast (rate 1/2 (f :a :b :c)) - runs the fork at half speed" [ratio node] {:v node :rate ratio :type :rate}) (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))) (= :p (:type node)) (let [children (:v node)] (reduce lcm 1 (map compute-cycle children))) (= :rate (:type node)) ;; Rate doesn't change the cycle count - it just compresses/expands time ;; The parent sees the same cycle count as the child (compute-cycle (:v node)))) (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))) (= :p (:type node)) (let [children (:v node)] (mapcat (fn [child] (unfold-node child start end iteration)) children)) (= :rate (:type node)) (let [ratio (:rate node) child (:v node) child-base-cycle (compute-cycle child) ;; rate scales how many times the base pattern repeats ;; rate 2 means fit 2x cycles in this span ;; rate 1/2 means fit 0.5x cycles (half a cycle) num-child-cycles (* ratio child-base-cycle) child-cycle-duration (/ duration num-child-cycles)] (vec (mapcat (fn [i] (unfold-node child (+ start (* i child-cycle-duration)) (+ start (* (inc i) child-cycle-duration)) i)) (range num-child-cycles))))))) (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)))))