Skip to content

Example: Start Player 3 when another Player starts

James Elliott edited this page Sep 19, 2024 · 5 revisions

@drummerClint asked me (@brunchboy) for the code that would allow him to tell one player to start playing when another started, as an experiment. This seemed like it would be a useful example of how to figure something like that out, so I wrote it up here rather than just responding.

Basic Approach

I knew that the method he would need to call exists in the VirtualCdj class, and suggested he start with the Beat Link API docs. Specifically, the method is sendFaderStartCommand.

Writing code in Beat Link Trigger uses Clojure, so to call a Java method you need to look at Java Interop. That can get pretty deep for a method with complex arguments like this one, so I suggested he take a look at how I had called it in Beat Link Trigger, in the track loader window. Searching for the sendFaderStartCommand method in that file reveals where I used it.

There is still some pretty hairy Java Interop code involved there, so I am glad I decided to write this up rather than forcing Clint to figure it out himself. The biggest annoyance is that we need to create sets of Java Integer objects, and the native Clojure representation for numbers uses Long objects, so we need to cast them. Also, the track loader namespace makes the VirtualCdj singleton instance conveniently available in Clojure as virtual-cdj, but the expressions namespace where your code runs does not (at least, not yet), so you need to explicitly reference it using a longer expression.

What that all boils down to is that, if you want to start Player 3 playing in your Trigger code, you would write something like this:

(let [virtual-cdj (org.deepsymmetry.beatlink.VirtualCdj/getInstance)]
  (.sendFaderStartCommand virtual-cdj #{(int 3)} #{}))

The first line lets us refer to the VirtualCdj singleton instance as virtual-cdj in the body of the let, just for readability (this would be more useful if we were calling multiple methods, but it still helps keep our lines reasonably short). The second line tells the VirtualCdj to send a fader start command, with a deviceNumbersToStart set containing the Integer 3, and an empty deviceNumbersToStop set.

If you set that up as the Activation Expression for an enabled trigger configured to watch Player 2, then whenever Player 2 starts playing, Player 3 will as well.

You can tweak this by adding multiple players in the start set—​for example to start all four players it would look like #{(int 1) (int 2) (int 3) (int 4)}, or you can stop players by putting integers in the second set, which is empty in the example above.

Building Convenience

If you are actually building an integration where it makes sense to start and stop players from more than one expression, then it’s worth building a function to make it easier, translating between the Clojure and Java worlds for you. Here is one that you could put in your Global Setup Expression:

(defn start-players
  "Takes a list of player numbers. If they are positive, tells that player
  to start playing; if negative, tells it to stop playing."
  [& numbers]
  (let [virtual-cdj (org.deepsymmetry.beatlink.VirtualCdj/getInstance)
        to-start    (set (map int (filter pos-int? numbers)))
        to-stop     (set (map (comp int -) (filter neg-int? numbers)))]
    (.sendFaderStartCommand virtual-cdj to-start to-stop)))

The & notation at the start of the argument list tells Clojure our function takes any number of arguments, and they will be all grouped into numbers as a vector. As in the simpler example above, we start by getting access to the Virtual CDJ singleton instance as virtual-cdj. We then pick out only the positive integer arguments, turn them into Java Integer objects, and group them into a Set object named to-start. We do a similar thing with all the negative integer arguments, but we make them positive before grouping them into the Set named to-stop. Finally, we use those sets to call the sendFaderStartCommand method.

This function is only slightly longer than the first example, but is much more convenient to use from your expressions. With it installed, if you want to start Player 1, you can just say:

(start-players 1)

If you want to start all four players, it’s just:

(start-players 1 2 3 4)

If you want to start Player 1 and stop Player 2, that is:

(start-players 1 -2)

Concise and easy… and any arguments that are not valid player numbers are simply ignored.

I hope this example is useful both as a concrete demonstration of how to use Fader Start from your expressions, and of how to explore the API documentation and BLT source code to figure out your own things to try.