summaryrefslogtreecommitdiff
path: root/src/midi.clj
blob: 4c10de08fcfc39e4c4c169001e229d8981d4eb08 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
(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
   (let [device-info
         (m/?< (>device-info device-name))
         ^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)
                         >midi-messages)))
    ))

(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