(ns unheard.clock (:require [missionary.core :as m])) ;; TODO: Put in missionary util (defn poll [init task] (m/ap (m/? (m/?> (m/seed (concat [(m/sp init)] (repeat task))))))) ;; TODO: Put in missionary util (defn feedback [f init & args] (m/ap (m/amb init (let [rdv (m/rdv) x (m/?> (apply f (poll init rdv) args))] (m/amb (do (m/? (rdv x)) (m/amb)) x))))) ;; Return value ;; TODO: Make part of public API (defonce mono-clock-freq (atom 120)) ;; hz (defonce >mono-clock-freq (m/signal (m/watch mono-clock-freq))) (def >mono-clock "This is the base monotonic clock used for driving playback. It makes a best-effort attempt to tick at `mono-clock-freq`. It makes no guarantees that it will tick a given number of times in any unit of time. When `mono-clock-freq` changes, it will wait for the completion of the current tick prior to adopting the new frequency." ;; TODO: Change this behavior. I don't want to wait for ;; the current tick to complete during a frequency change.. (m/signal (m/relieve (feedback (fn [v] (m/ap (let [[t freq] (m/?< (m/latest vector v >mono-clock-freq))] (m/? (m/compel (m/sleep (/ 1000 freq)))) (System/nanoTime)))) (System/nanoTime))))) (comment (def cancel ((m/reduce prn nil >mono-clock) prn prn)) (reset! mono-clock-freq 10) (reset! mono-clock-freq 1) (cancel)) (defonce bpm (atom 120)) (defonce >bpm (m/signal (m/watch bpm))) (def >beat-clock "Counts beats at `bpm`. Guaranteed not to lose or gain time." (m/signal (m/relieve (let [init-beat 1] (m/reductions {} init-beat (m/ap (let [state (object-array 2) _ (aset state 0 (System/nanoTime)) _ (aset state 1 init-beat) [t bpm] (m/?< (m/latest vector >mono-clock >bpm)) last-tick (aget state 0) last-beat (aget state 1) next-tick (+ last-tick (* 100000000000 (/ 1 bpm)))] (if (< next-tick t) (do (aset state 0 next-tick) (aset state 1 (inc last-beat)) (inc last-beat)) (m/amb))))))))) (comment (def cancel ((m/reduce prn nil >beat-clock) prn prn)) (reset! bpm 60) (reset! bpm 120) (reset! mono-clock-freq 120) (reset! mono-clock-freq 1) (cancel)) (defonce numerator (atom 4)) (defonce >numerator (m/signal (m/watch numerator))) (def >measure-clock "Emits measure count. Increases at the end of the current measure. Follows changes to numerator." (m/signal (m/relieve (let [init-measure 1] (m/reductions {} init-measure (m/ap (let [state (object-array 2) last-measure-idx 0 last-downbeat-idx 1 _ (aset state last-measure-idx init-measure) _ (aset state last-downbeat-idx init-measure) [beat numerator] (m/?< (m/latest vector >beat-clock >numerator)) last-measure (aget state last-measure-idx) last-downbeat (aget state last-downbeat-idx) next-downbeat (+ last-downbeat numerator)] (if (<= next-downbeat beat) (do (aset state last-measure-idx (inc last-measure)) (aset state last-downbeat-idx next-downbeat) (inc last-measure)) (m/amb))))))))) (comment (def cancel ((m/reduce prn nil (m/latest vector >beat-clock >measure-clock)) prn prn)) (reset! mono-clock-freq 120) (reset! bpm 120) (reset! bpm -120) (reset! bpm 160) (reset! numerator 2) (reset! numerator 3) (reset! numerator 4) (cancel))