summaryrefslogtreecommitdiff
path: root/src/midi.clj
diff options
context:
space:
mode:
Diffstat (limited to 'src/midi.clj')
-rw-r--r--src/midi.clj125
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