Level Playing Field

Note: This chapter has been updated with a new link to the r128x tool, but using it on Apple Silicon-based Macs may require some adjustments not yet described herein.

Some QLab users often find themselves having to assemble cue lists from audio files supplied at the last minute, with wildly differing levels. Throwing those audio files into QLab without having the time to manually check levels can result in audibility problems at least, and conceivably hearing damage.

This tutorial demonstrates a method to analyze a set of selected cues in a QLab workspace and adjust their master levels in the Audio Levels inspector tab, so that they all playback at a subjectively similar volume.

This is useful for any situation where you want to normalize a set of cues to a standard playback level.

There are many ways of defining the loudness of an audio file but one of the best, and the one used in this tutorial, is the European Broadcasting Union’s R128 standard which defines the LUFS measurement system. LUFS stands for Loudness Units Full Scale which is a carefully developed method which measures the loudness of an audio signal taking into consideration many aspects of the physiology of human hearing, with the aim of ensuring that tracks with equal LUFS will be perceived subjectively as being at the same volume. The R128 standard both defines this unit, and outlines its use in broadcast audio.

To achieve the goal of matching Audio cues’ levels to the R128 standard entirely within a QLab workspace, some way is needed of expanding the capabilities of QLab to analyze files to calculate LUFS. This project uses a command line tool that can easily be installed and then utilized within an AppleScript in a QLab Script cue.

The analyzer is called r128x, and it was written by Audionuma and released under a GNU General Public License. It’s now maintained by Shiki Suen and Pan93412. You can see and download the source code on Shiki Suen’s GitHub page here, and a compiled and tested copy of the program is included in the example workspace download link.

To use it, you install the file called r128x-cli into the folder /usr/local/bin, which you can easily locate by choosing Go to Folder… from the Go Menu in the finder.

Once you’ve installed r128x-cli into the /bin folder, right-click (or two-finger-click, or control-click) on it, mouse over to the Open with… submenu, and choose Terminal. You’ll be presented with a warning; do you really want to do this? Answer yes, you do. It will open a window in Terminal.app and run itself. Once that’s done, which will be more or less immediate, simply quit Terminal. After you do this once, your Mac will regard this file as safe to run, and QLab will be able to use it without any further intervention from you.

Go To Folder...

Here it is in action:

In the video you can see four Audio cues with their master levels set to -10.

While this video was being created, an A-weighted SPL meter measuring the actual output of QLab was on a camera shown in the Audition Window.

The Piano cue is metering in the bottom half of the meter, around 60 to 70dBA.

The Announcement cue is metering in the top half of the meter, around 70 to 76dBA.

The Disco cue is off the top of the scale.

The Brass Band cue is so quiet it doesn’t even register on the meter.

When the script is run (hotkey ⌃L in the example), the selected Audio cues are analyzed and each cue’s master level is adjusted so that all cues have a subjectively similar loudness.

When the cues are played again, they are, indeed, all at the same volume.

How it works:

Here’s the AppleScript inside the Script cue:

set theReferenceLevel to -24 --set desired LUFS level
set thefaderLevel to -0 --set the master fader level for your preferred output level for cues with an LUFS at the reference level
set currentTIDs to AppleScript's text item delimiters
tell application id "com.figure53.QLab.4" to tell front workspace
  display dialog "WARNING: This will change the master levels of all selected cues" & return & return & "A dialog will signal when the level setting is complete." & return & return & "PROCEED?"
    set theselected to the selected as list
    if (count of items of theselected) > 0 then
      repeat with eachcue in theselected
        if q type of eachcue is "audio" then
          set currentFileTarget to quoted form of POSIX path of (file target of eachcue as alias)
          set theLUFS to (do shell script "/usr/local/bin/r128x-cli" & " " & currentFileTarget as string)
          --parse theLUFS to extract the actual LUFS from a very long string
          --replace every occurrence of "+" with "plus"
          set AppleScript's text item delimiters to "+"
          set the item_list to every text item of theLUFS
          set AppleScript's text item delimiters to "plus"
          set theLUFS to the item_list as string
          --replace every occurrence of "-" with "minus"
          set AppleScript's text item delimiters to "-"
          set the item_list to every text item of theLUFS
          set AppleScript's text item delimiters to "minus"
          set theLUFS to the item_list as string
          set AppleScript's text item delimiters to currentTIDs
          --get the third word from the end
          set the theLUFS to word -3 of theLUFS
          --replace the string "minus" in theLUFS with "-"
          if character 1 of theLUFS = "m" then
            set theLUFS to "-" & characters 6 thru -1 of theLUFS
            --replace the string "plus" in theLUFS with "+"
            set theLUFS to "+" & characters 5 thru -1 of theLUFS
          end if
          set theadjustment to (theReferenceLevel - theLUFS) + thefaderLevel
          set the notes of eachcue to theLUFS & " " & theadjustment
          eachcue setLevel row 0 column 0 db theadjustment
        end if
      end repeat
      display dialog "Level Setting Complete" buttons "OK" default button "OK"
    end if
  end try
end tell

At the top of the script, two variables are set. The first is the target LUFS, which in the demo is set to -24 LUFS, which is a standard broadcast level and a good starting point.

Since it’s possible that some cues will already be at the target LUFS, the second variable lets you set the master level control which will represent that loudness.

So, in the example above, if you have a cue whose audio target file has a LUFS of -24, that cue will replay at your preferred volume when the master level control is at -10. A file with an LUFS of -34 would require a boost of +10dB to play at your preferred volume, so the fader would be at 0dB. A file with an LUFS of -14 would require a 10dB cut, so the fader would be at -20dB.

It’s important not to set the value of faderLevel too high. If a quiet cue required a 20dB boost and faderLevel was set to -10, then the master level control of that quiet cue would need be set to +10 to achieve the boost. If faderLevel was set to -5, though, the required master level would be +15. Since the default maximum level in QLab is +12, this would not work correctly in most workspaces.

The script then records the current value of AppleScript’s text delimiters. Text delimiters are the characters that AppleScript uses to separate text items in strings. We are going to change the text delimiter in a moment, and it’s good practice before changing it to get its current value and store it so you can change it back when you have finished.

A warning is displayed that the script will change the levels of selected cues, to give an opportunity to cancel the process if the script has been triggered accidentally.

The script then checks that some Audio cues have been selected and then does the following for each selected cue:

  1. Gets the full file path of the target audio file.
  2. Runs a shell script which calls r128x-cli to analyze that file and return a LUFS value.
  3. Reformats the text returned by r128x-cli. This requires some discussion.

r128x-cli returns the LUFS in a long string, with lots of other information we don’t need, as it normally prints its results in a terminal window. The string even includes progress indicators as the file is analyzed. The string output for printing looks something like this:

FILE                                       IL (LUFS)    LRA (LU)  MAXTP (dBTP)
Brick Walled Loud Disco.mp3                     -6.3        +0.2          +1.8

All that is required for this application is the LUFS value, i.e. -6.3. In theory this should be easy to extract as it is the third word from the end of the string. However a couple of “features”’” of AppleScript prevent it being as straightforward as that.

AppleScript treats a ”+"" character at the beginning of a word as a separate word, and erases any ”-” character at the beginning of a word. For our purposes, this is less than helpful.

By manipulating AppleScript’s text item delimiters, we can perform a find-and-replace to change every minus sign to the word “minus” and every plus sign to the word “plus”. Once we do that, the value we want is indeed the third word from the end, which in AppleScript terminology is word -3. Once we copy that word, we just change the word “minus” back to the symbol ”-” and we have an LUFS value with the correct sign which can be read as a number.

The script then sets the master level of the cue by taking the LUFS reference level (set at the top of the script), subtracting the measured LUFS, and then adding the fader level (also set at the top of the script).

Finally, the script informs the user that all the audio files have been analyzed, and their levels set. The analysis and level setting is reasonably fast, but if you select a lot of cues you should be prepared to wait a bit. AppleScript has never won any awards for speed.

Chapter Image: Wikimedia Commons Public Domain

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

Disco music: Ether Disco by Kevin MacLeod. Licensed under Creative Commons: By Attribution 3.0

Brass band music: Public Domain.

Spoken word announcement: All rights reserved. Not for use outside this demo.