diff options
| author | Jake Zerrer <him@jakezerrer.com> | 2025-12-01 15:38:37 -0500 |
|---|---|---|
| committer | Jake Zerrer <him@jakezerrer.com> | 2025-12-01 15:39:11 -0500 |
| commit | 2d1418040bac7bec0ebac8b9c6d89ced3af7faeb (patch) | |
| tree | e6da9a133b60659fabfc980667ab7115d16c3736 | |
| parent | f48bd390a2d25bab6f7202941c5eb3a4149ab5a3 (diff) | |
Add replicate function
| -rw-r--r-- | src/unheard/cycles.clj | 34 | ||||
| -rw-r--r-- | test/unheard/cycles_test.clj | 66 |
2 files changed, 97 insertions, 3 deletions
diff --git a/src/unheard/cycles.clj b/src/unheard/cycles.clj index acf1900..eb53c57 100644 --- a/src/unheard/cycles.clj +++ b/src/unheard/cycles.clj @@ -74,6 +74,21 @@ [weight node] {:v node :weight weight :type :elongate}) +(defn rep + "Replication modifier: repeats an element N times, subdividing its time equally. + + The element is repeated the specified number of times within the same + time duration it would normally occupy. Each repetition gets an equal + time slice. + + Equivalent to TidalCycles/Strudel '!' operator. + + Examples: + (l (rep 3 :a) :b) - :a repeats 3x in first half, :b in second half + (l :x (rep 2 :y) :z) - :y repeats 2x in middle third" + [times node] + {:v node :times times :type :rep}) + (defn scalar? [x] (not (and (map? x) (:type x)))) @@ -116,6 +131,11 @@ (= :elongate (:type node)) ;; Elongation doesn't change the cycle count - it just affects time division ;; The parent sees the same cycle count as the child + (compute-cycle (:v node)) + + (= :rep (:type node)) + ;; Replication doesn't change the cycle count - it just subdivides time + ;; The parent sees the same cycle count as the child (compute-cycle (:v node)))) (defn unfold-node [node start end iteration] @@ -167,7 +187,19 @@ (= :elongate (:type node)) ;; Elongate just wraps a child - unfold the child with the same time bounds - (unfold-node (:v node) start end iteration)))) + (unfold-node (:v node) start end iteration) + + (= :rep (:type node)) + ;; Rep subdivides the time span into N equal parts and repeats the child + (let [times (:times node) + child (:v node) + slice-duration (/ duration times)] + (vec (mapcat (fn [i] + (unfold-node child + (+ start (* i slice-duration)) + (+ start (* (inc i) slice-duration)) + iteration)) + (range times))))))) (defn unfold "Unfolds a pattern tree into concrete time intervals. diff --git a/test/unheard/cycles_test.clj b/test/unheard/cycles_test.clj index 242b6fc..e2e1889 100644 --- a/test/unheard/cycles_test.clj +++ b/test/unheard/cycles_test.clj @@ -1,6 +1,6 @@ (ns unheard.cycles-test (:require [clojure.test :refer [deftest is testing]] - [unheard.cycles :refer [l f p rate elongate unfold]])) + [unheard.cycles :refer [l f p rate elongate rep unfold]])) (deftest unfold-tests (testing "single scalar" @@ -157,4 +157,66 @@ (testing "rate with elongate inside list" (is (= [[0 1/4 :a] [1/4 3/8 :b] [3/8 1/2 :c] [1/2 3/4 :a] [3/4 7/8 :b] [7/8 1 :c]] - (unfold 1 (rate 2 (l (elongate 2 :a) :b :c))))))) + (unfold 1 (rate 2 (l (elongate 2 :a) :b :c)))))) + + (testing "rep 2 - element repeats twice" + (is (= [[0 1/2 :a] [1/2 1 :a]] + (unfold 1 (rep 2 :a))))) + + (testing "rep 3 - element repeats three times" + (is (= [[0 1/3 :a] [1/3 2/3 :a] [2/3 1 :a]] + (unfold 1 (rep 3 :a))))) + + (testing "rep in list - subdivides its time slot" + (is (= [[0 1/6 :a] [1/6 1/3 :a] [1/3 1/2 :a] + [1/2 1 :b]] + (unfold 1 (l (rep 3 :a) :b))))) + + (testing "rep with three elements in list" + (is (= [[0 1/6 :x] [1/6 1/3 :x] + [1/3 2/3 :y] + [2/3 1 :z]] + (unfold 1 (l (rep 2 :x) :y :z))))) + + (testing "multiple reps in list" + (is (= [[0 1/6 :a] [1/6 1/3 :a] + [1/3 1/2 :b] [1/2 2/3 :b] + [2/3 1 :c]] + (unfold 1 (l (rep 2 :a) (rep 2 :b) :c))))) + + (testing "rep with fork" + (is (= [[0 1/2 :a] [1/2 1 :a] + [1 2 :b]] + (unfold 1 (f (rep 2 :a) :b))))) + + (testing "rep inside parallel" + (is (= [[0 1/2 :a] [1/2 1 :a] + [0 1 :b]] + (unfold 1 (p (rep 2 :a) :b))))) + + (testing "rep with rate" + (is (= [[0 1/4 :a] [1/4 1/2 :a] + [1/2 3/4 :a] [3/4 1 :a]] + (unfold 1 (rep 2 (rate 2 :a)))))) + + (testing "rate with rep inside" + (is (= [[0 1/4 :a] [1/4 1/2 :a] + [1/2 3/4 :a] [3/4 1 :a]] + (unfold 1 (rate 2 (rep 2 :a)))))) + + (testing "rep in list without elongate" + (is (= [[0 1/6 :a] [1/6 1/3 :a] + [1/3 2/3 :b] + [2/3 1 :c]] + (unfold 1 (l (rep 2 :a) :b :c))))) + + (testing "elongate and rep together" + (is (= [[0 1/6 :a] [1/6 1/3 :a] + [1/3 2/3 :b] + [2/3 1 :c]] + (unfold 1 (l (rep 2 (elongate 2 :a)) :b :c))))) + + (testing "rep of list" + (is (= [[0 1/4 :a] [1/4 1/2 :b] + [1/2 3/4 :a] [3/4 1 :b]] + (unfold 1 (rep 2 (l :a :b))))))) |
