summaryrefslogtreecommitdiff
path: root/src/unheard/cycles.clj
blob: 513b090db43abdd9037cafc6329ed7f21a65bb65 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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)))))