{:beat-link-trigger-version "0.5.5-SNAPSHOT", :triggers [{:bar true, :start "Start", :channel 1, :start-stop false, :note 17, :gear false, :stop false, :expressions {:tracked "(when trigger-active? (set-gm-tempo effective-tempo))"}, :comment " Set chase speed on grandMA2 to tempo of master player when playing", :outputs #beat_link_trigger.util.MidiChoice{:full-name "CoreMIDI4J - Bus 1"}, :send false, :players #beat_link_trigger.util.PlayerChoice{:number 0}, :channel-label "Channel:", :enabled "Always", :message "Custom"}], :expressions {:setup ";; Attempt to connect to the grandMA2 telnet command port.\n;; Edit the variable definitions below to reflect your setup.\n(try\n (let [gm-address \"127.0.0.1\"\n gm-port 30000\n gm-user \"Administrator\"\n gm-password \"admin\"\n gm-speedmaster \"3.1\"\n connect-timeout 5000\n socket-address (InetSocketAddress. gm-address gm-port)\n socket (java.net.Socket.)]\n (.connect socket socket-address connect-timeout)\n (swap! globals assoc :gm-connection {:socket socket :bpm-master gm-speedmaster})\n (future (gm-response-handler))\n (send-gm-command (str \"login \\\"\" gm-user \"\\\" \\\"\" gm-password \"\\\"\")))\n (catch Exception e\n (timbre/error e \"Unable to connect to grandMA2\")))", :shutdown ";; Disconnect from the grandMA2 telnet command port.\n(when-let [socket (get-in @globals [:gm-connection :socket])]\n (.close socket)\n (swap! globals dissoc :gm-connection))", :shared "(defn gm-response-handler\n \"A loop that reads messages from grandMA2 and responds\n appropriately. (Currently we don't respond in any way, but simply\n consume responses as they arrive.)\"\n []\n (try\n (loop [socket (get-in @globals [:gm-connection :socket])]\n (when (and socket (not (.isClosed socket)))\n (let [buffer (byte-array 1024)\n input (.getInputStream socket)\n n (.read input buffer)]\n (when (pos? n) ; We got data, so the socket has not yet been closed.\n (let [message (String. buffer 0 n \"UTF-8\")]\n (timbre/info \"Received from grandMA2:\" message)\n ;; TODO: Here is where we would analyze and respond if needed; see the ShowXPress example.\n )\n (recur (get-in @globals [:gm-connection :socket]))))))\n (catch Throwable t\n (timbre/error t \"Problem reading from grandMA2, loop aborted.\"))))\n\n(defn send-gm-command\n \"Sends a command message to grandMA2.\"\n [message]\n (let [socket (get-in @globals [:gm-connection :socket])]\n (if (and socket (not (.isClosed socket)))\n (.write (.getOutputStream socket) (.getBytes (str message \"\\r\\n\") \"UTF-8\"))\n (timbre/warn \"Cannot write to grandMA2, no open socket, discarding:\" message))))\n\n(defn set-gm-tempo\n \"Tells grandMA2 the current tempo if it is different than the\n value we last reported. Rounds to the nearest beat per minute\n because the protocol does not accept any fractional values.\n The expected way to use this is to include the following in a\n trigger's Tracked Update Expression:\n\n `(when trigger-active? (set-gm-tempo effective-tempo))`\"\n [bpm]\n (let [bpm (Math/round bpm)\n master (get-in @globals [:gm-connection :bpm-master])]\n (when-not (= bpm (get-in @globals [:gm-connection :bpm]))\n (send-gm-command (str \"SpecialMaster \" master \" At \" bpm))\n (swap! globals assoc-in [:gm-connection :bpm] bpm)\n (timbre/info \"grandMA2 tempo set to\" bpm))))\n\n;; An alternate approach. You would probably only want to use one of set-gm-tempo (above) and\n;; send-gm-beat (below), depending on which works best in your application.\n\n(defn send-gm-beat\n \"Sends a learn command to grandMA2. The expected way to use this is\n to include the following in a trigger's Beat Expresssion:\n\n `(when trigger-active? (send-gm-beat))`\"\n []\n (let [master (get-in @globals [:gm-connection :bpm-master])]\n (send-gm-command (str \"Learn SpecialMaster \" master))))"}, :tracks-using-playlists? nil, :request-metadata? true, :send-status? false}