diff options
Diffstat (limited to 'src/main.clj')
| -rw-r--r-- | src/main.clj | 114 |
1 files changed, 92 insertions, 22 deletions
diff --git a/src/main.clj b/src/main.clj index fdb306f..eb0e2d2 100644 --- a/src/main.clj +++ b/src/main.clj @@ -1,45 +1,115 @@ (ns main (:require [missionary.core :as m] - [clojure.set :refer [difference]])) + [clojure.set :refer [difference union]])) ;; How many times per second are output continuous values sampled and turned ;; into events? -(def sample-rate (atom 1)) +(def sample-rate (atom 30)) + +(defn set-sample-rate [v] (reset! sample-rate v)) ;; Temporary atom to explore the concept of note state as a continuous value (def notes-on (atom #{})) (def >notes-on (m/signal (m/watch notes-on))) -(defn play-note [v] (swap! notes-on conj v)) +(def playback-enabled? (atom true)) + +(defn enable-playback [] + (reset! playback-enabled? true)) + +(defn disable-playback [] + (reset! playback-enabled? false)) -(defn stop-note [v] (swap! notes-on disj v)) +(defn play-notes [& v] + (when @playback-enabled? + (swap! notes-on union (into #{} v)))) + +(defn stop-notes [& v] + (when @playback-enabled? + (swap! notes-on difference (into #{} v)))) (def clock (m/ap (loop [] (m/amb + ;; TODO: This seems to be emitting twice per cycle + ;; TODO: Currently, there will be latency when changing the sample rate + ;; due to having to wait for the most recent sleep to complete + ;; Update clock to be reactive on sample-rate, too (m/? (m/sleep (/ 1000 @sample-rate))) :tick (recur))))) -#_(play-note 1) -#_(stop-note 1) - ;; convert the continuous time >notes-on flow to a series of discrete midi note on and off events (def output - (m/eduction (map (fn [{:keys [note-on note-off]}] {:note-on note-on :note-off note-off})) - (m/reductions (fn [{:keys [active note-on note-off]} [curr _]] - {:note-on (difference (difference curr active) note-on) - :note-off (difference (difference active curr) note-off) - :active curr}) - #{} - (m/sample - vector - >notes-on - clock)))) - -(def cancel - ((m/reduce prn output) {} {})) - -(cancel) + (m/eduction + (comp (remove #(= (select-keys % [:note-on :note-off]) {:note-on #{} :note-off #{}})) + (dedupe)) + (m/reductions (fn [{:keys [active note-on note-off]} [curr _]] + {:note-on (difference (difference curr active) note-on) + :note-off (difference (difference active curr) note-off) + :active curr}) + {:note-on #{} + :note-off #{} + :active #{}} + ;; This actually does a bunch of unnecessary work + ;; What we really want is to sample at _at most_ sample rate, + ;; but not at all if nothing has changed + (m/sample + vector + >notes-on + clock)))) + +(def toggle-playback + (m/ap + (let [local-playback-enabled? (atom nil) + local-active (atom #{}) + [tag value] (m/amb= + [:playback-enabled? (m/?< (m/watch playback-enabled?))] + [:note-event (m/?< output)])] + (case tag + :playback-enabled? + (let [playback-enabled? value + active @local-active] + (reset! local-playback-enabled? playback-enabled?) + (if (not playback-enabled?) + (do + (reset! notes-on #{}) + (reset! local-active #{}) + {:note-on #{} + :note-off active + :active #{}}) + (m/amb))) + :note-event + (let [{:keys [active] + :as note-event} value] + (reset! local-active active) + (if @local-playback-enabled? + note-event + (m/amb))))))) + +(defonce process (atom nil)) + +(defn start-process [] + (when (not @process) + (reset! process + (do (enable-playback) + ((m/reduce prn toggle-playback) {} {}))))) + +(defn stop-process [] + (disable-playback) + (@process) + (reset! process nil)) + +(comment + (start-process) + (stop-process) + (set-sample-rate 1) + (play-notes 1 2 3 4 5) + (stop-notes 3) + (play-notes 1 2 3 4 5) + (stop-notes 1 2 3 4 5) + + (enable-playback) + (disable-playback)) |
