(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