Multifiler
QLab supports audio files with up to 24 channels, but making multichannel files according to your own specifications turns out to be surprisingly tricky. While most DAWs support the output of multichannel file in specific output formats intended for use in broadcast and cinema production, many folks want to use multichannel files in QLab to mix stems, where each channel represents an instrument or group of instruments. This sort of output is possible from some DAWs, but not most of them. This chapter describes a script which converts several individual Audio cues, each with their own channels, into a new cue with its own multichannel file. This can streamline your cue list, speed up editing, and simplify copying or moving the cue around.
Usage
Using this script is very straightforward: select two or more Audio cues that you want to combine, and run the script. You can either run the script using Script Editor, or put the script in a Script cue and run the cue using a hotkey, MIDI trigger, or OSC.
Here it is in action:
Setup
This method relies on an external command-line tool called sox
, which is billed as “the Swiss Army knife of audio manipulation.” You can read about sox here. We recommend installing sox
using Homebrew, which is a tool for easily installing command-line based software on the Mac. If you haven’t yet installed Homebrew, the instructions to do so are easy to follow. Once homebrew is installed, install sox
by opening a Terminal window and entering the command:
brew install sox
Once sox
is installed, no further configuration is necessary.
Details
We’ll go through the script section by section. The script is listed in total after that, and the downloadable example includes the same script inside a Script cue.
-- if you've installed sox NOT using homebrew
-- or if this script doesn't seem to be able to find sox
-- you'll need to explicitly set soxPath to the full path
-- to the sox executable.
-- mostly, this command does it properly:
set soxPath to do shell script "bash -l -c '/usr/bin/which sox'"
-- if it doesn't work for you, edit the command below
-- and use it to replace the command above
-- set soxPath to /path/to/sox
This section is necessary because AppleScript runs shell commands without knowledge of your PATH variable. Since Homebrew supports using a custom path, and since it is obligated to use different defaults on different macOS systems, we can’t just hard-code the path to sox
into this script. The set soxPath
line runs a little shell command which attempts to ascertain the location of sox
, but if for some reason that doesn’t work you’ll need to manually set the path to sox
yourself.
tell application id "com.figure53.qlab.5"
set theParent to q name of parent of first item of (selected of front workspace as list)
set theFirstCue to q list name of first item of (selected of front workspace as list)
set theDate to do shell script "date \"+%Y%m%d-%H%M%S\""
theParent
is the name of the cue that contains the selected cues. We’ll use it to name the new cue later on if the cues are inside a Group. theFirstCue
is the name of the first of the selected cues. We’ll use is to name the new cue later on if the cues are not inside a Group.
theDate
is today’s date and the time, which we’ll use to make sure we uniquely name the files we’ll be creating.
-- ask how many outputs we're using
-- saves time to not go through all 64 outputs if we don't need to
display dialog "How many cue outputs are we using?" default answer "64" buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel"
set numberOfOutputs to (text returned of result) as integer
Folks don’t always use all 64 cue outputs, and it’s a nice little timesaver to only deal with the number of cue outputs that are actually in use. Since there’s no way for the script to know what that is, we ask the human and store the answer.
-- prepare to flip through the selected cues
set originalCues to (selected of front workspace as list)
set theFiles to ""
set totalChannels to 0
set theLevels to {}
set theSlices to {}
We’re about to run a big “repeat” loop, and this sets up all the variables that we’ll be using. theLevels
is a list which will hold all the levels of all the channels of all the cues. theSlices
is a list which will hold all the slices of all the cues.
-- now do it
repeat with eachCue in originalCues
if q type of eachCue is "Audio" then
-- get file targets of original cues
set theFile to file target of eachCue
set preWait to pre wait of eachCue
Make sure we’re only looking at Audio cues, then loop through all the cues, grabbing the file target and pre-wait of each one.
-- if there's a pre-wait, make a new file
-- with the pre-wait baked in
if preWait > 0 then
set theFilePath to POSIX path of theFile
set theFileName to q number of (info for theFilePath)
do shell script soxPath & " " & theFilePath & " ~/Desktop/pad-" & theDate & "-" & theFileName & " pad " & preWait
set theFiles to theFiles & " ~/Desktop/pad-" & theDate & "-" & theFileName
else
set theFiles to theFiles & " " & POSIX path of theFile
end if
If a cue has a pre-wait, we want to “bake” that pre-wait into the final product, so we create a new audio file using sox
which creates a copy of the original file target of the cue, padding the beginning of the file by the pre-wait time. That new file is saved to the desktop with the word “pad” and the date and time prepended to the filename.
If the cue doesn’t have a pre-wait, we just add its file target to the list of files that we’ll merge later.
-- get levels of original cues
set theChannels to audio input channels of eachCue
set totalChannels to totalChannels + theChannels
repeat with input from 1 to theChannels
repeat with output from 1 to numberOfOutputs
set theLevel to eachCue getLevel row input column output
copy theLevel to end of theLevels
end repeat
end repeat
First, we find out how many channels the cue has. Then we get the crosspoint levels for each of those channels and append those levels to the end of the list of levels.
-- get slices of original cues
set allSlices to slice markers of eachCue
repeat with eachSlice in allSlices
copy eachSlice to end of theSlices
end repeat
end if
end repeat
Getting slices from each cue is simple. We do that, then add the slices from each cue to the end of the list of slices.
-- make combined file using the list we just built
set theNewFile to "combined-" & theDate & ".aiff"
do shell script soxPath & " -M " & theFiles & " -t aiff -r 48000 -b 24 ~/Desktop/" & theNewFile
First, prepare the name of the new file as the word “combined-”, the date and time, and the suffix .aiff
.
Then actually make the new audio file, which turns out to be pretty simple too. The options used are:
-M
- merge input files into a new multichannel file.-t aiff
- make the new file an AIFF file.-r 48000
- make the sample rate of the new file 48 kHz.-b 24
- make the bit depth of the new file 24 bits.
Then the new file gets saved on the desktop.
-- make a new cue that uses the new combined file
make front workspace type "Audio"
set newCue to last item of (selected of front workspace as list)
set theTarget to ((path to desktop folder) & theNewFile) as string
set file target of newCue to theTarget
if theParent is "Main Cue List" then
set q name of newCue to "multitrack with " & theFirstCue & " and others"
else
set q name of newCue to theParent & " (multitrack)"
end if
Now we create a new Audio cue, set its target to the new audio file we just created, and give it a name. If the old cues were inside a Group cue, we’ll use the name of that Group cue as the basis for the new cue’s name. If not, we’ll use the name of the first of the original cues as the basis. In either case, of course, you can just change the name to whatever you like after the fact.
-- set levels of the new cue
repeat with input from 1 to totalChannels
repeat with output from 1 to numberOfOutputs
set theItem to ((input - 1) * numberOfOutputs + output)
set theLevel to item theItem of theLevels
newCue setLevel row input column output db theLevel
end repeat
end repeat
Now we loop through the list of levels we made before and apply them to the levels in the new cue.
This is a little tricky; there’s an outer repeat loop which steps through the rows, and then an inner loop which steps through the columns. The outer loop counts from 1 up to totalChannels
, which is the count we’d been maintaining back when we pulled levels out of the original cues. That way, we know how many rows there are. The inner loop counts up from 1 to the number of outputs that we asked about at the beginning of the script.
This line is the hardest part:
set theItem to ((input - 1) * numberOfOutputs + output)
The levels from all the crosspoints of all the original cues are stored in one big list. Since we know the order that levels went into that list, we can use this little bit of math to grab the item from the list that corresponds to where we are in both loops. If, for example, we’re in the third iteration of the outer loop (i.e. the levels corresponding to the third channel in the input files) and the fourth iteration of the inner loop (i.e. cue output 4), and we told the script we’re using 10 outputs in total, then this line becomes, effectively, set theItem to ((3 - 1) * 10 + 4)
. That makes theItem
equal to 24
, which means “the 24th item in the list of levels,” which checks out: each row is ten items, we’re on the third row, fourth cell. Item number 24.
-- set slices of the new cue
set slice markers of newCue to theSlices
end tell
Finally, we apply our list of slices to the new cue, so that all the slice markers and slice counts from the original cues are applied to the new cue.
Wrap-up
There are a few loose ends you need to tie up manually after running the script. First of all, file targets set via AppleScript don’t get automatically copied into the project folder, so you’ll need to manually move the new combined-....aiff
file into the project folder yourself. If any intermediary “pad-” files were created, you’ll need to delete those yourself. And if any slices from the original cues overlapped in a weird way, you’ll need to tidy that up yourself.
The Script In Total
Here’s the script in its totality:
-- if you've installed sox NOT using homebrew
-- or if this script doesn't seem to be able to find sox
-- you'll need to explicitly set soxPath to the full path
-- to the sox executable.
-- mostly, this command does it properly:
set soxPath to do shell script "bash -l -c '/usr/bin/which sox'"
-- if it doesn't work for you, edit the command below
-- and use it to replace the command above
-- set soxPath to /path/to/sox
tell application id "com.figure53.qlab.5"
set theParent to q name of parent of first item of (selected of front workspace as list)
set theFirstCue to q list name of first item of (selected of front workspace as list)
set theDate to do shell script "date \"+%Y%m%d-%H%M%S\""
-- ask how many outputs we're using
-- saves time to not go through all 64 outputs if we don't need to
display dialog "How many cue outputs are we using?" default answer "64" buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel"
set numberOfOutputs to (text returned of result) as integer
-- prepare to flip through the selected cues
set originalCues to (selected of front workspace as list)
set theFiles to ""
set totalChannels to 0
set theLevels to {}
set theSlices to {}
-- now do it
repeat with eachCue in originalCues
if q type of eachCue is "Audio" then
-- get file targets of original cues
set theFile to file target of eachCue
set preWait to pre wait of eachCue
-- if there's a pre-wait, make a new file
-- with the pre-wait baked in
if preWait > 0 then
set theFilePath to POSIX path of theFile
set theFileName to q number of (info for theFilePath)
do shell script soxPath & " " & theFilePath & " ~/Desktop/pad-" & theDate & "-" & theFileName & " pad " & preWait
set theFiles to theFiles & " ~/Desktop/pad-" & theDate & "-" & theFileName
else
set theFiles to theFiles & " " & POSIX path of theFile
end if
-- get levels of original cues
set theChannels to audio input channels of eachCue
set totalChannels to totalChannels + theChannels
repeat with input from 1 to theChannels
repeat with output from 1 to numberOfOutputs
set theLevel to eachCue getLevel row input column output
copy theLevel to end of theLevels
end repeat
end repeat
-- get slices of original cues
set allSlices to slice markers of eachCue
repeat with eachSlice in allSlices
copy eachSlice to end of theSlices
end repeat
end if
end repeat
-- make combined file using the list we just built
set theNewFile to "combined-" & theDate & ".aiff"
do shell script soxPath & " -M " & theFiles & " -t aiff -r 48000 -b 24 ~/Desktop/" & theNewFile
-- make a new cue that uses the new combined file
make front workspace type "Audio"
set newCue to last item of (selected of front workspace as list)
set theTarget to ((path to desktop folder) & theNewFile) as string
set file target of newCue to theTarget
if theParent is "Main Cue List" then
set q name of newCue to "multitrack with " & theFirstCue & " and others"
else
set q name of newCue to theParent & " (multitrack)"
end if
-- set levels of the new cue
repeat with input from 1 to totalChannels
repeat with output from 1 to numberOfOutputs
set theItem to ((input - 1) * numberOfOutputs + output)
set theLevel to item theItem of theLevels
newCue setLevel row input column output db theLevel
end repeat
end repeat
-- set slices of the new cue
set slice markers of newCue to theSlices
end tell