diff options
| author | Jake Zerrer <him@jakezerrer.com> | 2025-10-14 10:56:15 -0400 |
|---|---|---|
| committer | Jake Zerrer <him@jakezerrer.com> | 2025-10-14 14:49:17 -0400 |
| commit | a6e52696756056ccaf3a517a96b01f6756837d2f (patch) | |
| tree | 578b8743abdd6e8211d442ddd4da2367e248b4ef /src | |
| parent | 408d6a9f49f4ecac6abccfd993e72a44b1bc8103 (diff) | |
Toggle midi on and off
Diffstat (limited to 'src')
| -rw-r--r-- | src/.main.clj.swp | bin | 0 -> 16384 bytes | |||
| -rw-r--r-- | src/main.clj | 164 |
2 files changed, 125 insertions, 39 deletions
diff --git a/src/.main.clj.swp b/src/.main.clj.swp Binary files differnew file mode 100644 index 0000000..b91b1b6 --- /dev/null +++ b/src/.main.clj.swp diff --git a/src/main.clj b/src/main.clj index eb0e2d2..7674cc1 100644 --- a/src/main.clj +++ b/src/main.clj @@ -2,30 +2,56 @@ (:require [missionary.core :as m] [clojure.set :refer [difference union]])) +(def >portal + (m/signal + (m/ap + (try + (m/?< + (m/observe + (fn [cb] + ((m/via m/blk ((requiring-resolve 'portal.api/open))) {} {}) + (cb :open) + (fn [] + ((m/via m/blk + ((requiring-resolve 'portal.api/close)) + ((requiring-resolve 'portal.api/clear))) {} {}))))) + (catch missionary.Cancelled _ + (m/amb)))))) + ;; How many times per second are output continuous values sampled and turned ;; into events? (def sample-rate (atom 30)) -(defn set-sample-rate [v] (reset! sample-rate v)) +(defn set-sample-rate + "Change the output 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))) -(def playback-enabled? (atom true)) +(def midi-enabled? (atom true)) -(defn enable-playback [] - (reset! playback-enabled? true)) +(defn enable-midi + "Enable midi out." + [] + (reset! midi-enabled? true)) -(defn disable-playback [] - (reset! playback-enabled? false)) +(defn disable-midi + "Stop all active notes and disable midi out." + [] + (reset! midi-enabled? false)) -(defn play-notes [& v] - (when @playback-enabled? +(defn play-notes + "Play notes." + [& v] + (when @midi-enabled? (swap! notes-on union (into #{} v)))) -(defn stop-notes [& v] - (when @playback-enabled? +(defn stop-notes + "Stop notes." + [& v] + (when @midi-enabled? (swap! notes-on difference (into #{} v)))) (def clock @@ -41,8 +67,9 @@ :tick (recur))))) -;; convert the continuous time >notes-on flow to a series of discrete midi note on and off events (def output + "Convert the continuous time >notes-on flow to a series of discrete midi note + on and off events." (m/eduction (comp (remove #(= (select-keys % [:note-on :note-off]) {:note-on #{} :note-off #{}})) (dedupe)) @@ -61,19 +88,22 @@ >notes-on clock)))) -(def toggle-playback +(def process-midi-toggle-events + "Listen for changes on midi-enabled? + When playback is disabled, send a note-off event for each active note + and then zero out notes-on." (m/ap - (let [local-playback-enabled? (atom nil) + (let [local-midi-enabled? (atom nil) local-active (atom #{}) [tag value] (m/amb= - [:playback-enabled? (m/?< (m/watch playback-enabled?))] + [:midi-enabled? (m/?< (m/watch midi-enabled?))] [:note-event (m/?< output)])] (case tag - :playback-enabled? - (let [playback-enabled? value + :midi-enabled? + (let [midi-enabled? value active @local-active] - (reset! local-playback-enabled? playback-enabled?) - (if (not playback-enabled?) + (reset! local-midi-enabled? midi-enabled?) + (if (not midi-enabled?) (do (reset! notes-on #{}) (reset! local-active #{}) @@ -85,31 +115,87 @@ (let [{:keys [active] :as note-event} value] (reset! local-active active) - (if @local-playback-enabled? + (if @local-midi-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) {} {}))))) +(defonce engine (atom nil)) -(defn stop-process [] - (disable-playback) - (@process) - (reset! process nil)) +(def set->midi-events + "Convert set representation of notes to midi events" + (m/ap + (let [{:keys [note-on note-off]} (m/?< process-midi-toggle-events)] + (m/amb= + (loop [notes note-on] + (if (first notes) + (m/amb [:note-on (first notes)] + (recur (rest notes))) + (m/amb))) + (loop [notes note-off] + (if (first notes) + (m/amb [:note-off (first notes)] + (recur (rest notes))) + (m/amb))))))) + +(def enable-portal-submission? (atom false)) +(def >enable-portal-submission? (m/watch enable-portal-submission?)) + +(defn enable-portal-submission [] + (reset! enable-portal-submission? true)) + +(defn disable-portal-submission [] + (reset! enable-portal-submission? false)) + +(def tap-flow + (m/ap + (let [local-enable-portal-submission? (atom nil) + [tag value] + (m/amb= [:enable-portal-submission? (m/?< >enable-portal-submission?)] + [:event (m/?< set->midi-events)])] + (case tag + :enable-portal-submission? + (do + (reset! local-enable-portal-submission? value) + (when value + (m/?< >portal)) + (m/amb)) + :event + (do + (when @local-enable-portal-submission? + (m/? (m/via m/blk ((requiring-resolve 'portal.api/submit) value)))) + value))))) + +(defn start-engine + "Start playback engine." + [] + (when (not @engine) + (reset! engine + (do (enable-midi) + ((m/reduce prn tap-flow) {} {}))))) + +(defn stop-engine + "Stop playback engine." + [] + (disable-midi) + (when @engine + (@engine) + (reset! engine nil))) (comment - (start-process) - (stop-process) - (set-sample-rate 1) - (play-notes 1 2 3 4 5) - (stop-notes 3) + (start-engine) + (play-notes 1 2 3 4 5) - (stop-notes 1 2 3 4 5) + (stop-notes 4 5) + (enable-portal-submission) + (play-notes 1 2 3 4) + + (disable-midi) + (play-notes 1 2 3 4) + (disable-portal-submission) + + (enable-midi) + (play-notes 1 2 3 4) + (play-notes 6 7 8) + (play-notes 100) - (enable-playback) - (disable-playback)) + (stop-engine)) |
