Skip to content

Commit

Permalink
Add UI for attaching metadata archives for Opus Quad
Browse files Browse the repository at this point in the history
  • Loading branch information
brunchboy committed Apr 28, 2024
1 parent 1ebe2eb commit 843c6fe
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 36 deletions.
26 changes: 7 additions & 19 deletions src/beat_link_trigger/BeatLinkTrigger.clj
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
(ns beat-link-trigger.BeatLinkTrigger
"This is configured as the main class which is run when the jar is
executed. Its task is to check whether the Java version is high
enough to support running beat-link-trigger, and if so, proceed to
initializing it. Otherwise, display a dialog explaining the issue
and offering to take the user to the download page."
executed. Its task is to parse command-line arguments and provide
help about them if requested, before passing the parsed results to
`beat-link-trigger.core/start`."
(:require [clojure.java.browse :as browse]
[clojure.string]
[clojure.tools.cli :as cli]
Expand All @@ -20,7 +19,7 @@
:default-desc ""
:update-fn conj]
["-S" "--suppress" "Do not reopen shows from previous run"]
[nil "--reset FILE" "Write saved configuration to file and clear it"]
[nil "--reset FILE" "Write saved configuration to file and forget it"]
["-c" "--config FILE" "Use specified configuration file"]
["-h" "--help" "Display help information and exit"]])

Expand Down Expand Up @@ -57,20 +56,9 @@
(System/exit status))

(defn -main
"Check the Java version and either proceed or offer to download a newer one."
"Parse and handle any command-line arguments, then proceed with startup
if appropriate."
[& args]
(when (< (Float/valueOf (System/getProperty "java.specification.version")) 1.8)
(let [options (to-array ["Download" "Cancel"])
choice (JOptionPane/showOptionDialog
nil
(str "To run BeatLinkTrigger you will need to install a current\n"
"Java Runtime Environment, or at least Java 1.8.")
"Newer Java Version Required"
JOptionPane/YES_NO_OPTION JOptionPane/ERROR_MESSAGE nil
options (aget options 0))]
(when (zero? choice) (browse/browse-url "https://openjdk.java.net")))
(System/exit 1))

(let [{:keys [options arguments errors summary]} (cli/parse-opts args cli-options)]

;; Handle help and error conditions
Expand All @@ -80,5 +68,5 @@
(seq arguments) (exit 1 (str "Unexpected arguments: " (clojure.string/join ", " arguments) "\n\n"
(usage summary))))

;; We have a recent enough Java version that it is safe to load the user interface.
;; Proceed to load the user interface and continue startup.
((requiring-resolve 'beat-link-trigger.core/start) options)))
2 changes: 1 addition & 1 deletion src/beat_link_trigger/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@
(seesaw/invoke-now
(prefs/load-from-file file))
(do
(println "--config: file" reset "does not have required extension:"
(println "--config: file" config "does not have required extension:"
(str "." (util/extension-for-file-type :configuration)))
(System/exit 1)))))

Expand Down
82 changes: 69 additions & 13 deletions src/beat_link_trigger/players.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
[clojure.core.async :as async :refer [<! >!!]]
[clojure.java.io]
[clojure.string]
[seesaw.chooser :as chooser]
[seesaw.core :as seesaw]
[seesaw.mig :as mig]
[taoensso.timbre :as timbre])
Expand All @@ -18,7 +19,7 @@
DeviceAnnouncement DeviceAnnouncementListener DeviceFinder DeviceUpdate
LifecycleListener MediaDetailsListener VirtualCdj]
[org.deepsymmetry.beatlink.data AlbumArt AlbumArtListener AnalysisTagFinder AnalysisTagListener
ArtFinder MetadataFinder MountListener SearchableItem SlotReference TimeFinder TrackMetadata
ArtFinder MetadataFinder MountListener OpusProvider SearchableItem SlotReference TimeFinder TrackMetadata
TrackMetadataListener TrackPositionUpdate WaveformDetailComponent WaveformFinder WaveformPreviewComponent]
[jiconfont.icons.font_awesome FontAwesome]
[jiconfont.swing IconFontSwing]))
Expand Down Expand Up @@ -87,12 +88,24 @@
singleton."
(ArtFinder/getInstance))

(def ^OpusProvider opus-provider
"A convenient reference to the [Beat Link
`OpusProvider`](https://deepsymmetry.org/beatlink/apidocs/org/deepsymmetry/beatlink/data/OpusProvider.html)
singleton."
(OpusProvider/getInstance))

(defn- sending-status?
"Checks whether we are currently sending status packets, which is
required to reliably obtain metadata for non-rekordbox tracks."
[]
((resolve 'beat-link-trigger.triggers/real-player?)))

(defn- opus-quad?
"Checks whether we are operating in Opus Quad compatiblity
mode rather than Pro DJ Link mode."
[]
((resolve 'beat-link-trigger.triggers/opus-quad?)))

(def magnify-cursor
"A custom cursor that indicates something can be magnified."
(delay (.createCustomCursor (java.awt.Toolkit/getDefaultToolkit)
Expand Down Expand Up @@ -463,19 +476,58 @@
(seesaw/config! bank-label :visible? true :text (util/track-bank-name tag)))
(seesaw/config! [mood-label bank-label] :visible? false))))

(defn opus-slot-state
"Describes the metadata archive mounted for an Opus Quad slot, if any."
[^CdjStatus$TrackSourceSlot slot]
(let [slot-reference (SlotReference/getSlotReference 9 slot)]
(if-let [filesystem (.findFilesystem opus-provider slot-reference)]
(str filesystem)
"No metadata archive.")))

(defn update-slot-states
"Called when the metadata archive attached to an Opus Quad slot
changes, to update the name associated with that slot for all
players on it."
[^CdjStatus$TrackSourceSlot slot]
(let [state (opus-slot-state slot)
label (util/case-enum slot
CdjStatus$TrackSourceSlot/USB_SLOT :#usb-name
CdjStatus$TrackSourceSlot/SD_SLOT :#sd-name)]
(doseq [player (seesaw/config (seesaw/select @@#'beat-link-trigger.players/player-window [:#players]) :items)]
(seesaw/text! (seesaw/select player [label]) state))))

(defn- mount-media
"Has the user choose a metadata archive, then attaches it for the
specified Opus Quad slot."
[^CdjStatus$TrackSourceSlot slot]
(when-let [file (chooser/choose-file @player-window :type "Attach Metadata Archive"
:all-files? false
:filters [["Beat Link Metadata Archive"
[(util/extension-for-file-type :metadata-archive)]]])]
(.attachMetadataArchive opus-provider file slot)
(update-slot-states slot)))

(defn- slot-popup
"Returns the actions that should be in a popup menu for a particular
player media slot. Arguments are the player number, slot
keyword (`:usb` or `:sd`), and the event triggering the popup."
[n slot e]
(when (seesaw/config e :enabled?)
(let [slot (case slot
:usb CdjStatus$TrackSourceSlot/USB_SLOT
:sd CdjStatus$TrackSourceSlot/SD_SLOT)
:usb CdjStatus$TrackSourceSlot/USB_SLOT
:sd CdjStatus$TrackSourceSlot/SD_SLOT)
slot-reference (SlotReference/getSlotReference (int n) slot)]
(filter identity
[(seesaw/action :handler (fn [_] (track-loader/show-dialog slot-reference))
:name "Load Track from Here on a Player")]))))
(if (opus-quad?)
(filter identity
[(seesaw/action :handler (fn [_] (mount-media slot))
:name "Attach Metadata Archive")
(when (.findFilesystem opus-provider slot-reference)
(seesaw/action :handler (fn [_]
(.attachMetadataArchive opus-provider nil slot)
(update-slot-states slot))
:name "Remove Metadata Archive"))])
[(seesaw/action :handler (fn [_] (track-loader/show-dialog slot-reference))
:name "Load Track from Here on a Player")]))))

(defn- handle-preview-move
"Mouse movement listener for a wave preview component; shows a tool
Expand Down Expand Up @@ -517,14 +569,18 @@
title-label (seesaw/label :text "[track metadata not available]"
:font (Font. "serif" Font/ITALIC 14) :foreground :yellow)
artist-label (seesaw/label :text "" :font (Font. "serif" Font/BOLD 12) :foreground :green)
usb-gear (seesaw/button :id :usb-gear :icon (seesaw/icon "images/Gear-outline.png") :enabled? false
usb-gear (seesaw/button :id :usb-gear :icon (seesaw/icon "images/Gear-outline.png") :enabled? (opus-quad?)
:popup (partial slot-popup n :usb))
usb-label (seesaw/label :id :usb-label :text "USB:")
usb-name (seesaw/label :id :usb-name :text "Empty")
sd-gear (seesaw/button :id :sd-gear :icon (seesaw/icon "images/Gear-outline.png") :enabled? false
usb-name (seesaw/label :id :usb-name :text (if (opus-quad?)
(opus-slot-state CdjStatus$TrackSourceSlot/USB_SLOT)
"Empty"))
sd-gear (seesaw/button :id :sd-gear :icon (seesaw/icon "images/Gear-outline.png") :enabled? (opus-quad?)
:popup (partial slot-popup n :sd))
sd-label (seesaw/label :id :sd-label :text "SD:")
sd-name (seesaw/label :id :sd-name :text "Empty")
sd-name (seesaw/label :id :sd-name :text (if (opus-quad?)
(opus-slot-state CdjStatus$TrackSourceSlot/SD_SLOT)
"Empty"))
detail (when @should-show-details (WaveformDetailComponent. (int n)))
zoom-slider (when @should-show-details
(seesaw/slider :id :zoom :min 1 :max 32 :value 4
Expand Down Expand Up @@ -698,10 +754,10 @@

(defn- update-slot-labels
"Updates the USB/SD labels of a player cell in case the device is an
XDJ-XZ, which has two USB slots instead."
XDJ-XZ or Opus Quad, which has two USB slots instead."
[cell ^DeviceAnnouncement device]
(seesaw/invoke-soon
(if (= (.getDeviceName device) "XDJ-XZ")
(if (#{"XDJ-XZ" "OPUS-QUAD"} (.getDeviceName device))
(do
(seesaw/value! (seesaw/select cell [:#sd-label]) "USB 1:")
(seesaw/value! (seesaw/select cell [:#usb-label]) "USB 2:"))
Expand Down Expand Up @@ -773,7 +829,7 @@
(update-slot-labels player device)
player))
players)
grid (seesaw/grid-panel :id players :columns (player-columns visible-players))]
grid (seesaw/grid-panel :id :players :columns (player-columns visible-players))]

(seesaw/config! grid :items (or (seq visible-players) [no-players]))
grid))
Expand Down
28 changes: 25 additions & 3 deletions src/beat_link_trigger/triggers.clj
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
^JCheckBoxMenuItem []
(seesaw/select @trigger-frame [:#online]))

(defn- opus-menu-item
"Helper function to find the Opus Quad menu item, which is disabled
when online."
^JCheckBoxMenuItem []
(seesaw/select @trigger-frame [:#opus-quad]))

(def ^DeviceFinder device-finder
"A convenient reference to the [Beat Link
`DeviceFinder`](https://deepsymmetry.org/beatlink/apidocs/org/deepsymmetry/beatlink/DeviceFinder.html)
Expand Down Expand Up @@ -99,7 +105,7 @@
"Create the values to assign the trigger preferences map."
[]
(merge {:global true}
(select-keys (prefs/get-preferences) [:send-status? :tracks-using-playlists?])))
(select-keys (prefs/get-preferences) [:send-status? :opus-quad? :tracks-using-playlists?])))

(defonce ^{:private true
:doc "Global trigger configuration and expressions."}
Expand All @@ -124,6 +130,13 @@
[]
(boolean (:send-status? @trigger-prefs)))

(defn opus-quad?
"Checks whether the user wants to operate in a mode that is compatible
with the Opus Quad standalone hardware instead of a real Pro DJ Link
network."
[]
(boolean (:opus-quad? @trigger-prefs)))

(defn- enabled?
"Check whether a trigger is enabled."
([trigger]
Expand Down Expand Up @@ -1181,7 +1194,7 @@
:window-positions @util/window-positions}
(when-let [exprs (:expressions @trigger-prefs)]
{:expressions exprs})
(select-keys @trigger-prefs [:tracks-using-playlists? :send-status?]))))
(select-keys @trigger-prefs [:tracks-using-playlists? :send-status? :opus-quad?]))))

(defonce ^{:private true
:doc "The menu action which saves the configuration to the preferences."}
Expand Down Expand Up @@ -1436,6 +1449,7 @@
(.doClick ^JRadioButtonMenuItem (seesaw/select @trigger-frame [(if (:tracks-using-playlists? m)
:#track-position :#track-id)]))
(.setSelected ^JMenuItem (seesaw/select @trigger-frame [:#send-status]) (true? (:send-status? m)))
(.setSelected ^JMenuItem (seesaw/select @trigger-frame [:#opus-quad]) (true? (:opus-quad? m)))
(when-let [exprs (:expressions m)]
(swap! trigger-prefs assoc :expressions exprs)
(doseq [[kind expr] (editors/sort-setup-to-front exprs)]
Expand Down Expand Up @@ -1604,6 +1618,7 @@
online-item (seesaw/checkbox-menu-item :text (online-menu-name) :id :online :selected? (util/online?))
real-item (seesaw/checkbox-menu-item :text "Use Real Player Number?" :id :send-status
:selected? (real-player?))
opus-item (seesaw/checkbox-menu-item :text "Opus Quad Mode?" :id :opus-quad :selected? (opus-quad?))
bg (seesaw/button-group)
track-submenu (seesaw/menu :text "Default Track Description"
:items [(seesaw/radio-menu-item :text "recordbox id [player:slot]" :id :track-id
Expand All @@ -1628,6 +1643,12 @@
(do
(carabiner/cancel-full-sync)
(.setSendingStatus virtual-cdj false)))))
(seesaw/listen opus-item :item-state-changed
(fn [^java.awt.event.ItemEvent e]
(let [opus-mode (= (.getStateChange e) java.awt.event.ItemEvent/SELECTED)]
(when opus-mode (seesaw/config! real-item :selected? false))
(seesaw/config! real-item :enabled? (not opus-mode))
(swap! trigger-prefs assoc :opus-quad? opus-mode))))
(seesaw/menubar :items [(seesaw/menu :text "File"
:items (concat [@save-action @save-as-action @load-action
(seesaw/separator) new-show-action open-show-action
Expand All @@ -1643,7 +1664,7 @@
:id :triggers-menu)

(seesaw/menu :text "Network"
:items [online-item real-item
:items [online-item real-item opus-item
(seesaw/separator)
@open-simulator-item
@player-status-action @load-track-action @load-settings-action
Expand Down Expand Up @@ -1717,6 +1738,7 @@
(seesaw/config! [@playlist-writer-action @load-track-action @load-settings-action @player-status-action]
:enabled? (util/online?))
(.setText (online-menu-item) (online-menu-name))
(seesaw/config! (opus-menu-item) :enabled? (not (util/online?)))
(if (util/online?)
(seesaw/hide! @open-simulator-item)
(seesaw/show! @open-simulator-item))
Expand Down

0 comments on commit 843c6fe

Please sign in to comment.