{:tracks-using-playlists? nil, :carabiner {:port 17000, :latency 1, :bars true}, :triggers [{:bar true, :start "Start", :channel 1, :start-stop false, :note 127, :gear false, :stop true, :expressions {:beat ";; We can only run when a valid MIDI output is chosen, and the TimeFinder is running.\n(when (and trigger-output track-time-reached)\n ;; Record that this beat has been handled, and the Tracked Update expression can ignore it.\n (swap! midi-cue-times assoc-in [:sent device-number] beat-number)\n ;; Send the MIDI cues, if any, falling on this beat.\n (send-midi-cues-near-time track-time-reached device-number trigger-output trigger-channel))", :deactivation ";; Clear record of last beat handled since the player is stopping.\n;; If we restart in this same location, we should evaluate cues again.\n(swap! midi-cue-times update :sent dissoc device-number)", :activation "", :tracked ";; We can only run when playing, a valid MIDI output is chosen, and the TimeFinder is running.\n(when (and playing? trigger-output track-time-reached)\n ;; Do nothing if the current beat has already been handled.\n (when (not= beat-number (get-in @midi-cue-times [:sent device-number]))\n (swap! midi-cue-times assoc-in [:sent device-number] beat-number) ; Note this beat's been handled.\n ;; Send the MIDI cues, if any, for the point where playback began.\n ;; We assume playback began at the start of the current beat.\n (let [grid (.getLatestBeatGridFor beatgrid-finder device-number)\n started (.getTimeWithinTrack grid beat-number)]\n (send-midi-cues-near-time started device-number trigger-output trigger-channel))))"}, :comment "Cue-driven MIDI Sender", :outputs #beat_link_trigger.util.MidiChoice{:full-name "CoreMIDI4J - IAC Driver Bus 1"}, :send true, :players #beat_link_trigger.util.PlayerChoice{:number 0}, :enabled "Always", :message "Custom"}], :nrepl {:cider false}, :expressions {:setup "(.addTrackMetadataListener metadata-finder midi-cue-indexer)", :shutdown "(.removeTrackMetadataListener metadata-finder midi-cue-indexer)", :shared "(defonce ^{:doc \"Holds a map from player number to a map of cue times for that player.\n The cue time maps are indexed by track position (in milliseconds),\n and their values are sets of MIDI note numbers to send when we reach\n a beat that is within 50 milliseconds of that time. This map is\n built by `find-midi-cues` below whenever the track metadata for a\n player changes.\"}\n midi-cue-times (atom {}))\n\n(defn find-midi-cues\n \"Scans all the cues and loops found in the supplied track metadata\n looking for any that contain the string MIDI: followed immediately\n by a number. Returns a map whose keys are the track time at which\n each such cue or loop begins, and whose values are sets of the\n number that was found in the cue name(s) that started at that time.\n If there is no track metadata, or it has no cue list, returns\n `nil`.\"\n [^TrackMetadata md]\n (when md\n (when-let [cue-list (.getCueList md)]\n (reduce (fn [result cue]\n (if-let [[_ n] (re-find #\"MIDI:(\\d+)\" (.-comment cue))] ; This cue name matches.\n (let [note (Long/valueOf n)]\n (if (<= 1 note 127) ; Make sure the number is a valid MIDI note\n (update result (.-cueTime cue) (fnil conj #{}) (Long/valueOf n)) ; Yes, add it.\n (do ; No, log a warning with the cue name and ignore it.\n (timbre/warn \"Ignoring cue with invalid MIDI note number\" (.-comment cue))\n result)))\n result))\n {} (.-entries cue-list)))))\n\n(def midi-cue-indexer\n \"Responds to the coming and going of track metadata, and updates our\n list of cue-defined beats on which MIDI messages need to be sent.\"\n (reify org.deepsymmetry.beatlink.data.TrackMetadataListener\n (metadataChanged [this md-update]\n (swap! midi-cue-times assoc (.player md-update) (find-midi-cues (.metadata md-update))))))\n\n(defn send-midi-cues-near-time\n \"Finds all MIDI cues close enough to the specified time for the\n specified device and sends the corresponding MIDI notes on the\n specified MIDI output and channel.\"\n [time device-number midi-output midi-channel]\n (doseq [[_ notes] (filter (fn [[cue-time]] (> 50 (Math/abs (- time cue-time))))\n (get @midi-cue-times device-number))]\n (doseq [note notes] ; Send note-on messages for each note specified by one of the cues we reached.\n (midi/midi-note-on midi-output note 127 (dec midi-channel)))\n (doseq [note notes] ; And then immediately send the corresponding note-off messages for them too.\n (midi/midi-note-off midi-output note (dec midi-channel)))))", :online "(triggers/show-player-status)"}, :beat-link-trigger-version "0.6.2-SNAPSHOT-60-0x63c4-DIRTY", :window-positions {:triggers [32 70 826 185], "show-/Users/jim/git/beat-link-trigger/doc/non-git/Shows/Test.bls" [27 402 1293 754], "show-/Users/jim/git/beat-link-trigger/doc/modules/ROOT/assets/attachments/OrangePiSMPTE.bls" [506 897 900 600], :nrepl [284 95], :player-status [892 54], :carabiner [676 268], :waveform-detail-1 [482 143 600 200]}, :send-status? false}