Rehearsal Room Location

This chapter details a workspace that will be most useful for rehearsing dances and other performances where it is helpful to jump to named cue points in music recordings repeatedly and rapidly.

The workspace

It uses QLab’s slice markers to mark cue points, and extends their functionality by providing a means for them to be named using a list of names in the notes field of the Audio cue.

When you select a cue that contains these named markers, the cue is “loaded into” a cart. The “locate buttons” in the cart become labelled with the marker names, and pressing a button will commence playback immediately from the named location displayed on that button (with an optional pre-roll).

For stems in a Timeline Group Cue, the Group can be played from named locations based on the slices and notes of the first Audio cue in the Group.

The Workspace locates to timeline values relative to the start time of the cue, regardless of repeated slices, and can therefore locate beyond infinitely looped slices.

The cue rate setting can be changed between 0.1X and 2X speed in 0.1 increments to allow for rehearsals at slower (or faster) tempos.

In addition, location names can be imported directly from WAV files that contain named markers saved by a DAW or other audio editing application.

Here it is in action:

Here’s the play-by-play:

  1. When the GET cart cue is pressed, the selected Audio cue and all its locations are loaded into the cart.
  2. The location names are entered in the notes field of the Audio cue with one name per line (paragraph). The number of location names must match the number of slice markers in the Audio cue.
  3. Various locations are started, stopped, and repeated using the appropriate cart buttons.
  4. The cue is stopped with the STOP cart cue, and the next cue is selected and loaded using the GET cart cue.
  5. A pre-roll of 3 seconds is applied by pressing the PREROLL cart cue and entering a time in the following dialog.
  6. The cart cues now locate to just before the named locations using the pre-roll time.
  7. The Audio cue is stopped, and the next Audio cue is selected.
  8. The GET cart button is pressed, and the selected cue and all its locations, based on its slice times and the names in its notes field, are loaded into the cart.
  9. The tempo is altered using the SLOWER and FASTER cart cues, which indicate their state and the tempo.
  10. Reloading the Audio cue with GET resets the tempo to 1.
  11. The pre-roll is set back to 0.
  12. The Timeline Group cue containing a batch of stems is selected.
  13. The GET cart cue loads the locators from the slice times and names in the notes of the first cue in the Timeline Group.
  14. The cart cues locate to their named marker in the first Audio cue and start the Timeline Group at that time.
  15. The last cue is selected in the cue list. This has markers imported from the file target, but no assigned marker names in its notes.
  16. The GET cart cue is pressed, which displays an error because the number of slice markers does not match the number of names in the notes of the cue.
  17. Hotkey 1 is pressed, which fetches the marker names from the data in the target audio file and copies those names into the notes of the Group.
  18. The GET cue can now load the Audio cue and its locations into the cart.
  19. All locators work as expected, even though the slice after marker 2 is set to loop infinitely.
  20. If the playhead is in the loop between markers 2 and 3, the loop can be exited by pressing the DEVAMP cue.

How it Works

All the programming is contained in the cart cues. This makes the cart fully portable, as it has no dependencies. It can just be copied from workspace to workspace.

We’ll examine the functionality of each cart cue in turn.

GET

This is a Script cue with the following script:

set theLast to 12 --number of last locator button in this cart
tell application id "com.figure53.QLab.5" to tell front workspace
  try
    set theselected to last item of (selected as list)
  on error
    my errorFlag(1)
  end try
  if the q type of theselected is in {"audio", "video"} then
    try
      set theSlices to slice markers of theselected
      set theLocators to (paragraphs of (get notes of theselected))
      set theName to q list name of theselected
      
    on error
      my errorFlag(2)
      return
    end try
  else if the q type of theselected is "group" then
    if the mode of theselected is not timeline then my errorFlag(10)
    try
      set theFirst to (first cue of theselected)
    on error
      my errorFlag(4)
    end try
    try
      set theSlices to slice markers of theFirst
      set theLocators to (paragraphs of (get notes of theFirst))
      set theName to q list name of theselected
      
    on error
      my errorFlag(3)
    end try
  else
    my errorFlag(7)
    return
  end if
  if the (count of theSlices) is not equal to the (count of theLocators) then my errorFlag(5)
  
  set the q name of cue "GET" to q number of theselected & " " & q list name of theselected
  set the cue target of cue "DEVAMP" to theselected
  set the notes of cue "SLOWER" to 1.1
  start cue "SLOWER"
  set the notes of cue "GET" to uniqueID of theselected
  if the q type of theselected is "group" then
    set the notes of cue "LOC0" to uniqueID of theFirst
  else
    set the notes of cue "LOC0" to uniqueID of theselected
  end if
  repeat with i from 1 to theLast
    set the q name of cue ("LOC" & (i as text)) to "--- "
  end repeat
  try
    repeat with i from 1 to count of theSlices
      set the q name of cue ("LOC" & (i as text)) to item i of theLocators
    end repeat
  end try
  try
    repeat with i from 1 to count of theSlices
      set locCue to cue ("LOC" & (i as text))
      set the q name of locCue to item i of theLocators
      set the notes of locCue to item i of theSlices
    end repeat
  end try
end tell

on errorFlag(errorNumber)
  if errorNumber = 1 then display alert "No cue Selected"
  if errorNumber = 2 then display alert "Cue selected is not an audio, video or group cue"
  if errorNumber = 3 then display alert "First cue in the selected group is not an audio or video cue "
  if errorNumber = 4 then display alert "Group is empty"
  if errorNumber = 5 then display alert " Slices and Locators do not match"
  if errorNumber = 7 then display alert "Cue Selected is not an audio, video, or group cue"
  if errorNumber = 8 then display alert "Notes field of selected cue does not contain any locator data"
  if errorNumber = 10 then display alert "Mode of group is not timeline "
  error number -128 --exit script
end errorFlag

The first thing you may notice with this script is the large amount of error-checking. There are many reasons the GET script could fail; they are listed in the on errorFlag block. Every time an error is encountered, one of these error conditions will be triggered, and the person using the workspace will get some information about what’s wrong. Better that than to fail silently, leaving the person wondering what to do next.

The script begins by setting a variable to tell it how many locator buttons are in the cart.

The selected cue is assigned to a variable.

The selected cue is tested to make sure it is either a Video or Audio cue; if it is, then its slice markers are placed in a list variable, the location names (each one of which is a separate paragraph in the cue’s notes field) in another list variable, and the name of the cue in a third variable.

If the selected cue is a Group cue, then the first cue in the Group is assigned to a variable so that its slices and names can be accessed when required.

We are going to need all these variables accessible after the GET script is run in order to make everything else in the cart work, so they need to be stored in places where they will persist until another cue is loaded with the GET cart cue.

The name of the cart cue numbered GET is set to the name of the selected cue, as is the cue target of the DEVAMP cart cue.

The rate is reset back to 1. The current rate is stored in the notes of the cart cue numbered SLOWER.

The cue id of the cue whose slice markers and location names are going to be used for the locating is stored in the notes of cue LOC0.

All the LOC cart cues have their names reset to –-– to indicate they are empty, then each of the locator cart cues is set up in turn using a repeat loop.

Each cart cue is named with the marker name it will locate to, and the slice tome for that marker is stored in its notes.

The whole cart is now set up to refer to the cue that was selected when the GET cue was last started, even if the selected cue changes. This will be the case until another cue is selected and the GET cue is started again. The whole cart has become a dedicated control panel for that one cue. In the following cue descriptions, this cue is referred to as the cue that is “on” the cart.

STOP

This is a Script cue with a short, simple script:

tell application id "com.figure53.QLab.5" to tell front workspace
  try
    set theCue to cue id (notes of cue "GET")
    stop theCue
  end try
end tell

It uses the variable stored by the GET script to identify the cue on the cart and stop it.

DEVAMP

This is a Devamp cue set to devamp the currently looping slice with no other options set.

It is set to target the cue on the cart by the GET script.

PREROLL

This is a Script cue with a short, simple script:

tell application id "com.figure53.QLab.5" to tell front workspace
  try
    set thePreRoll to text returned of (display dialog "Set PreRoll time (Seconds) " default answer (q list name of cue "PREROLL" as text)) as real
  on error
    set the q name of cue "PREROLL" to 0
    return
  end try
  set the q name of cue "PREROLL" to thePreRoll
end tell

It sets its own name to the number of seconds returned from a dialog with the person using the workspace. The dialog supplies a default answer, which is the current pre-roll time. If the user doesn’t type a number as a valid response, the script fails silently.

SLOWER and FASTER

These are both Script cues. This is the SLOWER script:

tell application id "com.figure53.QLab.5" to tell front workspace
  set theCue to (notes of cue "GET")
  set theRate to (notes of cue "SLOWER")
  if the notes of cue "SLOWER" > 0.1 then
    set the notes of cue "SLOWER" to theRate - 0.1
    set the q name of cue "SLOWER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
    set the q name of cue "FASTER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
    set theOSC to "/cue_id/" & (notes of cue "GET") & "/rate/live " & (get notes of cue "SLOWER" as text as real)
    do shell script "echo " & quoted form of theOSC & " | nc -u -w 0 127.0.0.1 53535"
  end if
  
  set theTempo to notes of cue "SLOWER" as text as real
  if theTempo < 1 then
    set the q color of cue "SLOWER" to "red"
    set the q color of cue "FASTER" to "none"
    set the q name of cue "SLOWER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10 & return & "<<<<<<<<<<"
    set the q name of cue "FASTER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
  else if theTempo > 1 then
    set the q color of cue "SLOWER" to "none"
    set the q color of cue "FASTER" to "red"
    set the q name of cue "SLOWER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
    set the q name of cue "FASTER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10 & return & ">>>>>>>>>>"
    
  else
    set the q color of cue "SLOWER" to "none"
    set the q color of cue "FASTER" to "none"
    set the q name of cue "SLOWER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
    set the q name of cue "FASTER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
    end if
  end tell

This is the FASTER script:

tell application id "com.figure53.QLab.5" to tell front workspace
  set theCue to (notes of cue "GET")
  set theRate to (notes of cue "SLOWER")
  if the notes of cue "SLOWER" < 2 then
    set the notes of cue "SLOWER" to theRate + 0.1
    set the q name of cue "SLOWER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
    set the q name of cue "FASTER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
    set theOSC to "/cue_id/" & (notes of cue "GET") & "/rate/live " & (get notes of cue "SLOWER" as text as real)
    do shell script "echo " & quoted form of theOSC & " | nc -u -w 0 127.0.0.1 53535"
  end if
  set theTempo to notes of cue "SLOWER" as text as real
  if theTempo < 1 then
    set the q color of cue "SLOWER" to "red"
    set the q color of cue "FASTER" to "none"
    set the q name of cue "SLOWER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10 & return & "<<<<<<<<<<"
    set the q name of cue "FASTER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
  else if theTempo > 1 then
    set the q color of cue "SLOWER" to "none"
    set the q color of cue "FASTER" to "red"
    set the q name of cue "SLOWER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
    set the q name of cue "FASTER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10 & return & ">>>>>>>>>>"
  else
    set the q color of cue "SLOWER" to "none"
    set the q color of cue "FASTER" to "none"
    set the q name of cue "SLOWER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
    set the q name of cue "FASTER" to return & "TEMPO: " & ((notes of cue "SLOWER") * 10 as integer) / 10
  end if
end tell

The notes of cue SLOWER hold the current rate setting. The notes were set back to 1 when the GET script was run.

When the SLOWER cue is started, the stored rate is decremented by 0.1. When the FASTER button is pressed, it is incremented by 0.1.

The names of both SLOWER and FASTER show the current tempo. If the rate is less than 1, the SLOWER cue turns red and displays a running arrows icon. If the rate is greater than 1, the FASTER cue turns red and displays the arrows.

Generally, you’ll want to set all Audio and Video cues to preserve pitch in the Time & Loops tab of the inspector. That way, tempo changes don’t also change the pitch of the audio.

Preserve pitch

LOC 0 TOP OF CUE

This is a Script cue with the following script:

tell application id "com.figure53.QLab.5" to tell front workspace
  try
    set theCue to cue id (notes of cue "GET")
    stop theCue
    delay 0.1
    start theCue
    set theOSC to "/cue_id/" & (notes of cue "GET") & "/rate/live " & (get notes of cue "SLOWER" as text as real)
    do shell script "echo " & quoted form of theOSC & " | nc -u -w 0 127.0.0.1 53535"
  end try
end tell

This script stops the cue on the cart and plays it from the beginning at the current rate stored in the notes of cue SLOWER.

This uses the live rate, so that when the cue is played normally in the cue list, it will play at the correct speed; the rate only affects the cue on the cart.

QLab’s AppleScript dictionary does not contain the live rate command needed, but the OSC dictionary does. In this and similar cases, the solution is to format an OSC command and to send it as plain text to port 53535 using a shell script. Some important points need to be observed.

First, QLab uses localhost as an internal loopback address. As this OSC must go outside QLab to be routed through the terminal, the IP address must be 127.0.0.1, not localhost.

Second, the “no passcode” entry for OSC access must be given edit permission in Workspace Settings → Network → OSC Access.

OSC Access

The rest of the cart cues locate to the relevant named marker and start playback.

LOC 1 - LOC12

These are Script cues which all have a very similar, brief script:

set myLOC to 1 -- 1 to 12 depending on the cue
---------------------------
tell application id "com.figure53.QLab.5" to tell front workspace
  set notes of cue "LOC" to myLOC
  start cue "LOC"
end tell

The cues simply identify themselves when started by setting the notes of cue LOC to their identifying number, and then start cue LOC which contains the actual locator script.

The first advantage of doing it this way is that the locator script can be adjusted quickly. Otherwise, every time you wanted to change the way locating works, you’d need to edit all 12 locate cues.

The second advantage is that it can easily scale. If 92 locator cues were required, it would only be necessary to:

  • duplicate the LOC 1 button 91 times,
  • change the first line of the script in each new copy to include the relevant number,
  • number the duplicate cues to match. For example, the script in cue LOC53 would start set myLOC to 92

You could even use a script to generate the cart cues:

set theButtonQuantity to 92 --number of cart cues to make
tell application id "com.figure53.QLab.5" to tell front workspace
  repeat with x from 1 to theButtonQuantity
    make type "script"
    set theButton to last item of (selected as list)
    set the q number of theButton to "LOC" & x
    set the q name of theButton to "--"
    set the script source of theButton to "set myLOC to " & x & return & "tell application id \"com.figure53.QLab.5\" to tell front workspace" & return & "set notes of cue \"LOC\" to myLOC" & return & "start cue \"LOC\"" & return & "end tell"
    set the q color of theButton to "green"
  end repeat
end tell

LOC

The cart cue numbered LOC holds the main locator script and also functions as a repeat button to play the previous location again.

tell application id "com.figure53.QLab.5" to tell front workspace
  set thePreRoll to q list name of cue "PREROLL" as real
  set theCue to cue id (notes of cue "GET")
  set theMarkerRef to cue id (notes of cue "LOC0")
  set myLOC to notes of cue "LOC"
  set q name of cue "LOC" to "LAST USED (" & myLOC & ")"
  stop theCue
  delay 0.1
  set theSlices to slice markers of theMarkerRef
  set theTime to (time of item myLOC of theSlices) - thePreRoll - the (start time of theMarkerRef)
  if theTime < 0 then set theTime to 0
  set theOSC to "/cue_id/" & (notes of cue "LOC0") & "/loadFileAt " & theTime
  do shell script "echo " & quoted form of theOSC & " | nc -u -w 0 127.0.0.1 53535"
  if the q type of theCue is "group" then load theCue time (action elapsed of theMarkerRef)
  delay 0.1
  set theOSC to "/cue_id/" & (notes of cue "LOC0") & "/rate/live " & (get notes of cue "SLOWER" as text as real)
  do shell script "echo " & quoted form of theOSC & " | nc -u -w 0 127.0.0.1 53535"
  start theCue
end tell

The script reads in the values for pre-roll, the cue on the cart, the cue containing the marker names and slices (either the same as the cue on the cart, or the first child cue if the cue on the cart is a Group cue), and the name of the locator cue that started the cue LOC.

It sets its name to the name of the locator button, to indicate the last Location point.

It finds the slice time corresponding to LOC1-12, e.g. if LOC6 started cue LOC, then the slice time is item 6 of the list of slice times.

It sets the live rate of the cue on the cart to the current playback rate using the OSC method previously discussed.

Then it starts the cue on the cart.

Importing Marker Names From Files

Manually adding slices and marker names to cues is reasonably quick, and will work with any file target type. If your project uses a large number of WAV files, and those WAV files contain markers added in a DAW or audio editor, it may be worthwhile to automate the import of marker names to the notes field of the audio cues.

This method uses a free command line tool called ExifTool which was created by Phil Harvey.

ExifTool is a platform-independent Perl library plus a command line application for reading, writing, and editing metadata in a wide variety of media files of all types. It’s very powerful and very complicated. To read marker names from the metadata of a WAV file, ExifTool uses a config file called cuepointlist.config.

Installation Instructions

Command line tools are sometimes fiddly to install and get working. Before embarking on an installation, consider whether you really will save a lot of time by using this method. Of course if you’re sufficiently curious to see how it works, then the practical time savings may be irrelevant to you. Having said that, a test of these installation instructions on two separate computers got the basics working in about five minutes. Your experience level with the Terminal, the version of macOS that your computer is using, and possibly other software installed on your Mac might all have an effect on the amount of time that this takes.

Download the ExifTool installer here and double-click on the download to install the program.

Download cuepointlist.config from the download link on this page.

It needs to be put in the folder /usr/local/bin, which you can quickly locate by choosing Go to Folder… from the Go menu in the Finder.

Go to Folder

This is the same folder in which ExifTool was installed automatically.

Once it’s installed, you can use this script, triggered from a hotkey, which will attempt to fetch marker names from selected cues’ targeted WAV files.

tell application id "com.figure53.QLab.5" to tell front workspace
  set theCues to (selected as list)
  repeat with eachCue in theCues
    if the q type of eachCue is not "audio" then
      display alert "Cue " & (q number of eachCue) & " is not an audio cue"
      return
    end if
    set theFile to (file target of eachCue) as alias
    tell application "Finder" to set theType to name extension of theFile
    if theType is not "<abbr>WAV</abbr>" then
      display alert "Cue " & (q number of eachCue) & " is not a <abbr>WAV</abbr>"
      return
    end if
    if notes of eachCue is "" then
      set theFile to quoted form of POSIX path of (file target of eachCue as alias)
      set ExifTool to "/usr/local/bin/exiftool "
      set theData to (do shell script ExifTool & " -config cuepointlist.config -cuepointlist -b " & theFile) as text
      set theMarkerNames to ""
      repeat with thePara from 2 to (count of paragraphs of theData)
        repeat with theWord from 4 to count of words of paragraph thePara of theData
          set theMarkerNames to theMarkerNames & (word theWord of paragraph thePara of theData)
          if theWord is not (count of words of paragraph thePara of theData) then set theMarkerNames to theMarkerNames & " "
        end repeat
        if thePara is not (count of paragraphs of theData) then set theMarkerNames to theMarkerNames & return
      end repeat
      set notes of eachCue to theMarkerNames
    else
      display alert "Cue " & (q number of eachCue) & " was not edited as its notes field contained notes which you might not want to overwrite, or existing marker names which may have been edited." & return & return & "To import the original marker names from the audio file, delete all notes in this cue"
      set the flagged of eachCue to true
    end if
  end repeat
end tell

To protect cues you have edited manually, or cues which have been previously imported but may have had extra slice markers and names added, or cues that have had the slice markers moved about, only cues that have empty notes fields will be considered by the script. The script also checks that the selected cues’ file targets are WAV files. Any selected cues whosenotes fields are not empty or whose file targets are not WAV files will generate an alert message in the script. The script also flags the offending cue to make it easy for you to review the situation.

Goldberg Variations by JS Bach Public Domain. The Open Goldberg Variations is a project by pianist Kimiko Ishizaka, and MuseScore.com, to create a public domain recording and score of J.S. Bach’s masterpiece, Die Goldberg Variationen (BWV 988).

Other music arranged and recorded by Mic Pool ©2023, all rights reserved.

ExifTool and its config file are freeware created by Phil Harvey.

Chapter image © Raimond Spekking / CC BY-SA 4.0 (via Wikimedia Commons).