From 06a4d14160acb7b8699760d1aaf1167fa3991931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Fri, 31 Jan 2025 16:25:11 -0600 Subject: [PATCH 1/7] Add `:cycle` to current-event --- src/time_time/sequencing_3.cljc | 16 +++- test/time_time/sequencing_3_test.clj | 112 ++++++++++++++++++++++++--- 2 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/time_time/sequencing_3.cljc b/src/time_time/sequencing_3.cljc index 9148813..6077488 100644 --- a/src/time_time/sequencing_3.cljc +++ b/src/time_time/sequencing_3.cljc @@ -15,20 +15,27 @@ "Gets a config and returns a `voice-atom` that will start playing, if `playing?` is true (default)" [durs on-event & - {:keys [ratio tempo loop? start-index start-time elapsed playing? - before-update on-schedule extra-data on-startup-error] + {:keys [ratio tempo loop? start-index start-time elapsed elapsed-dur playing? + before-update on-schedule extra-data on-startup-error durs-len] :or {ratio 1 tempo 60 loop? false start-index 0 start-time (now) elapsed 0 + elapsed-dur 0 ;; TODO investigate if this is compatible with `elapsed`, if so, substitute playing? true before-update identity ;; receives the voice data before it is used to `reset!` the `voice-atom` on-schedule (fn [voice event-schedule] event-schedule) extra-data {}}}] (let [voice (atom (merge extra-data {:durs durs + :elapsed-dur elapsed-dur + :cycle-len (cond + (and (fn? durs) (not durs-len)) (do (timbre/warn "`durs` is a function but no `durs-len` has been provided, defaulting to 1") + 1) + durs-len durs-len + :else (apply + durs)) :on-event on-event :ratio ratio :tempo tempo @@ -72,11 +79,12 @@ {:dur dur :event-dur event-dur})) (defn calculate-next-voice-state - [{:keys [index elapsed-ms] :as voice}] + [{:keys [index elapsed-ms cycle-len elapsed-dur] :as voice}] (let [{:keys [dur event-dur]} (get-current-dur-data voice) updated-state {:index (inc index) + :elapsed-dur (+ elapsed-dur dur) :elapsed-ms (+ elapsed-ms event-dur) - :current-event {:dur-ms event-dur :dur dur}}] + :current-event {:dur-ms event-dur :dur dur :cycle (quot elapsed-dur cycle-len)}}] (merge voice updated-state))) (defn play-event? diff --git a/test/time_time/sequencing_3_test.clj b/test/time_time/sequencing_3_test.clj index 8ca5fd9..4c42598 100644 --- a/test/time_time/sequencing_3_test.clj +++ b/test/time_time/sequencing_3_test.clj @@ -12,92 +12,184 @@ (deftest calculate-next-voice-state-test (testing "Increases the `index` by one, updates the `elapsed` value and updates (or sets) the `current-event-dur`" (is (= {:durs [1 2 1], + :cycle-len 4 :index 1, :tempo 60, :loop? false, :started-at 0, :elapsed-ms 1000, + :elapsed-dur 1 :ratio 1, - :current-event {:dur-ms 1000, :dur 1}} + :current-event {:dur-ms 1000, :dur 1 :cycle 0}} (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 :index 0 :tempo 60 :loop? false :started-at 0 :elapsed-ms 0 + :elapsed-dur 0 :ratio 1}))) (is (= {:durs [1 2 1], + :cycle-len 4 :index 2, :tempo 60, :loop? false, :started-at 0, :elapsed-ms 3000, + :elapsed-dur 3 :ratio 1, - :current-event {:dur-ms 2000, :dur 2}} + :current-event {:dur-ms 2000, :dur 2 :cycle 0}} (calculate-next-voice-state {:durs [1 2 1], + :cycle-len 4 :index 1, :tempo 60, :loop? false, :started-at 0, :elapsed-ms 1000, + :elapsed-dur 1 + :ratio 1}))) + (is (= {:durs [1 2 1], + :cycle-len 4 + :index 4, + :tempo 60, + :loop? false, + :started-at 0, + :elapsed-ms 5000, + :elapsed-dur 5 + :ratio 1, + :current-event {:dur-ms 1000, :dur 1 :cycle 1}} + (calculate-next-voice-state {:durs [1 2 1], + :cycle-len 4 + :index 3, + :tempo 60, + :loop? false, + :started-at 0, + :elapsed-ms 4000, + :elapsed-dur 4 :ratio 1})))) + + (testing "`cycle` calculation" + (is (= [nil ;; the first state doesn't have a `current-event` so value is `nil` + {:dur-ms 1000, :dur 1, :cycle 0} + {:dur-ms 2000, :dur 2, :cycle 0} + {:dur-ms 1000, :dur 1, :cycle 0} + {:dur-ms 1000, :dur 1, :cycle 1} + {:dur-ms 2000, :dur 2, :cycle 1} + {:dur-ms 1000, :dur 1, :cycle 1} + {:dur-ms 1000, :dur 1, :cycle 2} + {:dur-ms 2000, :dur 2, :cycle 2} + {:dur-ms 1000, :dur 1, :cycle 2} + {:dur-ms 1000, :dur 1, :cycle 3} + {:dur-ms 2000, :dur 2, :cycle 3} + {:dur-ms 1000, :dur 1, :cycle 3}] + (->> (range 12) + (reduce (fn [events _] (conj events (calculate-next-voice-state (last events)))) + [{:durs [1 2 1] + :cycle-len 4 + :index 0 + :tempo 60 + :loop? false + :started-at 0 + :elapsed-ms 0 + :elapsed-dur 0 + :ratio 1}]) + (map :current-event)))) + (testing "`cycle-len` is different from the `durs` sum" + (is (= [nil + {:dur-ms 1000, :dur 1, :cycle 0} + {:dur-ms 2000, :dur 2, :cycle 0} + {:dur-ms 1000, :dur 1, :cycle 1} + {:dur-ms 1000, :dur 1, :cycle 1} + {:dur-ms 2000, :dur 2, :cycle 1} + {:dur-ms 1000, :dur 1, :cycle 2} + {:dur-ms 1000, :dur 1, :cycle 2} + {:dur-ms 2000, :dur 2, :cycle 3} + {:dur-ms 1000, :dur 1, :cycle 3} + {:dur-ms 1000, :dur 1, :cycle 4} + {:dur-ms 2000, :dur 2, :cycle 4} + {:dur-ms 1000, :dur 1, :cycle 5}] + (->> (range 12) + (reduce (fn [events _] (conj events (calculate-next-voice-state (last events)))) + [{:durs [1 2 1] + :cycle-len 3 + :index 0 + :tempo 60 + :loop? false + :started-at 0 + :elapsed-ms 0 + :elapsed-dur 0 + :ratio 1}]) + (map :current-event)))))) (testing "`current-event-dur` is related to tempo" - (is (= {:dur-ms 500N, :dur 1} + (is (= {:dur-ms 500N, :dur 1 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 :index 0 :tempo 120 :loop? false :started-at 0 :elapsed-ms 0 + :elapsed-dur 0 :ratio 1})))) - (is (= {:dur-ms 1000N, :dur 2} + (is (= {:dur-ms 1000N, :dur 2 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 :index 1 :tempo 120 :loop? false :started-at 0 :elapsed-ms 0 + :elapsed-dur 1 :ratio 1})))) - (is (= {:dur-ms 4000, :dur 2} + (is (= {:dur-ms 4000, :dur 2 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 :index 1 :tempo 30 :loop? false :started-at 0 :elapsed-ms 0 + :elapsed-dur 3 :ratio 1}))))) (testing "Different ratio" - (is (= {:dur-ms 250N, :dur 1/2} + (is (= {:dur-ms 250N, :dur 1/2 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 :index 0 :tempo 120 :loop? false :started-at 0 :elapsed-ms 0 + :elapsed-dur 0 :ratio 1/2})))) - (is (= {:dur-ms 500N, :dur 1} + (is (= {:dur-ms 500N, :dur 1 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 :index 1 :tempo 120 :loop? false :started-at 0 :elapsed-ms 0 + :elapsed-dur 1 :ratio 1/2}))))) (testing "`:durs` can be a function" - (is (= {:dur-ms 250N, :dur 1/2} + (is (= {:dur-ms 250N, :dur 1/2 :cycle 0} (:current-event (calculate-next-voice-state {:durs (fn [{:keys [index ratio] :as _voice}] (* ratio (wrap-at index [1 2 1]))) + :cycle-len 2 :index 0 :tempo 120 :loop? false :started-at 0 :elapsed-ms 0 + :elapsed-dur 0 :ratio 1/2})))))) (deftest play-event?-test @@ -117,11 +209,13 @@ (deftest schedule!-test (let [base-voice {:durs [1 2 1 2 1 1] + :cycle-len (apply + [1 2 1 2 1 1]) :index 0 :tempo 6000 ;; NOTE a dur of `1` lasts `10N`ms :loop? false :started-at 0 :elapsed-ms 0 + :elapsed-dur 0 :ratio 1 :playing? true} default-continue (fn [ev _] (< (ev :index) @@ -168,7 +262,6 @@ (let [total-events 5 {:keys [event-chan result-chan]} (async-events-tester (fn [ev _] - (println "continue") (< (ev :index) total-events))) v (atom {}) _ (reset! v (assoc base-voice @@ -177,7 +270,6 @@ (* ratio (wrap-at index [1 2 1 2 1 1]))) :started-at (+ 1 (now)) :on-event (fn [{:keys [data _voice _dur _dur-ms]}] - (println (keys data)) (a/>!! event-chan (select-keys data [:index :dur :dur-ms])) From 3ea1ed0dc75cfeb41bc519a34406ea57926fdf4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Fri, 31 Jan 2025 16:28:34 -0600 Subject: [PATCH 2/7] Add `:index` to current-event --- src/time_time/sequencing_3.cljc | 5 ++- test/time_time/sequencing_3_test.clj | 66 ++++++++++++++-------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/src/time_time/sequencing_3.cljc b/src/time_time/sequencing_3.cljc index 6077488..3088cd9 100644 --- a/src/time_time/sequencing_3.cljc +++ b/src/time_time/sequencing_3.cljc @@ -84,7 +84,10 @@ updated-state {:index (inc index) :elapsed-dur (+ elapsed-dur dur) :elapsed-ms (+ elapsed-ms event-dur) - :current-event {:dur-ms event-dur :dur dur :cycle (quot elapsed-dur cycle-len)}}] + :current-event {:dur-ms event-dur + :dur dur + :index index + :cycle (quot elapsed-dur cycle-len)}}] (merge voice updated-state))) (defn play-event? diff --git a/test/time_time/sequencing_3_test.clj b/test/time_time/sequencing_3_test.clj index 4c42598..62e0fea 100644 --- a/test/time_time/sequencing_3_test.clj +++ b/test/time_time/sequencing_3_test.clj @@ -20,7 +20,7 @@ :elapsed-ms 1000, :elapsed-dur 1 :ratio 1, - :current-event {:dur-ms 1000, :dur 1 :cycle 0}} + :current-event {:dur-ms 1000, :dur 1 :index 0 :cycle 0}} (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 :index 0 @@ -39,7 +39,7 @@ :elapsed-ms 3000, :elapsed-dur 3 :ratio 1, - :current-event {:dur-ms 2000, :dur 2 :cycle 0}} + :current-event {:dur-ms 2000, :dur 2 :index 1 :cycle 0}} (calculate-next-voice-state {:durs [1 2 1], :cycle-len 4 :index 1, @@ -58,7 +58,7 @@ :elapsed-ms 5000, :elapsed-dur 5 :ratio 1, - :current-event {:dur-ms 1000, :dur 1 :cycle 1}} + :current-event {:dur-ms 1000, :dur 1 :index 3 :cycle 1}} (calculate-next-voice-state {:durs [1 2 1], :cycle-len 4 :index 3, @@ -71,18 +71,18 @@ (testing "`cycle` calculation" (is (= [nil ;; the first state doesn't have a `current-event` so value is `nil` - {:dur-ms 1000, :dur 1, :cycle 0} - {:dur-ms 2000, :dur 2, :cycle 0} - {:dur-ms 1000, :dur 1, :cycle 0} - {:dur-ms 1000, :dur 1, :cycle 1} - {:dur-ms 2000, :dur 2, :cycle 1} - {:dur-ms 1000, :dur 1, :cycle 1} - {:dur-ms 1000, :dur 1, :cycle 2} - {:dur-ms 2000, :dur 2, :cycle 2} - {:dur-ms 1000, :dur 1, :cycle 2} - {:dur-ms 1000, :dur 1, :cycle 3} - {:dur-ms 2000, :dur 2, :cycle 3} - {:dur-ms 1000, :dur 1, :cycle 3}] + {:dur-ms 1000, :dur 1, :index 0, :cycle 0} + {:dur-ms 2000, :dur 2, :index 1, :cycle 0} + {:dur-ms 1000, :dur 1, :index 2, :cycle 0} + {:dur-ms 1000, :dur 1, :index 3, :cycle 1} + {:dur-ms 2000, :dur 2, :index 4, :cycle 1} + {:dur-ms 1000, :dur 1, :index 5, :cycle 1} + {:dur-ms 1000, :dur 1, :index 6, :cycle 2} + {:dur-ms 2000, :dur 2, :index 7, :cycle 2} + {:dur-ms 1000, :dur 1, :index 8, :cycle 2} + {:dur-ms 1000, :dur 1, :index 9, :cycle 3} + {:dur-ms 2000, :dur 2, :index 10, :cycle 3} + {:dur-ms 1000, :dur 1, :index 11, :cycle 3}] (->> (range 12) (reduce (fn [events _] (conj events (calculate-next-voice-state (last events)))) [{:durs [1 2 1] @@ -97,18 +97,18 @@ (map :current-event)))) (testing "`cycle-len` is different from the `durs` sum" (is (= [nil - {:dur-ms 1000, :dur 1, :cycle 0} - {:dur-ms 2000, :dur 2, :cycle 0} - {:dur-ms 1000, :dur 1, :cycle 1} - {:dur-ms 1000, :dur 1, :cycle 1} - {:dur-ms 2000, :dur 2, :cycle 1} - {:dur-ms 1000, :dur 1, :cycle 2} - {:dur-ms 1000, :dur 1, :cycle 2} - {:dur-ms 2000, :dur 2, :cycle 3} - {:dur-ms 1000, :dur 1, :cycle 3} - {:dur-ms 1000, :dur 1, :cycle 4} - {:dur-ms 2000, :dur 2, :cycle 4} - {:dur-ms 1000, :dur 1, :cycle 5}] + {:dur-ms 1000, :dur 1, :index 0, :cycle 0} + {:dur-ms 2000, :dur 2, :index 1, :cycle 0} + {:dur-ms 1000, :dur 1, :index 2, :cycle 1} + {:dur-ms 1000, :dur 1, :index 3, :cycle 1} + {:dur-ms 2000, :dur 2, :index 4, :cycle 1} + {:dur-ms 1000, :dur 1, :index 5, :cycle 2} + {:dur-ms 1000, :dur 1, :index 6, :cycle 2} + {:dur-ms 2000, :dur 2, :index 7, :cycle 3} + {:dur-ms 1000, :dur 1, :index 8, :cycle 3} + {:dur-ms 1000, :dur 1, :index 9, :cycle 4} + {:dur-ms 2000, :dur 2, :index 10, :cycle 4} + {:dur-ms 1000, :dur 1, :index 11, :cycle 5}] (->> (range 12) (reduce (fn [events _] (conj events (calculate-next-voice-state (last events)))) [{:durs [1 2 1] @@ -122,7 +122,7 @@ :ratio 1}]) (map :current-event)))))) (testing "`current-event-dur` is related to tempo" - (is (= {:dur-ms 500N, :dur 1 :cycle 0} + (is (= {:dur-ms 500N, :dur 1 :index 0 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 @@ -133,7 +133,7 @@ :elapsed-ms 0 :elapsed-dur 0 :ratio 1})))) - (is (= {:dur-ms 1000N, :dur 2 :cycle 0} + (is (= {:dur-ms 1000N, :dur 2 :index 1 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 @@ -144,7 +144,7 @@ :elapsed-ms 0 :elapsed-dur 1 :ratio 1})))) - (is (= {:dur-ms 4000, :dur 2 :cycle 0} + (is (= {:dur-ms 4000, :dur 2 :index 1 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 @@ -156,7 +156,7 @@ :elapsed-dur 3 :ratio 1}))))) (testing "Different ratio" - (is (= {:dur-ms 250N, :dur 1/2 :cycle 0} + (is (= {:dur-ms 250N, :dur 1/2 :index 0 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 @@ -167,7 +167,7 @@ :elapsed-ms 0 :elapsed-dur 0 :ratio 1/2})))) - (is (= {:dur-ms 500N, :dur 1 :cycle 0} + (is (= {:dur-ms 500N, :dur 1 :index 1 :cycle 0} (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 @@ -179,7 +179,7 @@ :elapsed-dur 1 :ratio 1/2}))))) (testing "`:durs` can be a function" - (is (= {:dur-ms 250N, :dur 1/2 :cycle 0} + (is (= {:dur-ms 250N, :dur 1/2 :index 0 :cycle 0} (:current-event (calculate-next-voice-state {:durs (fn [{:keys [index ratio] :as _voice}] (* ratio (wrap-at index [1 2 1]))) From 0a9ab82c4a80c2a09c20fcdd8cf1f3836a4908c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Fri, 31 Jan 2025 17:13:44 -0600 Subject: [PATCH 3/7] Ensure cycle data is available in on-event fn --- src/time_time/dynacan/players/gen_poly.cljc | 15 ++++--- src/time_time/sequencing_3.cljc | 47 +++++++++++++++------ test/time_time/sequencing_3_test.clj | 17 ++++++++ 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/src/time_time/dynacan/players/gen_poly.cljc b/src/time_time/dynacan/players/gen_poly.cljc index 3a4b4cb..4d20293 100644 --- a/src/time_time/dynacan/players/gen_poly.cljc +++ b/src/time_time/dynacan/players/gen_poly.cljc @@ -75,8 +75,9 @@ `at-index` (alias `at-i`,function that get a value in a collection based on index, it wraps with `mod`) can take an `offset` as the first argument and the collection as the second, and `offset` can be a function or a number" [& forms] - `(fn [~'{{:keys [index dur dur-ms dur-s] :as data} :data}] + `(fn [~'{{:keys [index cycle cycle-delta dur dur-ms dur-s] :as data} :data}] (let [~'i ~'index + ~'delta ~'cycle-delta ~'at-index (partial at-index* ~'index) ~'at-i ~'at-index] ~@forms))) @@ -125,9 +126,10 @@ :as config}] (let [existing-voice? (and (@refrains id) (-> @refrains id deref :playing?)) refrains* (cond - existing-voice? (update-refrain id #(assoc % - :update config - :refrain/config config)) + existing-voice? (let [config* (merge config (s/get-cycle-data durs config))] + (update-refrain id #(assoc % + :update config* + :refrain/config config*))) (and (@refrains ref) (-> @refrains ref deref :playing?)) (let [voice (add-to! (-> @refrains ref deref) distance on-event config)] @@ -150,10 +152,9 @@ ;; A function (ref-rain :id :hola - :durs (fn [{:keys [index]}] - 1) + :durs [1 2] :on-event (on-event - (println "holas" (at-index sec)) + (println "holas" cycle) ;; throw at some point of the execution #_(throw (ex-info "ups" {})))) diff --git a/src/time_time/sequencing_3.cljc b/src/time_time/sequencing_3.cljc index 3088cd9..7f3cb3d 100644 --- a/src/time_time/sequencing_3.cljc +++ b/src/time_time/sequencing_3.cljc @@ -10,12 +10,27 @@ #?(:clj [taoensso.timbre :as timbre] :cljs [taoensso.timbre :as timbre :include-macros true]))) -(declare schedule! get-current-dur-data) +(declare schedule! get-current-dur-data cycle-delta) + +(defn get-cycle-data + [durs + {:keys [elapsed-dur durs-len] :as _config + :or {elapsed-dur 0}}] + (let [cycle-len (cond + (and (fn? durs) (not durs-len)) (do (timbre/warn "`durs` is a function but no `durs-len` has been provided, defaulting to 1") + 1) + durs-len durs-len + :else (apply + durs))] + {:cycle-len cycle-len + :cycle (quot elapsed-dur cycle-len) + :cycle-delta (cycle-delta elapsed-dur cycle-len)})) + (defn play! "Gets a config and returns a `voice-atom` that will start playing, if `playing?` is true (default)" [durs on-event & - {:keys [ratio tempo loop? start-index start-time elapsed elapsed-dur playing? + {:as config + :keys [ratio tempo loop? start-index start-time elapsed elapsed-dur playing? before-update on-schedule extra-data on-startup-error durs-len] :or {ratio 1 tempo 60 @@ -28,14 +43,10 @@ before-update identity ;; receives the voice data before it is used to `reset!` the `voice-atom` on-schedule (fn [voice event-schedule] event-schedule) extra-data {}}}] - (let [voice (atom (merge extra-data + (let [cycle-data (get-cycle-data durs config) + voice (atom (merge extra-data {:durs durs :elapsed-dur elapsed-dur - :cycle-len (cond - (and (fn? durs) (not durs-len)) (do (timbre/warn "`durs` is a function but no `durs-len` has been provided, defaulting to 1") - 1) - durs-len durs-len - :else (apply + durs)) :on-event on-event :ratio ratio :tempo tempo @@ -52,7 +63,8 @@ :playing? playing? :before-update before-update :on-schedule on-schedule - :on-startup-error on-startup-error}))] + :on-startup-error on-startup-error} + cycle-data))] (schedule! voice) voice)) @@ -78,16 +90,23 @@ event-dur (dur->ms dur tempo)] {:dur dur :event-dur event-dur})) +(defn cycle-delta [elapsed-dur cycle-len] + (/ (mod elapsed-dur cycle-len) + cycle-len)) + (defn calculate-next-voice-state - [{:keys [index elapsed-ms cycle-len elapsed-dur] :as voice}] + [{:keys [index elapsed-ms cycle-len cycle elapsed-dur] :as voice}] (let [{:keys [dur event-dur]} (get-current-dur-data voice) + elapsed-dur* (+ elapsed-dur dur) updated-state {:index (inc index) - :elapsed-dur (+ elapsed-dur dur) + :cycle (quot elapsed-dur* cycle-len) + :cycle-delta (cycle-delta elapsed-dur* cycle-len) + :elapsed-dur elapsed-dur* :elapsed-ms (+ elapsed-ms event-dur) :current-event {:dur-ms event-dur :dur dur :index index - :cycle (quot elapsed-dur cycle-len)}}] + :cycle cycle}}] (merge voice updated-state))) (defn play-event? @@ -112,6 +131,10 @@ ;; TODO calculate-next-voice-state should only return the fields below (select-keys voice-update [:index + :cycle + :cycle-delta + :cycle-len + :elapsed-dur :elapsed-ms :current-event :prev-on-event diff --git a/test/time_time/sequencing_3_test.clj b/test/time_time/sequencing_3_test.clj index 62e0fea..0b9a79a 100644 --- a/test/time_time/sequencing_3_test.clj +++ b/test/time_time/sequencing_3_test.clj @@ -13,6 +13,8 @@ (testing "Increases the `index` by one, updates the `elapsed` value and updates (or sets) the `current-event-dur`" (is (= {:durs [1 2 1], :cycle-len 4 + :cycle 0 + :cycle-delta 1/4 :index 1, :tempo 60, :loop? false, @@ -23,6 +25,7 @@ :current-event {:dur-ms 1000, :dur 1 :index 0 :cycle 0}} (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 + :cycle 0 :index 0 :tempo 60 :loop? false @@ -32,6 +35,8 @@ :ratio 1}))) (is (= {:durs [1 2 1], :cycle-len 4 + :cycle 0 + :cycle-delta 3/4 :index 2, :tempo 60, :loop? false, @@ -42,6 +47,7 @@ :current-event {:dur-ms 2000, :dur 2 :index 1 :cycle 0}} (calculate-next-voice-state {:durs [1 2 1], :cycle-len 4 + :cycle 0 :index 1, :tempo 60, :loop? false, @@ -51,6 +57,8 @@ :ratio 1}))) (is (= {:durs [1 2 1], :cycle-len 4 + :cycle 1 + :cycle-delta 1/4 :index 4, :tempo 60, :loop? false, @@ -61,6 +69,7 @@ :current-event {:dur-ms 1000, :dur 1 :index 3 :cycle 1}} (calculate-next-voice-state {:durs [1 2 1], :cycle-len 4 + :cycle 1 :index 3, :tempo 60, :loop? false, @@ -87,6 +96,7 @@ (reduce (fn [events _] (conj events (calculate-next-voice-state (last events)))) [{:durs [1 2 1] :cycle-len 4 + :cycle 0 :index 0 :tempo 60 :loop? false @@ -113,6 +123,7 @@ (reduce (fn [events _] (conj events (calculate-next-voice-state (last events)))) [{:durs [1 2 1] :cycle-len 3 + :cycle 0 :index 0 :tempo 60 :loop? false @@ -126,6 +137,7 @@ (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 + :cycle 0 :index 0 :tempo 120 :loop? false @@ -137,6 +149,7 @@ (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 + :cycle 0 :index 1 :tempo 120 :loop? false @@ -148,6 +161,7 @@ (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 + :cycle 0 :index 1 :tempo 30 :loop? false @@ -160,6 +174,7 @@ (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 + :cycle 0 :index 0 :tempo 120 :loop? false @@ -171,6 +186,7 @@ (:current-event (calculate-next-voice-state {:durs [1 2 1] :cycle-len 4 + :cycle 0 :index 1 :tempo 120 :loop? false @@ -184,6 +200,7 @@ (calculate-next-voice-state {:durs (fn [{:keys [index ratio] :as _voice}] (* ratio (wrap-at index [1 2 1]))) :cycle-len 2 + :cycle 0 :index 0 :tempo 120 :loop? false From 0dfb9445a09ba6aa06170918eb7a2fcc0200b652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Thu, 17 Apr 2025 22:52:14 -0600 Subject: [PATCH 4/7] Add sequencing-4 and refrains.v2 --- src/time_time/dynacan/players/refrain/v2.cljc | 203 +++++++++++++ src/time_time/sequencing_4.cljc | 266 ++++++++++++++++++ 2 files changed, 469 insertions(+) create mode 100644 src/time_time/dynacan/players/refrain/v2.cljc create mode 100644 src/time_time/sequencing_4.cljc diff --git a/src/time_time/dynacan/players/refrain/v2.cljc b/src/time_time/dynacan/players/refrain/v2.cljc new file mode 100644 index 0000000..71391cd --- /dev/null +++ b/src/time_time/dynacan/players/refrain/v2.cljc @@ -0,0 +1,203 @@ +(ns time-time.dynacan.players.refrain.v2 + "Uses sequencing-4" + (:require + [time-time.dynacan.core :refer [find-relative-voice-first-event]] + [time-time.sequencing-4 :as s4] + [time-time.standard :refer [wrap-at]])) + +;; API +(declare before-update update-refrain maybe-run-on-stop-fn active-refrains add-to!) + +(defonce refrains (atom {})) + +(defn reset [] (reset! refrains {})) + +(defn stop + ([] + (doseq [id (keys @refrains)] + (update-refrain id :playing? false) + (maybe-run-on-stop-fn (get @refrains id))) + (reset)) + ([id] + (cond (and (@refrains id) (:playing? (deref (@refrains id)))) + (do (update-refrain id :playing? false) + (maybe-run-on-stop-fn (get @refrains id))) + + (@refrains id) + (println "refrain already stopped") + + :else (println "Could not find refrain with id: " id)))) + +(defn ref-rain + [& {:keys [id durs on-event loop? ref distance] + :or {loop? true + distance 1} + :as config}] + (let [existing-voice-config (@refrains id) + existing-voice? (and existing-voice-config (:playing? @existing-voice-config)) + refrains* (cond + existing-voice? (let [config* (merge config (s4/get-cycle-data (merge @existing-voice-config config)))] + (update-refrain id #(assoc % + :update config* + :refrain/config config*))) + + (and (@refrains ref) (-> @refrains ref deref :playing?)) + (let [voice (add-to! (-> @refrains ref deref) distance on-event config)] + (swap! voice assoc :refrain/config config) + (swap! refrains assoc id voice)) + + :else + (let [voice (s4/play! {:durs durs + :on-event on-event + :loop? loop? + :tempo (config :tempo 60) + :ratio (config :ratio 1) + :on-startup-error (fn [] (stop id))})] + (swap! voice assoc :refrain/config config) + (swap! refrains assoc id voice)))] + (active-refrains refrains*))) + +(comment + (require '[overtone.music.time :refer [now]]) + (@refrains :hola) + (ref-rain :id :hola + :durs [1 2 3] + :on-event (on-event + (println "hola") + (when (cyi? 3) (println "First" dur)))) + (ref-rain :id :adios + :ref :hola + :durs [2] + :on-event (on-event (println "adios" (now)))) + + (stop)) + +(defn at-index* + "`offset` can be a function or a number" + ([index coll] (wrap-at index coll)) + ([index offset coll] (if (fn? offset) + (wrap-at (offset index) coll) + (wrap-at (+ offset index) coll)))) + +(defn cycle-0-index? + ([index cycle-0-index] + (= index cycle-0-index)) + ;; NOTE in most cases this will work, when the `cycle-len` is derived from the `durs` sum; however in cases where this is not, the return value will not be correct. + ([index cycle-0-index offset durs cycle-len] + (= index (+ (mod offset (if-not (fn? durs) (count durs) cycle-len)) + cycle-0-index)))) + +(defmacro cyi? + "To be used inside an on-event. If called without an argument, it will return true when the cycle is begining. + + With an `offset` argument it will attempt to calculate if the current index is an `offset` away from the start of the cycle. + Using an `offset` is prone to returning false positives if the `cycle-len` has not been derived from the sum of `durs`." + ([] `(~'cycle-0-index? ~'index ~'cycle-0-index)) + ;; NOTE in most cases this will work, when the `cycle-len` is derived from the `durs` sum; however in cases where this is not, the return value will not be correct. + ([offset] `(~'cycle-0-index? ~'index ~'cycle-0-index ~offset ~'durs ~'cycle-len))) + +(defmacro on-event + "Provides + `index` (alias `i`), + `dur` (original duration), + `dur-s` (duration in seconds), + `dur-ms` (duration in milliseconds) and + `at-index` (alias `at-i`,function that get a value in a collection based on index, it wraps with `mod`) + can take an `offset` as the first argument and the collection as the second, and `offset` can be a function or a number" + [& forms] + `(fn [~'{{:keys [index + dur + durs + dur-ms + dur-s + cycle + cycle-len + cycle-delta + cycle-0-index + cycle-delta-index] :as event} :event}] + (let [~'data ~'event + ~'i ~'index + ~'cy ~'cycle + ~'cy0i ~'cycle-0-index + ;; ~'cy0? (= ~'index ~'cycle-0-index) + ~'cyd ~'cycle-delta + ~'cydi ~'cycle-delta-index + ~'at-index (partial at-index* ~'index) + ~'at-i ~'at-index] + ~@forms))) + +(comment + (macroexpand-1 '(on-event (at-index [1 2 3]))) + ((on-event (at-index #(- 1 %) [1 2 3])) {:data {:index 1}}) + ((on-event (at-i [1 2 3])) {:data {:index 1}}) + (on-event (at-index [1 2 3]) + (at-index [1 2 3]))) + +(defmacro when-mod + "To be used within `on-event` as it assumes the existence of `index` in the context of the function" + [modulo index-set f-call] + `(when (~index-set (mod ~'index ~modulo)) + ~f-call)) + +(defn active-refrains [refrains] + (->> refrains + (filter (comp :playing? deref second)) + (map first))) + +(defn add-to! [ref-voice events-from-cp on-event + {:keys [durs ratio loop? cp] + :or {durs (ref-voice :durs) + ratio (ref-voice :ratio) + loop? (ref-voice :loop?)} + :as config}] + ;; TODO verify if ref-voice is playing? + (let [start-time (+ (ref-voice :elapsed-ms) (ref-voice :started-at)) + tempo (ref-voice :tempo) + {:keys [index elapsed cp cp-elapsed interval-from-cp events-from-cp]} + (find-relative-voice-first-event events-from-cp ;; TODO should probably return the start time of the voice + ref-voice + {:durs durs :cp cp :ratio ratio :loop? loop?})] + #_(log/debug "adding voice... cp-at ref-voice index" + (ref-voice :index) + (+ events-from-cp (ref-voice :index))) + (s4/play! {:durs durs + :on-event on-event + :ratio ratio + :start-index index + :elapsed elapsed + :tempo tempo + :start-time start-time + :loop? loop? + :before-update before-update + :extra-data {:cp cp + :cp-at cp-elapsed + :interval-from-cp interval-from-cp + :events-from-cp events-from-cp}}))) + +(defn before-update + [{:as data {dur :dur} :current-event}] + (-> data + (update :events-from-cp dec) + (update :interval-from-cp - dur))) + +;; TODO verificar cómo funciona el cp en `add-to`... cómo se estan leyendo las duraciones... tal vez rotación +;; compilar en diversos momentos +;; en diversos loops + +;; hacer pruebas (combinaciones de loops, corrección del cp) +;; pensar en tiempo de reposo (remainder) entre voces (que sea opcional) +;; pensar otros modos de control de las densidades* (quizá otra función aparte) +;; comenzar a pensar en la sintaxis + +(defn backup-on-event [config] + (assoc config :prev-on-event (:on-event config))) + +(defn update-refrain + ([id key val] (update-refrain id #(assoc % key val))) + ([id update-fn] + (swap! refrains update id #(do (swap! % (comp update-fn backup-on-event)) %)))) + +(defn- maybe-run-on-stop-fn + [refrain] + (let [on-stop (-> @refrain :refrain/config :on-stop)] + (when on-stop (on-stop @refrain)))) diff --git a/src/time_time/sequencing_4.cljc b/src/time_time/sequencing_4.cljc new file mode 100644 index 0000000..e56a230 --- /dev/null +++ b/src/time_time/sequencing_4.cljc @@ -0,0 +1,266 @@ +(ns time-time.sequencing-4 + (:require + [overtone.music.time :refer [apply-at now]] + [taoensso.timbre :as timbre] + [time-time.standard :refer [dur->ms wrap-at]])) + +(declare get-current-dur-data calculate-cycle-delta play-event? get-cycle-len get-cycle-data calculate-next-voice-state) + +(defn init-voice-data + [{:as config + :keys [durs on-event ratio tempo loop? start-index start-time elapsed elapsed-dur playing? + before-update on-schedule extra-data on-startup-error _durs-len] + :or {durs [1] + on-event println + ratio 1 + tempo 60 + loop? false + start-index 0 + start-time (now) + elapsed 0 + elapsed-dur 0 ;; TODO investigate if this is compatible with `elapsed`, if so, substitute + playing? true + before-update identity ;; receives the voice data before it is used to `reset!` the `voice-atom` + on-schedule (fn [voice event-schedule] event-schedule) + extra-data {}}}] + (let [cycle-data (get-cycle-data config) + data (merge + extra-data + {:durs durs + :on-event on-event + :elapsed-dur elapsed-dur + :ratio ratio + :tempo tempo + :loop? loop? + :index start-index + :cycle-0-index start-index + :started-at start-time + ;; maybe FIXME, perhaps `play!` should receive + ;; `elapsed-ms` instead of `elapsed` + :elapsed-ms (dur->ms elapsed tempo) + :playing? playing? + :before-update before-update + :on-schedule on-schedule + :on-startup-error on-startup-error} + cycle-data) + data (calculate-next-voice-state data)] + (atom data))) + +(defn calculate-next-voice-state + [{:keys [index + elapsed-ms + cycle-len + cycle-0-index + cycle + elapsed-dur + cycle-delta + started-at + playing? + durs + loop?] :as voice}] + (let [{:keys [dur event-dur]} (get-current-dur-data voice) + elapsed-dur* (+ elapsed-dur dur) + index* (inc index) + cycle-delta* (calculate-cycle-delta elapsed-dur* cycle-len) + updated-state {:index index* + :cycle (quot elapsed-dur* cycle-len) + :cycle-delta cycle-delta* + :cycle-0-index (if (zero? cycle-delta*) index* cycle-0-index) + :elapsed-dur elapsed-dur* + :elapsed-ms (+ elapsed-ms event-dur) + :playing? (and playing? (play-event? index durs loop?)) + :current-event {:index index + :dur-ms event-dur + :dur-s (/ event-dur 1000) + :dur dur + :durs durs + :cycle cycle + :cycle-len cycle-len + :cycle-delta cycle-delta + :cycle-0-index cycle-0-index + :cycle-delta-index (- index cycle-0-index) + :elapsed-ms elapsed-ms + :elapsed-dur elapsed-dur + :start-at (+ started-at elapsed-ms)}}] + (merge voice updated-state))) + +(comment + (init-voice-data {:durs [1 2 3] + :on-event println + :ratio 1}) + (def v1 (init-voice-data {:durs [1 2 3] + :on-event println + :ratio 1})) + + (update-voice-state! v1) + + (swap! v1 assoc + ;; :update? (fn [data] (zero? (:cycle-delta data))) + :update {:reset-cycle? true + :index 0 + :durs [1 3 2 1] + :ratio 1 + :tempo 60 + :on-event (fn [x] (println x))})) + +(defn maybe-reset-cycle [voice-data] + (if-not (:reset-cycle? voice-data) + voice-data + (-> voice-data + (assoc :cycle 0 + :cycle-delta 0 + :elapsed-dur 0) + (dissoc :reset-cycle?)))) + +(defn update-time-data [voice-data] + (-> voice-data + maybe-reset-cycle + calculate-next-voice-state)) + +(defn update-voice-config-data + [{:keys [update update?] :as voice-data}] + (let [update* (cond (and update? update) (if (update? voice-data) update nil) + update update + :else nil)] + (cond-> voice-data + update* (-> (merge update*) + (dissoc :update? :update)) + true (#(assoc % :cycle-len (get-cycle-len %)))))) + +(defn update-voice-state! + [voice-atom] + (swap! voice-atom + (fn [voice-data] (-> voice-data + update-voice-config-data + update-time-data)))) + +(defn play-event? + "Based on the index, determine if a voice has an event that should be + played." + [index durs loop?] + (cond + (sequential? durs) (or (< index (count durs)) loop?) + (fn? durs) loop? + :else (throw (ex-info "Cannot play event. `durs` must be vector, a list or a function" + {:durs durs})))) + +(defn schedule? + "Based on the index, determine if a voice has an event that should be + scheduled." + [index durs loop?] + (play-event? index durs loop?)) +(declare schedule!) + +(defn after-event [voice-atom] + (let [{:keys [playing?] :as _voice-data} (update-voice-state! voice-atom)] + (when playing? (schedule! voice-atom)))) + +(defn on-error [voice-atom error] + (timbre/error error) + (let [v* (deref voice-atom) + prev-on-event (:prev-on-event v*) + on-startup-error (:on-startup-error v*)] + (cond prev-on-event (try + (prev-on-event {:data v* + :voice voice-atom + :dur (-> v* :current-event :dur)}) + (swap! voice-atom :on-event prev-on-event) + #?(:clj (catch Exception _ (timbre/error "Can not recover from error")) + :cljs (catch js/Error _ (timbre/error "Can not recover from error"))) + (finally (after-event voice-atom))) + + (zero? (:index v*)) (on-startup-error) + ;; probably when there is no `:prev-on-event` and `:index` is not zero. + :else (after-event voice-atom)))) + +(defn get-on-event-fn [voice-atom] + (or (and (not (:update? (:update @voice-atom))) + (:on-event (:update @voice-atom))) + (:on-event @voice-atom))) + +(defn schedule! [voice-atom] + (let [schedule (-> voice-atom deref :current-event :start-at) + on-event* (fn [] + (let [{:keys [loop? durs current-event] :as v*} (deref voice-atom) + ;; the current event: + {:keys [index]} current-event] + (when (:playing? v*) + (timbre/debug "About to play event" index :durs durs :loop loop?) + (try (when (play-event? index durs loop?) + (let [on-event (get-on-event-fn voice-atom)] + (on-event {:event current-event + :voice voice-atom}))) + (after-event voice-atom) + #?(:clj (catch Exception e (on-error voice-atom e)) + :cljs (catch js/Error e (on-error voice-atom e)))))))] + (apply-at schedule on-event*))) + +(defn calculate-cycle-delta [elapsed-dur cycle-len] + (/ (mod elapsed-dur cycle-len) + cycle-len)) + +(defn get-cycle-len + [{:keys [durs ratio durs-len] :as _config}] + (cond + (and (fn? durs) (not durs-len)) (do (timbre/warn "`durs` is a function but no `durs-len` has been provided, defaulting to 1*ratio") + (* ratio 1)) + durs-len durs-len + :else (* ratio (apply + durs)))) + +(defn get-cycle-data + [{:keys [elapsed-dur] :as config + :or {elapsed-dur 0}}] + (let [cycle-len (get-cycle-len config)] + {:cycle-len cycle-len + :cycle (quot elapsed-dur cycle-len) + :cycle-delta (calculate-cycle-delta elapsed-dur cycle-len)})) + +(defn get-current-dur-data-multi-pred + [{:as voice-data :keys [durs]}] + (cond + (sequential? durs) :durs-vector + (fn? durs) :durs-gen-fn + :else (throw (ex-info "Unknown dur-data, cannot `get-current-dur-data-multi-pred`" voice-data)))) + +(defmulti get-current-dur-data + #'get-current-dur-data-multi-pred) + +(defmethod get-current-dur-data :durs-vector + [{:keys [tempo ratio durs index] :as _voice-data}] + (let [dur (-> (wrap-at index durs) (* ratio)) + event-dur (dur->ms dur tempo)] + {:dur dur :event-dur event-dur})) + +(defmethod get-current-dur-data :durs-gen-fn + [{:keys [durs tempo] :as voice-data}] + (let [dur (durs voice-data) + event-dur (dur->ms dur tempo)] + {:dur dur :event-dur event-dur})) + +(comment + (init-voice-data {:durs [1 2 3] + :on-event println + :ratio 1})) + +(defn play! + "Gets a config and returns a `voice-atom` that will start playing, + if `playing?` is true (default)" + [config] + (let [voice (init-voice-data config)] + (schedule! voice) + voice)) +(comment + (def v1 (play! {:durs [1 2 3] + :on-event println + :ratio 1}))) + +(defn stop! [voice-atom] + (swap! voice-atom assoc :playing? false)) + +(comment + (stop! v1) + (require '[overtone.core :as o]) + + (schedule! nil) + (o/stop) + (timbre/set-level! :debug)) From d6d7379dbadc8cdcfe07aff6c5929649436720b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Mon, 21 Apr 2025 11:42:31 -0600 Subject: [PATCH 5/7] Add tests for sequencing-4 --- src/time_time/dynacan/players/refrain/v2.cljc | 21 +- src/time_time/sequencing_4.cljc | 36 +- .../dynacan/players/refrain/v2_test.clj | 45 ++ test/time_time/sequencing_4_test.clj | 446 ++++++++++++++++++ 4 files changed, 527 insertions(+), 21 deletions(-) create mode 100644 test/time_time/dynacan/players/refrain/v2_test.clj create mode 100644 test/time_time/sequencing_4_test.clj diff --git a/src/time_time/dynacan/players/refrain/v2.cljc b/src/time_time/dynacan/players/refrain/v2.cljc index 71391cd..b7467b2 100644 --- a/src/time_time/dynacan/players/refrain/v2.cljc +++ b/src/time_time/dynacan/players/refrain/v2.cljc @@ -47,7 +47,8 @@ (swap! refrains assoc id voice)) :else - (let [voice (s4/play! {:durs durs + (let [voice (s4/play! {:extra-data {:id id :ref ref} + :durs durs :on-event on-event :loop? loop? :tempo (config :tempo 60) @@ -67,8 +68,10 @@ (when (cyi? 3) (println "First" dur)))) (ref-rain :id :adios :ref :hola - :durs [2] - :on-event (on-event (println "adios" (now)))) + ;; :reset-cycle? true + :durs [2 3] + :on-event (on-event + (println cycle cycle-delta-index cycle-delta))) (stop)) @@ -107,14 +110,14 @@ [& forms] `(fn [~'{{:keys [index dur - durs dur-ms dur-s cycle - cycle-len cycle-delta cycle-0-index - cycle-delta-index] :as event} :event}] + cycle-delta-index + new-cycle?] :as event} :event + {:keys [id cycle-len durs] :as voice} :voice}] (let [~'data ~'event ~'i ~'index ~'cy ~'cycle @@ -145,7 +148,7 @@ (map first))) (defn add-to! [ref-voice events-from-cp on-event - {:keys [durs ratio loop? cp] + {:keys [id durs ratio loop? cp] :or {durs (ref-voice :durs) ratio (ref-voice :ratio) loop? (ref-voice :loop?)} @@ -169,7 +172,9 @@ :start-time start-time :loop? loop? :before-update before-update - :extra-data {:cp cp + :extra-data {:id id + :ref (:id ref-voice) + :cp cp :cp-at cp-elapsed :interval-from-cp interval-from-cp :events-from-cp events-from-cp}}))) diff --git a/src/time_time/sequencing_4.cljc b/src/time_time/sequencing_4.cljc index e56a230..8c35953 100644 --- a/src/time_time/sequencing_4.cljc +++ b/src/time_time/sequencing_4.cljc @@ -9,9 +9,9 @@ (defn init-voice-data [{:as config :keys [durs on-event ratio tempo loop? start-index start-time elapsed elapsed-dur playing? - before-update on-schedule extra-data on-startup-error _durs-len] + before-update on-schedule extra-data on-startup-error _cycle-len] :or {durs [1] - on-event println + on-event (fn [{:keys [_event _voice]}]) ratio 1 tempo 60 loop? false @@ -21,9 +21,9 @@ elapsed-dur 0 ;; TODO investigate if this is compatible with `elapsed`, if so, substitute playing? true before-update identity ;; receives the voice data before it is used to `reset!` the `voice-atom` - on-schedule (fn [voice event-schedule] event-schedule) + on-schedule (fn [_voice event-schedule] event-schedule) extra-data {}}}] - (let [cycle-data (get-cycle-data config) + (let [cycle-data (get-cycle-data (assoc config :ratio ratio)) data (merge extra-data {:durs durs @@ -34,6 +34,7 @@ :loop? loop? :index start-index :cycle-0-index start-index + :new-cycle? true :started-at start-time ;; maybe FIXME, perhaps `play!` should receive ;; `elapsed-ms` instead of `elapsed` @@ -52,6 +53,7 @@ cycle-len cycle-0-index cycle + new-cycle? elapsed-dur cycle-delta started-at @@ -61,11 +63,13 @@ (let [{:keys [dur event-dur]} (get-current-dur-data voice) elapsed-dur* (+ elapsed-dur dur) index* (inc index) + cycle* (quot elapsed-dur* cycle-len) cycle-delta* (calculate-cycle-delta elapsed-dur* cycle-len) updated-state {:index index* - :cycle (quot elapsed-dur* cycle-len) + :cycle cycle* :cycle-delta cycle-delta* :cycle-0-index (if (zero? cycle-delta*) index* cycle-0-index) + :new-cycle? (not= cycle cycle*) :elapsed-dur elapsed-dur* :elapsed-ms (+ elapsed-ms event-dur) :playing? (and playing? (play-event? index durs loop?)) @@ -75,6 +79,7 @@ :dur dur :durs durs :cycle cycle + :new-cycle? new-cycle? :cycle-len cycle-len :cycle-delta cycle-delta :cycle-0-index cycle-0-index @@ -86,8 +91,9 @@ (comment (init-voice-data {:durs [1 2 3] - :on-event println - :ratio 1}) + ;; :on-event println + ;; :ratio 1 + }) (def v1 (init-voice-data {:durs [1 2 3] :on-event println :ratio 1})) @@ -109,7 +115,10 @@ (-> voice-data (assoc :cycle 0 :cycle-delta 0 - :elapsed-dur 0) + :elapsed-dur 0 + :cycle-len (get-cycle-len (assoc voice-data + :recalculate-cycle-len? + (not (:keep-cycle-len? voice-data))))) (dissoc :reset-cycle?)))) (defn update-time-data [voice-data] @@ -189,7 +198,8 @@ (try (when (play-event? index durs loop?) (let [on-event (get-on-event-fn voice-atom)] (on-event {:event current-event - :voice voice-atom}))) + :voice v* + :voice-atom voice-atom}))) (after-event voice-atom) #?(:clj (catch Exception e (on-error voice-atom e)) :cljs (catch js/Error e (on-error voice-atom e)))))))] @@ -200,11 +210,11 @@ cycle-len)) (defn get-cycle-len - [{:keys [durs ratio durs-len] :as _config}] + [{:keys [durs ratio cycle-len recalculate-cycle-len?] :as _config}] (cond - (and (fn? durs) (not durs-len)) (do (timbre/warn "`durs` is a function but no `durs-len` has been provided, defaulting to 1*ratio") - (* ratio 1)) - durs-len durs-len + (and (fn? durs) (not cycle-len)) (do (timbre/warn "`durs` is a function but no `cycle-len` has been provided, defaulting to 1*ratio") + (* ratio 1)) + (and cycle-len (not recalculate-cycle-len?)) cycle-len :else (* ratio (apply + durs)))) (defn get-cycle-data diff --git a/test/time_time/dynacan/players/refrain/v2_test.clj b/test/time_time/dynacan/players/refrain/v2_test.clj new file mode 100644 index 0000000..bee08b2 --- /dev/null +++ b/test/time_time/dynacan/players/refrain/v2_test.clj @@ -0,0 +1,45 @@ +(ns time-time.dynacan.players.refrain.v2-test + (:require + [clojure.test :refer [deftest is testing]] + [time-time.dynacan.players.refrain.v2 :refer [ref-rain refrains]] + [time-time.sequencing-4 :as s4])) + +(deftest ref-rain-test + (with-redefs [refrains (atom {}) + s4/schedule! (fn [_voice])] + (ref-rain :id :hola + :durs [1] + :loop? false + :on-event println) + (testing "Creates a refrain with some config keys" + (is (= #{:before-update + :current-event + :cycle + :cycle-0-index + :cycle-delta + :cycle-len + :durs + :elapsed-dur + :elapsed-ms + :id + :index + :loop? + :new-cycle? + :on-event + :on-schedule + :on-startup-error + :playing? + :ratio + :ref + :started-at + :tempo + :refrain/config} + (set (keys @(:hola @refrains)))))) + (testing "The `:refrain/config` key only contains the specified keys" + (is (= {:id :hola, + :durs [1], + :loop? false, + :on-event println} + (:refrain/config @(:hola @refrains))))) + (testing "The refrain `:id` is a top level key" + (is (= :hola (:id @(:hola @refrains))))))) diff --git a/test/time_time/sequencing_4_test.clj b/test/time_time/sequencing_4_test.clj new file mode 100644 index 0000000..f2bc552 --- /dev/null +++ b/test/time_time/sequencing_4_test.clj @@ -0,0 +1,446 @@ +(ns time-time.sequencing-4-test + (:require + [clojure.core.async :as a] + [clojure.test :refer [deftest is testing]] + [time-time.sequencing-4 + :refer + [calculate-next-voice-state init-voice-data play-event? schedule! + update-voice-state!]] + [time-time.standard :refer [now wrap-at]] + [time-time.utils.async :refer [async-events-tester]] + [time-time.utils.core :refer [close-to get-time-interval]])) + +(deftest calculate-next-voice-state-test + (testing "Increases the `index` by one, updates the `elapsed` value and updates (or sets) the `current-event-dur`" + (is (= {:durs [1 2 1], + :cycle 0, + :index 1, + :started-at 0, + :current-event + {:durs [1 2 1], + :cycle 0, + :index 0, + :start-at 0, + :cycle-delta-index 0, + :cycle-delta 0, + :dur 1, + :elapsed-dur 0, + :new-cycle? nil, + :cycle-0-index 0, + :elapsed-ms 0, + :dur-s 1, + :dur-ms 1000, + :cycle-len 4}, + :playing? nil, + :cycle-delta 1/4, + :ratio 1, + :elapsed-dur 1, + :new-cycle? false, + :loop? false, + :cycle-0-index 0, + :elapsed-ms 1000, + :tempo 60, + :cycle-len 4} + (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 + :cycle-0-index 0 + :cycle-delta 0 + :cycle 0 + :index 0 + :tempo 60 + :loop? false + :started-at 0 + :elapsed-ms 0 + :elapsed-dur 0 + :ratio 1})))) + + (testing "`cycle` calculation" + (let [v @(init-voice-data {:durs [1 2 1]}) + states (->> (range 10) + (reduce (fn [acc _i] + (conj acc (calculate-next-voice-state (last acc)))) + [v]) + (mapv (comp #(select-keys % [:index :cycle :cycle-delta :cycle-0-index]) + :current-event)))] + (is (= [{:index 0 :cycle 0 :cycle-delta 0 :cycle-0-index 0} + {:index 1 :cycle 0 :cycle-delta 1/4 :cycle-0-index 0} + {:index 2 :cycle 0 :cycle-delta 3/4 :cycle-0-index 0} + {:index 3 :cycle 1 :cycle-delta 0 :cycle-0-index 3} + {:index 4 :cycle 1 :cycle-delta 1/4 :cycle-0-index 3} + {:index 5 :cycle 1 :cycle-delta 3/4 :cycle-0-index 3} + {:index 6 :cycle 2 :cycle-delta 0 :cycle-0-index 6} + {:index 7 :cycle 2 :cycle-delta 1/4 :cycle-0-index 6} + {:index 8 :cycle 2 :cycle-delta 3/4 :cycle-0-index 6} + {:index 9 :cycle 3 :cycle-delta 0 :cycle-0-index 9} + {:index 10 :cycle 3 :cycle-delta 1/4 :cycle-0-index 9}] + states))) + (testing "different ratios" + (let [v @(init-voice-data {:durs [1 2 1] + :ratio 2}) + states (->> (range 10) + (reduce (fn [acc _i] + (conj acc (calculate-next-voice-state (last acc)))) + [v]) + (mapv (comp #(select-keys % [:index :cycle :cycle-delta :cycle-0-index :elapsed-dur]) + :current-event)))] + (is (= [{:index 0, :cycle 0, :cycle-delta 0, :cycle-0-index 0, :elapsed-dur 0} + {:index 1, :cycle 0, :cycle-delta 1/4, :cycle-0-index 0, :elapsed-dur 2} + {:index 2, :cycle 0, :cycle-delta 3/4, :cycle-0-index 0, :elapsed-dur 6} + {:index 3, :cycle 1, :cycle-delta 0, :cycle-0-index 3, :elapsed-dur 8} + {:index 4, :cycle 1, :cycle-delta 1/4, :cycle-0-index 3, :elapsed-dur 10} + {:index 5, :cycle 1, :cycle-delta 3/4, :cycle-0-index 3, :elapsed-dur 14} + {:index 6, :cycle 2, :cycle-delta 0, :cycle-0-index 6, :elapsed-dur 16} + {:index 7, :cycle 2, :cycle-delta 1/4, :cycle-0-index 6, :elapsed-dur 18} + {:index 8, :cycle 2, :cycle-delta 3/4, :cycle-0-index 6, :elapsed-dur 22} + {:index 9, :cycle 3, :cycle-delta 0, :cycle-0-index 9, :elapsed-dur 24} + {:index 10, :cycle 3, :cycle-delta 1/4, :cycle-0-index 9, :elapsed-dur 26}] + states)))) + (testing "`new-cycle?" + (let [v @(init-voice-data {:durs [1 2 1] + :ratio 2}) + states (->> (range 10) + (reduce (fn [acc _i] + (conj acc (calculate-next-voice-state (last acc)))) + [v]) + (mapv (comp #(select-keys % [:index :cycle :cycle-delta :new-cycle?]) + :current-event)))] + (is (= [{:index 0, :cycle 0, :cycle-delta 0, :new-cycle? true} + {:index 1, :cycle 0, :cycle-delta 1/4, :new-cycle? false} + {:index 2, :cycle 0, :cycle-delta 3/4, :new-cycle? false} + {:index 3, :cycle 1, :cycle-delta 0, :new-cycle? true} + {:index 4, :cycle 1, :cycle-delta 1/4, :new-cycle? false} + {:index 5, :cycle 1, :cycle-delta 3/4, :new-cycle? false} + {:index 6, :cycle 2, :cycle-delta 0, :new-cycle? true} + {:index 7, :cycle 2, :cycle-delta 1/4, :new-cycle? false} + {:index 8, :cycle 2, :cycle-delta 3/4, :new-cycle? false} + {:index 9, :cycle 3, :cycle-delta 0, :new-cycle? true} + {:index 10, :cycle 3, :cycle-delta 1/4, :new-cycle? false}] + states)))) + (testing "different ratios and tempo" + (let [v @(init-voice-data {:durs [1 2 1] + :ratio 2 + :tempo 120}) + states (->> (range 10) + (reduce (fn [acc _i] + (conj acc (calculate-next-voice-state (last acc)))) + [v]) + (mapv (comp #(select-keys % [:index :cycle :cycle-delta :cycle-0-index :elapsed-dur]) + :current-event)))] + (is (= [{:index 0, :cycle 0, :cycle-delta 0, :cycle-0-index 0, :elapsed-dur 0} + {:index 1, :cycle 0, :cycle-delta 1/4, :cycle-0-index 0, :elapsed-dur 2} + {:index 2, :cycle 0, :cycle-delta 3/4, :cycle-0-index 0, :elapsed-dur 6} + {:index 3, :cycle 1, :cycle-delta 0, :cycle-0-index 3, :elapsed-dur 8} + {:index 4, :cycle 1, :cycle-delta 1/4, :cycle-0-index 3, :elapsed-dur 10} + {:index 5, :cycle 1, :cycle-delta 3/4, :cycle-0-index 3, :elapsed-dur 14} + {:index 6, :cycle 2, :cycle-delta 0, :cycle-0-index 6, :elapsed-dur 16} + {:index 7, :cycle 2, :cycle-delta 1/4, :cycle-0-index 6, :elapsed-dur 18} + {:index 8, :cycle 2, :cycle-delta 3/4, :cycle-0-index 6, :elapsed-dur 22} + {:index 9, :cycle 3, :cycle-delta 0, :cycle-0-index 9, :elapsed-dur 24} + {:index 10, :cycle 3, :cycle-delta 1/4, :cycle-0-index 9, :elapsed-dur 26}] + states)))) + (testing "`cycle-len` is different from the `durs` sum" + (let [v @(init-voice-data {:durs [1 2 1] :cycle-len 3}) + states (->> (range 10) + (reduce (fn [acc _i] + (conj acc (calculate-next-voice-state (last acc)))) + [v]) + (mapv (comp #(select-keys % [:index :cycle :cycle-delta :cycle-0-index :elapsed-dur]) + :current-event)))] + ;; when elapsed dur is a multiple of 3 then cycle-delta is 0 + (is (= [{:index 0, :cycle 0, :cycle-delta 0, :cycle-0-index 0, :elapsed-dur 0} + {:index 1, :cycle 0, :cycle-delta 1/3, :cycle-0-index 0, :elapsed-dur 1} + {:index 2, :cycle 1, :cycle-delta 0, :cycle-0-index 2, :elapsed-dur 3} + {:index 3, :cycle 1, :cycle-delta 1/3, :cycle-0-index 2, :elapsed-dur 4} + {:index 4, :cycle 1, :cycle-delta 2/3, :cycle-0-index 2, :elapsed-dur 5} + {:index 5, :cycle 2, :cycle-delta 1/3, :cycle-0-index 2, :elapsed-dur 7} + {:index 6, :cycle 2, :cycle-delta 2/3, :cycle-0-index 2, :elapsed-dur 8} + {:index 7, :cycle 3, :cycle-delta 0, :cycle-0-index 7, :elapsed-dur 9} + {:index 8, :cycle 3, :cycle-delta 2/3, :cycle-0-index 7, :elapsed-dur 11} + {:index 9, :cycle 4, :cycle-delta 0, :cycle-0-index 9, :elapsed-dur 12} + {:index 10, :cycle 4, :cycle-delta 1/3, :cycle-0-index 9, :elapsed-dur 13}] + states))))) + + (testing "`current-event-dur` is related to tempo" + (is (= 500N + (:dur-ms (:current-event + (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 + :cycle-0-index 0 + :cycle-delta 0 + :cycle 0 + :index 0 + :tempo 120 + :loop? false + :started-at 0 + :elapsed-ms 0 + :elapsed-dur 0 + :ratio 1}))))) + (is (= 1000N + (:dur-ms (:current-event + (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 + :cycle 0 + :cycle-0-index 0 + :cycle-delta 1/4 + :index 1 + :tempo 120 + :loop? false + :started-at 0 + :elapsed-ms 0 + :elapsed-dur 1 + :ratio 1}))))) + (is (= 4000 + (:dur-ms (:current-event + (calculate-next-voice-state {:durs [1 2 1] + :cycle-len 4 + :cycle 0 + :cycle-0-index 0 + :cycle-delta 1/4 + :index 1 + :tempo 30 + :loop? false + :started-at 0 + :elapsed-ms 0 + :elapsed-dur 3 + :ratio 1})))))) + + (testing "`:durs` can be a function")) + +(deftest update-voice-state!-test + (testing "Basic: Operates over a voice-atom" + (let [voice-atom (init-voice-data {:durs [1 2 1]}) + states (->> (range 10) + (reduce (fn [acc _i] + (update-voice-state! voice-atom) + (conj acc @voice-atom)) + [@voice-atom]) + (mapv (comp #(select-keys % [:index + :cycle + :cycle-delta + :cycle-0-index]) + :current-event)))] + (is (= [{:index 0 :cycle 0 :cycle-delta 0 :cycle-0-index 0} + {:index 1 :cycle 0 :cycle-delta 1/4 :cycle-0-index 0} + {:index 2 :cycle 0 :cycle-delta 3/4 :cycle-0-index 0} + {:index 3 :cycle 1 :cycle-delta 0 :cycle-0-index 3} + {:index 4 :cycle 1 :cycle-delta 1/4 :cycle-0-index 3} + {:index 5 :cycle 1 :cycle-delta 3/4 :cycle-0-index 3} + {:index 6 :cycle 2 :cycle-delta 0 :cycle-0-index 6} + {:index 7 :cycle 2 :cycle-delta 1/4 :cycle-0-index 6} + {:index 8 :cycle 2 :cycle-delta 3/4 :cycle-0-index 6} + {:index 9 :cycle 3 :cycle-delta 0 :cycle-0-index 9} + {:index 10 :cycle 3 :cycle-delta 1/4 :cycle-0-index 9}] + states)))) + (testing "Can operate over an update of the voice, it can update any value of the voice, e.g. `durs` and `on-event`." + (let [voice-atom (init-voice-data {:durs [1 2 1] :on-event println}) + updated-on-event identity + states (->> (range 10) + (reduce (fn [acc i] + (when (= 5 i) + (swap! voice-atom assoc + :update {:durs [3 4] + :on-event updated-on-event})) + (update-voice-state! voice-atom) + (conj acc @voice-atom)) + [@voice-atom])) + current-events (mapv (comp #(select-keys % [:index + :cycle + :cycle-delta + :cycle-0-index + :dur]) + :current-event) + states) + on-events (mapv :on-event states)] + (is (= [{:index 0, :cycle 0, :cycle-delta 0, :cycle-0-index 0, :dur 1} + {:index 1, :cycle 0, :cycle-delta 1/4, :cycle-0-index 0, :dur 2} + {:index 2, :cycle 0, :cycle-delta 3/4, :cycle-0-index 0, :dur 1} + {:index 3, :cycle 1, :cycle-delta 0, :cycle-0-index 3, :dur 1} + {:index 4, :cycle 1, :cycle-delta 1/4, :cycle-0-index 3, :dur 2} + {:index 5, :cycle 1, :cycle-delta 3/4, :cycle-0-index 3, :dur 1} + {:index 6, :cycle 2, :cycle-delta 0, :cycle-0-index 6, :dur 3} + {:index 7, :cycle 2, :cycle-delta 3/4, :cycle-0-index 6, :dur 4} + {:index 8, :cycle 3, :cycle-delta 3/4, :cycle-0-index 6, :dur 3} + {:index 9, :cycle 4, :cycle-delta 1/2, :cycle-0-index 6, :dur 4} + {:index 10, :cycle 5, :cycle-delta 1/2, :cycle-0-index 6, :dur 3}] + current-events)) + (is (= (concat (repeat 6 println) + (repeat 5 identity)) + on-events)))) + (testing "Can update the cycle count" + (let [voice-atom (init-voice-data {:durs [1 2 1] :on-event println}) + states (->> (range 10) + (reduce (fn [acc i] + (when (= 5 i) + (swap! voice-atom assoc + :update {:reset-cycle? true + :durs [3 4]})) + (update-voice-state! voice-atom) + (conj acc @voice-atom)) + [@voice-atom])) + current-events (mapv (comp #(select-keys % [:index + :dur + :cycle + :cycle-len + :cycle-delta + :cycle-0-index]) + :current-event) + states)] + (is (= [{:index 0, :dur 1, :cycle 0, :cycle-len 4, :cycle-delta 0, :cycle-0-index 0} + {:index 1, :dur 2, :cycle 0, :cycle-len 4, :cycle-delta 1/4, :cycle-0-index 0} + {:index 2, :dur 1, :cycle 0, :cycle-len 4, :cycle-delta 3/4, :cycle-0-index 0} + {:index 3, :dur 1, :cycle 1, :cycle-len 4, :cycle-delta 0, :cycle-0-index 3} + {:index 4, :dur 2, :cycle 1, :cycle-len 4, :cycle-delta 1/4, :cycle-0-index 3} + {:index 5, :dur 1, :cycle 1, :cycle-len 4, :cycle-delta 3/4, :cycle-0-index 3} + {:index 6, :dur 3, :cycle 0, :cycle-len 7, :cycle-delta 0, :cycle-0-index 6} + {:index 7, :dur 4, :cycle 0, :cycle-len 7, :cycle-delta 3/7, :cycle-0-index 6} + {:index 8, :dur 3, :cycle 1, :cycle-len 7, :cycle-delta 0, :cycle-0-index 8} + {:index 9, :dur 4, :cycle 1, :cycle-len 7, :cycle-delta 3/7, :cycle-0-index 8} + {:index 10, :dur 3, :cycle 2, :cycle-len 7, :cycle-delta 0, :cycle-0-index 10}] + current-events))) + (testing "Can keep the original cycle-len" + (let [voice-atom (init-voice-data {:durs [1 2 1] :on-event println}) + update* {:reset-cycle? true + :keep-cycle-len? true + :durs [3 4]} + states (->> (range 10) + (reduce (fn [acc i] + (when (= 5 i) + (swap! voice-atom assoc + :update update*)) + (update-voice-state! voice-atom) + (conj acc @voice-atom)) + [@voice-atom])) + current-events (mapv (comp #(select-keys % [:index + :dur + :cycle + :cycle-len + :cycle-delta + :cycle-0-index + :new-cycle?]) + :current-event) + states)] + (is (= [{:index 0, :dur 1, :cycle 0, :cycle-len 4, :cycle-delta 0, :cycle-0-index 0, :new-cycle? true} + {:index 1, :dur 2, :cycle 0, :cycle-len 4, :cycle-delta 1/4, :cycle-0-index 0, :new-cycle? false} + {:index 2, :dur 1, :cycle 0, :cycle-len 4, :cycle-delta 3/4, :cycle-0-index 0, :new-cycle? false} + {:index 3, :dur 1, :cycle 1, :cycle-len 4, :cycle-delta 0, :cycle-0-index 3, :new-cycle? true} + {:index 4, :dur 2, :cycle 1, :cycle-len 4, :cycle-delta 1/4, :cycle-0-index 3, :new-cycle? false} + {:index 5, :dur 1, :cycle 1, :cycle-len 4, :cycle-delta 3/4, :cycle-0-index 3, :new-cycle? false} + {:index 6, :dur 3, :cycle 0, :cycle-len 4, :cycle-delta 0, :cycle-0-index 6, :new-cycle? true} + {:index 7, :dur 4, :cycle 0, :cycle-len 4, :cycle-delta 3/4, :cycle-0-index 6, :new-cycle? false} + {:index 8, :dur 3, :cycle 1, :cycle-len 4, :cycle-delta 3/4, :cycle-0-index 6, :new-cycle? true} + {:index 9, :dur 4, :cycle 2, :cycle-len 4, :cycle-delta 1/2, :cycle-0-index 6, :new-cycle? true} + {:index 10, :dur 3, :cycle 3, :cycle-len 4, :cycle-delta 1/2, :cycle-0-index 6, :new-cycle? true}] + current-events)))))) + +(deftest play-event?-test + (testing "Play a first and second event but not a third nor fourth" + (is (true? (play-event? 0 [1 2] false))) + (is (true? (play-event? 1 [1 2] false))) + (is (false? (play-event? 2 [1 2] false))) + (is (false? (play-event? 3 [1 2] false)))) + + (testing "Loop playing (always play events)" + (is (true? (play-event? 0 [1 2] true))) + (is (true? (play-event? 1 [1 2] true))) + (is (true? (play-event? 2 [1 2] true))) + (is (true? (play-event? 3 [1 2] true))))) + +(defn get-dur [event] + ((event :durs) (event :index))) + +(deftest schedule!-test + (let [base-voice {:durs [1 2 1 2 1 1] + :cycle-len (apply + [1 2 1 2 1 1]) + :index 0 + :tempo 6000 ;; NOTE a dur of `1` lasts `10N`ms + :loop? false + :started-at 0 + :elapsed-ms 0 + :elapsed-dur 0 + :ratio 1 + :playing? true} + default-continue (fn [ev _] (< (ev :index) + (dec (count (base-voice :durs)))))] + (testing "`:elapsed-ms` values are correct" + (let [{:keys [event-chan result-chan]} (async-events-tester + default-continue) + v (init-voice-data (assoc base-voice + :started-at (+ 1 (now)) + :on-event (fn [{:keys [event _voice]}] + (a/>!! event-chan event))))] + (schedule! v) + (is (= '({:index 0, :elapsed-ms 0, :dur 1} + {:index 1, :elapsed-ms 10N, :dur 2} + {:index 2, :elapsed-ms 30N, :dur 1} + {:index 3, :elapsed-ms 40N, :dur 2} + {:index 4, :elapsed-ms 60N, :dur 1} + {:index 5, :elapsed-ms 70N, :dur 1}) + (mapv #(-> % + (select-keys [:index :elapsed-ms]) + (assoc :dur (get-dur %))) + (a/!! event-chan (select-keys + event + [:index :dur :dur-ms])))))] + (schedule! v) + (is (= [{:index 0, :dur 1, :dur-ms 10N} + {:index 1, :dur 2, :dur-ms 20N} + {:index 2, :dur 1, :dur-ms 10N} + {:index 3, :dur 2, :dur-ms 20N} + {:index 4, :dur 1, :dur-ms 10N} + {:index 5, :dur 1, :dur-ms 10N}] + (a/!! event-chan (select-keys + event + [:index :dur :dur-ms])) + (when (> (:index event) total-events) + (swap! v assoc + :loop? false + :playing? false))))] + (schedule! v) + (is (= [{:index 0, :dur 1, :dur-ms 10N} + {:index 1, :dur 2, :dur-ms 20N} + {:index 2, :dur 1, :dur-ms 10N} + {:index 3, :dur 2, :dur-ms 20N} + {:index 4, :dur 1, :dur-ms 10N} + {:index 5, :dur 1, :dur-ms 10N}] + (a/!! event-chan + (assoc event :event-at (now))))))] + (schedule! v) + (is (= true + (->> (a/ Date: Wed, 30 Apr 2025 00:28:48 -0600 Subject: [PATCH 6/7] [refrain.v2] fix cyi? --- src/time_time/dynacan/players/refrain/v2.cljc | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/time_time/dynacan/players/refrain/v2.cljc b/src/time_time/dynacan/players/refrain/v2.cljc index b7467b2..ebd6839 100644 --- a/src/time_time/dynacan/players/refrain/v2.cljc +++ b/src/time_time/dynacan/players/refrain/v2.cljc @@ -95,9 +95,16 @@ With an `offset` argument it will attempt to calculate if the current index is an `offset` away from the start of the cycle. Using an `offset` is prone to returning false positives if the `cycle-len` has not been derived from the sum of `durs`." - ([] `(~'cycle-0-index? ~'index ~'cycle-0-index)) + ([] `(~cycle-0-index? ~'index ~'cycle-0-index)) ;; NOTE in most cases this will work, when the `cycle-len` is derived from the `durs` sum; however in cases where this is not, the return value will not be correct. - ([offset] `(~'cycle-0-index? ~'index ~'cycle-0-index ~offset ~'durs ~'cycle-len))) + ([offset] `(~cycle-0-index? ~'index ~'cycle-0-index ~offset ~'durs ~'cycle-len))) + +(comment + ((on-event (cyi?)) {:event {:index 0 :cycle-0-index 0}}) + ((on-event (cyi?)) {:event {:index 1 :cycle-0-index 0}}) + ((on-event (cyi?)) {:event {:index 1 :cycle-0-index 1}}) + ((on-event (cyi?)) {:event {:index 1 :cycle-0-index 0} + :voice {:durs [1 1 1] :cycle-len 3}})) (defmacro on-event "Provides @@ -128,7 +135,6 @@ ~'at-index (partial at-index* ~'index) ~'at-i ~'at-index] ~@forms))) - (comment (macroexpand-1 '(on-event (at-index [1 2 3]))) ((on-event (at-index #(- 1 %) [1 2 3])) {:data {:index 1}}) From ee931104e3484c74442342d5a0e26fd705f3ed03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Diego=20Villase=C3=B1or?= Date: Wed, 30 Apr 2025 00:29:12 -0600 Subject: [PATCH 7/7] [ci] add ci workflow --- .github/workflows/ci.yml | 39 +++++++++++++++++++++++++++++++++++++++ deps.edn | 7 ++++++- 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e6a34d2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +name: Test Suite + +on: + push: + branches: [ main, dev ] + pull_request: + branches: [ main, dev ] + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Clojure CLI + uses: DeLaGuardo/setup-clojure@10.1 + with: + cli: latest + + - name: Cache Maven dependencies + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('deps.edn') }} + restore-keys: | + ${{ runner.os }}-m2- + + - name: Cache gitlibs + uses: actions/cache@v3 + with: + path: ~/.gitlibs + key: ${{ runner.os }}-gitlibs-${{ hashFiles('deps.edn') }} + restore-keys: | + ${{ runner.os }}-gitlibs- + + - name: Run tests + run: clojure -M:test diff --git a/deps.edn b/deps.edn index b453307..5501577 100644 --- a/deps.edn +++ b/deps.edn @@ -5,4 +5,9 @@ org.clojure/test.check {:mvn/version "0.10.0"} com.taoensso/timbre {:mvn/version "4.10.0"} overtone/overtone {:mvn/version "0.10.6"} #_{:local/root "../overtone" :deps/manifest :deps} - org.clojure/tools.namespace {:mvn/version "1.3.0"}}} + org.clojure/tools.namespace {:mvn/version "1.3.0"}} + :aliases {:test {:extra-paths ["test"] + :extra-deps {io.github.cognitect-labs/test-runner + {:git/tag "v0.5.1" :git/sha "dfb30dd"}} + :main-opts ["-m" "cognitect.test-runner"] + :exec-fn cognitect.test-runner.api/test}}}