Debugging and Sharing

Beat Link Trigger offers a few mechanisms to help you understand how your triggers are (or are not) working, and to save and restore versions of them, or share them with friends and colleagues (or on the Zulip stream or project Wiki to either get help from others, or share and show them off). Help Menu

Help resources can be found in the Help menu, along with an embedded copy of this User Guide, which can be used even if you are not connected to the Internet (although links to other sites will not work in that case).

There’s no way for the author to know how many people are using the the program, or where they are, unless they choose to contact him. Many people have (especially on the Zulip stream), but if you have not, and would like to say hello and share how you are using Beat Link Trigger, it would be greatly appreciated! You can use Help  Send User Greeting (email) to make it easy.


When Beat Link Trigger is running it reports events to a log file. As noted above, this includes full stack traces for compilation problems when you try to save an expression that isn’t quite right. If a problem occurs while trying to run your expression later, that will end up in the log too. So it can be a useful place to look when things are not working the way you expect. You can find the log file by choosing Help  Open Logs Folder.

Each time you launch Beat Link Trigger it creates a new log folder in a temporary directory, so that they can be cleaned up automatically sometime after it exits. It cycles through log files and limits their maximum length, which can be important if an error is being logged each time a packet comes in because of a problematic Enabled Filter expression.

Even if things are not crashing, you might want to log your own information to get a better understanding of what is happening inside one of your expressions. Beat Link Trigger uses timbre to perform its logging, and so can you.

For example, if you are trying to figure out exactly what you are receiving in your Beat expression, you could add this as its first line:

(timbre/info "Received beat" status "master?" tempo-master?)

Suddenly your logs will be growing steadily, filling with lines like these:

2016-Jun-05 00:12:10 Alacrity.local INFO [beat-link-trigger.expressions:?] -
  Received beat Beat: Device 2, name: CDJ-2000nexus, pitch: +0.00%,
  track BPM: 126.0, effective BPM: 126.0, beat within bar: 4 master? true
2016-Jun-05 00:12:11 Alacrity.local INFO [beat-link-trigger.expressions:?] -
  Received beat Beat: Device 33, name: DJM-2000nexus, pitch: +0.00%,
  track BPM: 126.0, effective BPM: 126.0, beat within bar: 1 master? false

Reporting Issues

If you run into trouble and it seems likely the problem is in Beat Link Trigger itself rather than one of your own expressions, you can use Help  Report Issue (email) to send an email to Deep Symmetry asking for help. The message will start out populated with some information about the version of Beat Link Trigger you are running, along with the Java version and operating system, but the more details you can provide in the initial report (along with relevant logs), the more likely we will be able to figure out what is going on.

The program may also offer to compose an issue pre-filled with details when it encounters unexpected messages sent by CDJs it is talking to.

If the problem turns out to be trickier than can be understood from just logs, follow-up questions and experimentation may be needed. In such cases thread dumps are often a valuable next step.

Thread dumps can be very useful, and yet tedious to create, especially in Windows, so here is a way to make them more easily. Add the following function to your Shared Functions:

(defn log-thread-dump
  "Writes a thread dump to the BLT log."
  (timbre/info "----- Start of thread dump")
  (doseq [t (.dumpAllThreads
              true true)]
    (timbre/info t))
  (timbre/info "----- End of thread dump"))

This defines a new shared function called log-thread-dump which writes a thread dump to the BLT log file. Then you can trigger one by editing your Global Setup Expression and adding:


When you Update the Global Setup Expression to contain this call, BLT will immediately log a thread dump to the log file. You can make it happen again by editing the Global Setup Expression, adding a space at the end, and Updating it again.

You will want to remember to take the log-thread-dump call back out of your Global Setup Expression when you are done troubleshooting, though, or you will get a thread dump in the log every time you start up BLT.

If your user interface is getting locked up and preventing you from editing and saving the Global Setup expression (or you just find that too clumsy an approach), you can instead use a REPL connection to call log-thread-dump. First, install Leiningen on your system, then start up BLT’s built-in nREPL server as described in Advanced Coding. Once the Run checkbox is active, open a command or terminal window, and type:

lein repl :connect 17001

(If you chose a different nREPL port in the nREPL Server window, enter that number instead, without any commas or punctuation.) This will open a REPL connection to the Clojure environment inside Beat Link Trigger:

Connecting to nREPL at
REPL-y 0.5.1, nREPL 0.9.0
Clojure 1.10.3
OpenJDK 64-Bit Server VM
beat-link-trigger loaded.

At this point you can type Clojure expressions and have them immediately evaluated. Tab-completion should work to help make thigs faster. Assuming you installed log-thread-dump as a Shared Function as described above, you can run it by typing this in the REPL:


You will want to have set up this REPL connection, as well as opening the BLT logs folder, before doing the experiments that lead to the problem you are trying to debug, so that they will be ready and available even if the BLT user interface becomes unresponsive.

Wireshark Captures

If you are helping investigate an issue involving communication with the DJ Link gear (especially when new products are released), it will be necessary for you to include a packet capture of what was happening on the network when you saw the problem or tried to use the gear. For that, download and install Wireshark, then perform a capture and include the file in your report.

To make it easier to understand how the capture relates to an issue you are seeing, it is even better if you can include a movie of your screen showing when the problem happens, and also include the precise timestamp in that video. The easiest way to have timestamps appear in your video is to download and open the Timestamp show that is embedded in this guide, and position its window near the part of your screen where you are capturing the issue. The Timestamp show looks like this:

Timestamp Show

The timestamps it displays are identical to the ones that Wireshark writes in the capture file, which makes it possible to correlate things happening on the screen with packets in the capture.

If there is room to have the Wireshark capture window appear in your video also, great! That can be very helpful, but the timestamp window is easier to read at small movie sizes.

In case you are curious, the code that implements this Timestamp show is very short:

Global Setup Expression
(show/require-version show "0.5.5")  (1)
(swap! globals assoc :running true)  (2)
(let [formatter (java.text.SimpleDateFormat. "HH:mm:ss.SSS")
      timestamp (seesaw/label :text "00:00:00.0000"
                              :halign :center :valign :center
                              :font "DSEG7 Classic-40")]
  (show/block-tracks show timestamp)  (3)
  (future  (4)
    (loop []
        (seesaw/value! timestamp (.format formatter (java.util.Date.))))
      (Thread/sleep 33)
      (when (:running @globals) (recur)))))
1 Check that the version of BLT is new enough for this show to work.
2 Set a :running flag that we can use to stop the animation thread (below).
3 Update the show window to reflect that it does not use tracks, also replacing the normal content of the window with the big timestamp label.
4 Create a background animation thread to update the label 30 times each second.

And when the window closes, we tell the animation thread to stop by removing the :running flag:

Global Shutdown Expression
(swap! globals dissoc :running)

Since those are the only things in the show, the file is only 560 bytes long.

GitHub Issues

You can also skip the email and open an Issue directly on the project’s GitHub page.

In any case, even if Deep Symmetry is unable to investigate your report immediately (since this is free software developed during our spare time), we very much appreciate you taking the effort to send it.

Inspecting Locals and Globals

In addition to logging values to the log file, you can get a glimpse at what your expressions are up to by opening an inspector window on the Expression Globals or a particular trigger’s Expression Locals. The first is done by choosing Triggers  Inspect Expression Globals. This opens a window showing you all the keys and values that have been created in the globals atom shared by all triggers. Here is what that looks like after the example code shown in the Global Expressions section has run:

Expression Globals

The inspector is a little messy, but right away you can see the three keys we created, and the corresponding Java objects stored under them. by clicking next to the blue diamond in the lower pane, you can expand each entry and dive down into the fields and values that make it up, which can be quite a powerful way to explore the objects.

Similarly, the locals for a trigger can be inspected by choosing Inspect Expression Locals from that trigger’s context menu. Here’s the result of drilling down a little into the :resolume OSC client object created in that example’s trigger:

Expression Locals

Saving and Loading

The entire trigger configuration can be saved to a text file by choosing File  Save to File. That file can be sent to another machine, shared with a colleague, or just kept around for future use after you are done with a different project. As you would expect, File  Load from File replaces the current trigger configuration with one loaded from a save file.

Beat Link Trigger automatically saves your triggers when you exit the program normally. If you shut down your computer, or otherwise force-quit the Beat Link Trigger process, it may not have the chance to do this, and you might lose work. If you are concerned about that happening, you can periodically manually save your current trigger configuration by choosing File  Save.

Save option

Exporting and Importing Triggers

As mentioned in the Triggers Context Menu section, individual triggers can be exported on their own, and imported into other trigger configurations.

Writing Playlists

If you are in a situation where it is important to keep detailed records of the music being played (for example, a radio station that needs to pay royalties), the built in Playlist Writer can help. It builds on the ideas described in the Matching Tracks Manually section to give you a robust, convenient solution.

Start by choosing File  Write Playlist. This will bring up the Playlist Writer window.

Playlist Writer

You can configure how long a track needs to be played for before it gets included in the playlist (the default is ten seconds) and whether you want to ignore players that are not reporting themselves as being on the air (very convenient to ignore DJs pre-screening tracks, as long as you are using a DJM mixer that supports this feature, and the players are configured and connected properly).

You can also specify that a new playlist file should be started if a track is played after all players have been stopped for a while, and configure how many minutes can pass with no player playing before such a new playlist file is created. The default threshold for this when the auto-split feature is enabled is 30 minutes. When creating new files because this New Playlist Threshold has been exceeded, the file names have timestamps added to them as described in the next section.

Once everything is set the way you want it, click Start and you will be prompted to choose where to save the file. Once you have done that, the window updates to show you that the playlist is being written:

Playlist Writer active

From this point on, all tracks that play longer than your configured minimum time will be written to the playlist, in Comma-Separated Value format (for convenient use in spreadsheet programs). The playlist will include track titles, artists, albums (when this information is available), as well as the player they were played on, the source player and media type, when they started and stopped playing, and the total time they played.

When you are done recording the playlist you can either click Stop or close the window, and the file will be closed out.

Automatically Starting the Playlist Writer

If you want to be sure that playlsits are always being written, you can add a line to your Came Online Expression that starts the playlist writer as soon as Beat Link Trigger is connected to a DJ Link network. Call the function playlist-writer/write-playlist with three arguments, folder (which determines the directory in which the playlist should be written), prefix (which specifies the first part of the playlist’s filename), and append? which controls what happens if the file already exists.

The filename will be built by adding ".csv" after prefix. If a file by that name already exists in the specified folder, then if append? is true, the existing file will be used, and new tracks will be added to the end. If the file exists and append? is false, then a timestamp will be added in between the prefix and the ".csv" extension so that a new playlist file will always be created.

For example, consider the following code:

(playlist-writer/write-playlist "/Users/james/playlists" "playlist" false)

With this in the Came Online Expression, whenever BLT establishes connection to a DJ Link network, a new playlist will be created in the directory /Users/james/playlists/. If there is not already such a file present, it will be called /Users/james/playlists/playlist.csv. If that file already exists, then a file with a name like /Users/james/playlists/playlist_2022_02_20T17_16_19.csv will be created, using the year, month, day, hour, minute, and second to ensure it is unique.

Alternately, a line like the following would always use the file /tmp/played.csv, creating it and writing the header line if it did not exist, and otherwise just adding new tracks to the end of the file:

(playlist-writer/write-playlist "/tmp" "played" true)

Advanced Coding

The built in code editor gives you some basic help with writing Clojure, such as syntax coloring and parenthesis matching, but if you are going to write more than a few lines, or want help learning Clojure and testing ideas, you are much better off using a full-featured Clojure development environment, like Cursive or (if you already use the Emacs text editor) CIDER.

Much of the power of these environments comes from the way they interact with the live, running Clojure environment so that you can benefit from code-completion assistance, documentation popups, and even dive into the source code of your functions and the ones that make up Beat Link Trigger itself, making it easy to try things out, update your functions, examine values, and try again. This kind of instant feedback leads to a fertile learning experience and incredibly productive development workflow centered around the Clojure “REPL” (read-eval-print loop).

Beat Link Trigger has features to support working in these editors. At a basic level, the built-in code editor can save your expression or function code to an external text file, which you can edit in one of these environments, and then have the built-in editor load the results back in. But you will get the best results if you turn on a network REPL server inside BLT so the external IDE can connect to it and operate at full power.

To do this, choose Network  nREPL: Clojure IDE connection:

nREPL menu option

This will open a small window which allows you to configure and start the nREPL server the IDEs can use:

nREPL Server window

The default port number was chosen to be different from the Carabiner port (in case you are using that), and unlikely to be otherwise used. If for some reason port 17,001 is in use on your computer, you can pick a different port. You just need to tell the IDE which port you are using when you have it connect to the nREPL server.

If you are going to use Cursive, you can simply start the server by clicking the Run checkbox. If you are going to use CIDER, to make full use of CIDER’s powerful features, click the Inject checkbox first, which will configure the nREPL server to add special CIDER middleware. (If you forgot to do this, you can uncheck the Run checkbox to stop the server, check Inject, and then Run it again.)

Although it probably won’t hurt to inject the CIDER handler when using a different editor, it’s a little safer to leave it out of the loop if you don’t need it.

Connecting from Cursive

Once you have the nREPL server running (without injecting the CIDER handler), you will want to follow the Cursive instructions for Remote REPLs, using the Connect to server radio button, entering as the Host (assuming you are running Cursive on the same machine as Beat Link Trigger, although you don’t have to!) and then set the Port value to the one you chose in Beat Link Trigger.

Once you have your Remote REPL configuration created, it appears in the menu at the top right of the project window, and you can connect to it by pressing the green triangle (play button) to the right of it (I named my configuration BLT nREPL):

Cursive connected

Whatever expressions you type at the bottom of the REPL window are immediately evaluated, and the results shown above, and you can use Cursive’s editor window features to send functions and files to the REPL. Cursive’s built-in completion can be seen in action at the bottom of the above screen shot. See the Cursive User Guide for more information.

Connecting from CIDER

Once you have the nREPL server running with the CIDER handler injected, you can tell CIDER to connect to it by typing M-x cider-connect RET and CIDER will prompt you for the host and port information. If you are running BLT on the same machine as CIDER, enter localhost or for the host, and then enter the port number you chose in Beat Link Trigger.

The CIDER Docs give you the details of how to work with it.

Using the Connection

Regardless of the IDE you are using, you will probably want to switch your namespace to beat-link-trigger.expressions because this is where your Shared Functions live, and they are where you should put your most sophisticated coding, so that individual expressions can be short and sweet, using the Shared Functions for the heavy lifting. This namespace is also configured to include Clojure’s developer-friendly functions like doc (to let you look at the documentation for a function or variable), source (to let you see the source code of a function), and so on. The Clojure documentation mentions some of the things you can do at the REPL (although you can ignore the parts that talk about how to start Clojure; Beat Link Trigger has already done that for you). There is a page about basic usage, navigating namespaces, and a collection of resources describing REPLs and the mindset of using them effectively. (The navigation links on the left side and top of those pages can take you other good places for learning about the REPL, Clojure, troubleshooting, and so on.)

For more ideas specifically about how to explore Beat Link Trigger from a Clojure IDE, mention you are doing it on the Zulip stream!