diff options
Diffstat (limited to 'src/midi.clj')
| -rw-r--r-- | src/midi.clj | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/src/midi.clj b/src/midi.clj new file mode 100644 index 0000000..ca70273 --- /dev/null +++ b/src/midi.clj @@ -0,0 +1,125 @@ +(ns midi + (:require [missionary.core :as m]) + (:import [javax.sound.midi MidiSystem Receiver ShortMessage MidiDevice$Info MidiDevice Transmitter])) + +(def >midi-devices + (m/stream + (m/ap + (loop [] + (m/amb= (MidiSystem/getMidiDeviceInfo) + (do + (m/? (m/sleep 5000)) + (recur))))))) + +;; NOTE: Seems that there is a JVM bug that prevents device rescanning +;; +(def >midi-device-info + "A flow of maps of device name -> device properties" + (m/signal + (m/eduction (dedupe) + (m/ap (let [devices (m/?< >midi-devices)] + (into {} (map (fn [^MidiDevice$Info d] + [(.getName d) + {:description (.getDescription d) + :vendor (.getVendor d) + :version (.getVersion d) + :device-info d}]) devices))))))) + +(defn >device-info + [device-name] + (m/ap (let [device-info (m/?< >midi-device-info)] + (get-in device-info [device-name :device-info])))) + +(defn >midi-messages + "A flow of java midi messages" + [^MidiDevice device] + (m/stream + (m/ap + (let [^Transmitter transmitter (m/? (m/via m/blk (.getTransmitter device))) + transmit (atom nil) + >transmit (m/eduction (filter some?) (m/watch transmit)) + receiver (reify Receiver + (send [_this midi-message _timestamp] + (println "HI") + (reset! transmit midi-message)) + ;; TODO: Close + (close [this]))] + (try + (println "Connecting to transmitter") + (m/? (m/via m/blk (.setReceiver transmitter receiver))) + (println "Connected to transmitter") + (m/?< >transmit) + (finally + (println "Disconnecting from transmitter") + (m/? (m/compel (m/via m/blk (.close receiver)))) + (println "Disconnected from transmitter"))))))) + +(defn >device + "Returns a device for given device name. Returns nil if device not found." + [device-name with-device] + (m/ap + (when-let [device-info (m/?< (>device-info device-name))] + (let [^MidiDevice device (MidiSystem/getMidiDevice ^MidiDevice$Info device-info)] + ;; Essential problem: Combining taking element from flow to put in + ;; conditional, and cleaning up in catch. + ;; NOTE: You need the MidiDevice type hint when you call open and close! + (try + (println "Opening device") + (m/? (m/via m/blk (.open device))) + (println "Device opened") + (m/?< (with-device device)) + (finally + (println "Closing device") + (m/compel (m/? (m/via m/blk (.close device)))) + (println "Device closed") + )))))) + +(defn new-device + "Generate a new midi device. + Currently, this is a map of channel-num to [atom signal]." + [] + (into {} (map (fn [i] [i (let [v (atom #{})] [v (m/signal (m/watch v))])]) (range 0 127)))) + +(defn >midi-messages->ch-stream + [>midi-messages] + (m/signal + (m/ap + (let [device (new-device)] + (m/amb= device + (do + (let [v (m/?< >midi-messages)] + (cond (instance? ShortMessage v) + (let [channel (.getChannel ^ShortMessage v) + command (.getCommand ^ShortMessage v) + data-1 (.getData1 ^ShortMessage v)] + (cond (= command ShortMessage/NOTE_ON) + (swap! (first (get device channel)) conj data-1) + (= command ShortMessage/NOTE_OFF) + (swap! (first (get device channel)) disj data-1))) + :else :other)) + (m/amb))))))) + +(defn >ch-stream [>device ch] + (m/cp (m/?< (second (get >device ch))))) + +(def bus-sel (atom nil)) +(def >bus-sel (m/eduction (dedupe) (m/watch bus-sel))) +(reset! bus-sel "Bus 1") +#_(reset! bus-sel "Bus 2") +#_(reset! bus-sel nil) + +(def run + (m/ap + (prn (m/?< (>device (m/?< >bus-sel) + (fn [v] (m/ap (m/amb (m/?< v))))))) + )) + +(def close ((m/reduce prn {} run) {} {})) +#_ +(close) + +;; OH! You hreally have to think about the supervision tree at all times +;; It informs your function composition +;; +;;It helps to write in a continuation style - see `with-messages` +;;>device is my most mature fn |
