 (defn set-pad-color
   "Set the color of one of the 64 touch pads to the closest
   approximation available for a desired color."
-  [x y color]
+  [controller x y color]
   {:pre [(<= 0 x 7) (<= 0 y 7)]}
   (let [note (+ 36 x (* y 8))]  ;; Calculate note from grid coordinates
-    (midi/midi-note-on @user-port-out note (velocity-for-color color))))
+    (midi/midi-note-on (:user-port-out controller) note (velocity-for-color color))))
 
 (def monochrome-button-states
   "The control values and modes for a labeled button which does not

  "Render a frame of the welcome animation, or if it is done, start
  the main interface update thread, and terminate the task running the
  animation."
  [counter task]
  (try
    (cond
      (< @counter 8)
      (doseq [y (range 0 (inc @counter))]
        (let [color (com.evocomputing.colors/create-color
                     :h 0 :s 0 :l (max 10 (- 50 (/ (* 50 (- @counter y)) 4))))]
          (set-pad-color 3 y color)
          (set-pad-color 4 y color)))

      (< @counter 12)
      (doseq [x (range 0 (- @counter 7))
              y (range 0 8)]
        (let [color (com.evocomputing.colors/create-color
                     :h 340 :s 100 :l (if (= x (- @counter 8)) 75 50))]
          (set-pad-color (- 3 x) y color)
          (set-pad-color (+ 4 x) y color)))

      (< @counter 15)
      (doseq [y (range 0 8)]
        (let [color (com.evocomputing.colors/create-color
                     :h (* 13 (- @counter 11)) :s 100 :l 50)]
          (set-pad-color (- @counter 7) y color)
          (set-pad-color (- 14 @counter) y color)))

      (= @counter 15)
      (show-labels :bright :amber)
      
      (= @counter 16)
      (doseq [x (range 0 8)]
        (set-track-select-state x :bright :amber))
      
      (= @counter 17)
      (doseq [x (range 0 8)]
        (set-scene-launch-color x (com.evocomputing.colors/create-color :h 45 :s 100 :l 50))
        (set-track-select-state x :bright :red))

      (< @counter 26)
      (doseq [x (range 0 8)]
        (let [color (com.evocomputing.colors/create-color
                     :h (+ 60 (* 40 (- @counter 18))) :s 100 :l 50)]
          (set-pad-color x (- 25 @counter) color)))
      
      (= @counter 26)
      (do
        (show-labels :dim :amber)
        (doseq [x (range 0 8)]
          (set-track-select-state x :off)))

      (= @counter 27)
      (doseq [x (range 0 8)]
          (set-scene-launch-color x off-color))

      (< @counter 37)
      (doseq [x (range 0 8)]
        (set-pad-color x (- 35 @counter) off-color))
      
      :else
      (do
        (clear-interface)
        ;; 
        (at-at/kill @task)))
    (catch Throwable t
      (warn t "Animation frame failed")))

  (swap! counter inc))


(defn welcome-animation
  "Provide a fun animation to make it clear the Push is online."
  []
  (set-display-line 1 (concat (repeat 24 \space) (seq "Welcome toAfterglow")))
  (set-display-line 3 (concat (repeat 27 \space)
                              (seq (str "version" (env  :afterglow-version)))))
  (let [counter (atom 0)
        task (atom nil)]
    (reset! task (at-at/every 30 #(welcome-frame counter task)
                              controllers/pool))))

(defn clear-interface
  "Clears the text display and all illuminated buttons and pads."
  []
  (doseq [line (range 1 5)]
    (clear-display-line line))
  (doseq [x (range 8)]
    (set-track-select-state x :off)
    (set-scene-launch-color x off-color)
    (doseq [y (range 8)]
      (set-pad-color x y off-color)))
  (doseq [[_ button] control-buttons]
    (set-button-state button :off)))


(defn connect-to-push
  "Try to establish the connection to the Ableton Push, if that has
  not already been made, initialize the display, and start the UI
  updater thread. Since SysEx messages are required for updating the
  display, assumes that if you are on a Mac, you have installed
  [mmj](http://www.humatic.de/htools/mmj.htm) to provide a
  working implementation."
  {:doc/format :markdown}
  [& {:keys [prefix] :or {prefix (when (re-find #"Mac" (System/getProperty "os.name"))
                                   "Ableton Push - ")}}]
  (swap! user-port-out #(or % (midi/midi-out (str prefix "User Port"))))
  (swap! live-port-out #(or % (midi/midi-out (str prefix "Live Port"))))
  (clear-interface)
  (welcome-animation))

  {:author "James Elliott"
   :doc/format :markdown}
  (:require [afterglow.controllers :as controllers]
            [afterglow.rhythm :refer :all]
            [afterglow.show :as show]
            [afterglow.util :as util]
            [clojure.math.numeric-tower :as math]
            [environ.core :refer [env]]
            [com.evocomputing.colors :as colors]
            [overtone.midi :as midi]
            [overtone.at-at :as at-at]
            [taoensso.timbre :refer [warn]])
  (:import [java.util Arrays]))

;; TODO: Move globals into a map allocated when binding to a show, so
;;       multiple controllers can be active at once.

(defn velocity-for-color
  "Given a target color, calculate the MIDI note velocity which will
