summaryrefslogtreecommitdiff
path: root/src/unheard/cycles.clj
diff options
context:
space:
mode:
authorJake Zerrer <him@jakezerrer.com>2025-11-26 15:10:20 -0500
committerJake Zerrer <him@jakezerrer.com>2025-12-01 16:02:53 -0500
commitbc500bb5dc9f32b7b50ce39c5a98df13322e4167 (patch)
treec123662150db8dd6752156e0c22d2f6859fa4047 /src/unheard/cycles.clj
parent322b66627fd619c2ce0f2a35eae043e9304ee8bc (diff)
Create `paste` operator
Diffstat (limited to 'src/unheard/cycles.clj')
-rw-r--r--src/unheard/cycles.clj38
1 files changed, 38 insertions, 0 deletions
diff --git a/src/unheard/cycles.clj b/src/unheard/cycles.clj
index 29cad91..3cbfb97 100644
--- a/src/unheard/cycles.clj
+++ b/src/unheard/cycles.clj
@@ -92,6 +92,44 @@
(defn scalar? [x]
(not (and (map? x) (:type x))))
+(defn paste
+ "Paste operator: replaces scalar values in a template pattern with provided values.
+
+ Takes a template pattern and a sequence of replacement values. Each scalar
+ in the template (in depth-first order) is replaced by the corresponding value
+ from the replacement sequence. If a replacement value is nil, the original
+ scalar is preserved.
+
+ This allows separating rhythmic structure from content. Any values can be used
+ as placeholders in the template (commonly keywords like :_ or numbers).
+
+ Examples:
+ (paste (f :_ (f :_ :_) :_ :_) :c :e :g :b :d)
+ => (f :c (f :e :g) :b :d)
+
+ (paste (f :_ (f :_ :_) :_ :_) :c :e nil :b :d)
+ => (f :c (f :e :_) :b :d)
+
+ (paste (l 1 2 1) :a :b :c)
+ => (l :a :b :c)"
+ [template & values]
+ (let [values-seq (atom (seq values))]
+ (letfn [(replace-scalars [node]
+ (if (scalar? node)
+ (let [replacement (first @values-seq)]
+ (swap! values-seq rest)
+ (if (nil? replacement)
+ node
+ replacement))
+ ;; Non-scalar: check if :v is a vector (l, f, p) or single value (rate, elongate, rep)
+ (let [v (:v node)]
+ (if (vector? v)
+ ;; Combinators with multiple children
+ (assoc node :v (mapv replace-scalars v))
+ ;; Modifiers with single child
+ (assoc node :v (replace-scalars v))))))]
+ (replace-scalars template))))
+
(defn get-weight
"Returns the weight of a node. Elongated nodes have their specified weight,
all other nodes have a default weight of 1."