Automatic Ambient Audio Adjustment

[QLab 4.1 or higher only]

This chapter examines automatically setting sound levels in response to the ambient level of an audience. It uses a method which measures the noise the audience is making in gaps between playback. It is very much a work in progress and you will need to refine it to your precise needs before trying it out with a real audience.

Here’s it is in action:

In the video, a recording of an audience is played through the speakers to simulate audience walla before a show.

When cue 1 is triggered, before the announcement plays, a measurement is taken from a microphone and the measured level is used to set the master playback level of cue ANN.

Detailed Set up:

For the purposes of this demo, we will assume use of  ch 1+2 of an interface and that you are not using microphone cues for any other purpose.

Play cue AUD and  set a level that corresponds to a medium level of audience chat in the theatre. (You can do this in your studio instead to experiment, but obviously it’s not going to work on headphones, or without an interface, as you need a mic input to QLab and the mic needs to pick up the audience sound)

We will assume the mic is plugged into channel 1 of your interface. As we never want to hear the output of this microphone, but we do need for it to meter at full level in the cue, we will use the microphone device matrix to effectively turn off the audio from the microphone.

In settings/audio/output patch for mic/edit patch/device routing set the  ch 1 input level to -59 and the crosspoint to output 1 to -59

Double check this. If you don’t do this step correctly your measuring mic will route to your speakers and will most likely feed back horribly.


With the audience simulation audio running, start cue SPLMIC (select it and press V on the keyboard) and adjust the mic input gain on your interface to give a medium level on the meter of cue output 1 (probably somewhere between 30 and 60 dB).

Start with the Mic Gain at minimum (in case you don’t have the correct settings in the Mic device output matrix) and bring the gain up slowly. If there is any feedback press ESC immediately.

input adjust

Trigger the cue SETLEV and then play cue ANN. Using the Audio Trim MASTER adjust the level of the announcement so it can be heard clearly over the audience ambiance. If your announcement is recorded at around dB -20 RMS, you will probably want a level around +10dB.


Press ESC to panic the workspace and fade all audio.

Play the audience simulation cue again and change it’s level (using the audio levels master in the cue) to that of a quiet audience

Play Cue 1 and the announcement should be played at a lower level.

Repeat with different simulated audience levels and tweak the trim on the announcement cue to give a good result at all levels. Remember the audience will probably be quieter when they register an announcement is being made.

How it works:

Once you have got all the patch and settings sorted, there is a single network cue that does all the work.

network cue

This cue sends an OSC message to localhost:

/cue/ANN/sliderLevel 0 #/cue/SPLMIC/liveAverageLevel/1#

The part of the message between the hashes (or octothorps as printers used to call them) is an OSC query. This reads the meter of channel 1 of cue SPLMIC and returns the level as the result of the query.  This is used to set the master slider (slider 0) of the cue numbered ANN to the returned level.

If the level of cue SPLMIC is at -40, i.e quiet, then the master level of the announcement will be set at -40. If the level  of SPLMIC is -5, i.e loud, then the master level of cue ANN will be set to -5. We will probably need a bit more level on the announcements and that additional gain is what we have provided by using the trim control.

You can download the workspace and example audio for the project so far here.

You will need to change the patch to your audio interface for audio and mic cues, AND SET THE LEVELS IN settings/audio/output patch for mic/edit patch/device routing. 



Now we have a working simple system we can now make several improvements to its functionality.

The main problem is that, in this simple version, we are sampling the level at a single point in time. If someone coughed loudly close to the measurement microphone, at the precise point the measurement was taken, then the announcement level might be set considerably higher than it needed to be.

In order to address this, we need to get as many level readings as possible in the time available and average them.  (You could also use multiple microphones and mix them in the microphone cue to average over many areas of the auditorium, lobby or foyer).

For an announcement, we could perhaps sample the level for 5 seconds prior to the announcement. If we run the cue to set the levels of pre-show or interval music in the gaps between tracks, we might want to restrict the measurement to no more than a second, so that the music appears continuous.

The workspace required is very similar to what we have used. But we now need to record a lot of data for the multiple sound levels somewhere and then perform a calculation on it.

First, we will give the Network Cue a duration. For this example, we will get it to measure for 5 seconds before the announcement. Set the action time of the network cue to 5 secs and add 5 secs to any pre waits.

Because OSC queries do not allow maths to be performed on the query within a cue we need to store all the levels data recorded somewhere, In this example, we can use the notes field of the network cue itself.

You can do a lot more string manipulation in OSC queries than is immediately obvious. The most important thing is you can change the result of a query to text by enclosing the query in quotes. You can also, within these quotes, make multiple queries. If we change our OSC message to:

/cue/SETLEV/notes “#/cue/SETLEV/notes##/cue/SPLMIC/liveAverageLevel/1 0 1#:”
>Which is setting the notes field of cue “SPL” to it’s existing contents & the new contents & “:”

What this means is that in a single OSC cue with a query you can construct complex text in a notes field, which you can then use in calculations in a script cue. In the example workspace, the OSC cue has a duration of 5 seconds. Meaning that for those 5 seconds it is constantly adding values to the notes field separated by colons. On a reasonably specced machine, it can record about 100 OSC query results in this time.

In our previous example, we were directly reading a logarithmic value in dB from the level of the microphone, which could be applied directly to a slider. If we are averaging the levels we want to average a linear value and then convert that to a logarithmic dB value. If you look at the liveAverageLevel query in the OSC message you will see that there are 2 additional parameters after the channel number in this case 0 and 1. These numbers represent the minimum and maximum values on a linear scale between 0 for no meter level (silence) and 1, for the loudest a liveAverageLevel can be.

The meters on QLab cues work  as follows.  When the meter is at the point where the fader rests at 0dB (i.e just before it turns red that is 0dB RMS on that  cue output channel, that produces a maximum level in an OSC query.  /cue/SPLMIC/liveAverageLevel/1 0 100
it will return a level of 100.
As you increase the level the metering will turn red and extend into the area above a fader at 0dB. This will still return the maximum value set for the OSC query .

We will need a cue to reset the notes field to empty before we start measuring. This can be a simple network cue

/cue/SETLEV/notes “”

We then need a script cue to do all the averaging and any other calculations on the data stored in the notes field and set the level of the announcement (or music) accordingly.

The revised workspace will look like this.

averaged workspace

This workspace listens to the microphone and samples its output level about a hundred times in 5 secs. It then converts an average of those 100 values to a dB value that can be used to set a fader on the ANN cue prior to it being played.

If you can site the measurement mic so that the audio playback does not make a significant difference to the level at the measuring mic you could run it continuously and have it update the fader level of the playing cue every 10 secs. To do this you would probably want to update a fade cue so that the level change happened over a few seconds.

Here’s the script that performs the calculation: 

set setlevelcue to “ANN” –cue to set master level of
tell application id “com.figure53.QLab.4” to tell front workspace
set thevalues to notes of cue “SETLEV” as text
set olddelimiter to AppleScript’s text item delimiters
set AppleScript’s text item delimiters to “:”
set thecount to (count of text items of thevalues) – 1
set theaccumulator to 0.0
repeat with n from 1 to thecount
set theaccumulator to theaccumulator + (text item n of thevalues as number)
end repeat
set theaccumulator to theaccumulator / thecount
set thelog to my log10(theaccumulator)
set thedecibel to 20 * (thelog)
–insert any extra maths to modify level setting (min. max etc) here
cue setlevelcue setLevel row 0 column 0 db thedecibel
set AppleScript’s text item delimiters to olddelimiter
end tell
on log10(thenumber)
set natural_log to (do shell script (“echo ‘l(” & (thenumber as string) & “)’ | bc -l”)) as real
set natural_log_of_10 to (do shell script (“echo ‘l(10)’ | bc -l”)) as real
set common_log to natural_log / natural_log_of_10
return common_log
end log10

How it works:

We set the cue number  we are going to change the master level of at the top of the script so it’s easy to find.

We set a variable called thevalues to the contents of the notes field to get the large number of recorded results from the OSC queries.

We change the text delimiters to “:” so that each recorded result becomes a separate text item

We sum all the records and divide by the number of records to get an average of the level over 5 secs.

We then need to calculate 20*(log10(the average level)

AppleScript is somewhat deficient in the Maths department so we have to use a shell script to do this calculation at the system level. This is the function at the bottom of the script.

We now have a usable level which is used to set the master slider level of the cue number set at the top of the script.

You can download the workspace here.

Further Development:

At this stage, you will probably want to modify these workspaces and scripts to produce a system that works for you and your typical setups.

Here’s a tidied up version of this tutorial workspace in action:

All the cues that do the measuring and set the levels have been moved to another cue list. The same cue can be used every time we want to set a level automatically.

In our main cue list,  we put the script cue NOALA into a’ fire first child and go to next cue’ group with the cue we want to set the level and play immediately below it.

We set the sample time for the measurement in the post-wait of the NOALA cue (without an auto-follow or auto-continue).

I have also moved the audience simulator to another cue list and made it hotkey controllable. Key 1 will start it and 0 will stop. Keys 1-9 set the audience level from very quiet to very loud.

In the demo video, the audience simulation is started

Cue 1 is triggered, which starts a 5-second Measurement process after which the music cue’s level is set and the cue plays.

During the measurement process the name of the cue that is being set changes to a flashing pattern

Cue 2 fades the music, and,  after a pre-wait in the group cue to wait until the music is no longer audible, a 2-second measurement is taken. This sets the level of an announcement which is then played.

At the end of the announcement, a 1-second measurement is taken and the opening music of the show fades in from a level that is dependent on the ambient noise and then fades up to an absolute level.

The final cue fades the opening music.

How it works:

In another cue list, we have all the cues necessary to measure the ambient level and set the level of cues and play them.

NOALA list

In the main list, every time we want an automatic level set, we put in the ‘ fire first child and go to next cue’ group with the NOALA script cue, and the cue we want to set the level and play immediately below it.

The NOALA script is this.

tell application id “com.figure53.QLab.4” to tell front workspace
set mycue to last item of (active cues as list)
set notes of cue “CALC” to uniqueID of cue after mycue
set duration of cue “SETLEV” to post wait of mycue
start cue “NOALA”
end tell

Every time it is run, it finds out the unique cue id of the cue below it and stores it in the notes field of cue “CALC” (in the other cue list)

It also sets the action time (duration) of the cue “SETLEV” to it’s post wait time.  The post wait time of the NOALA cue is the only thing that changes,  everything else is identical.

The cue that any NOALA script cue sets the level of and plays is always the cue that immediately follows it.

Finally, the script starts the cue in the other list numbered NOALA


In this cue we have the main script (numbered calc)

tell application id “com.figure53.QLab.4” to tell front workspace
set setlevelcue to (notes of cue “CALC”)
set theduration to duration of cue “SETLEV” as number
set theoldcuename to q name of cue id setlevelcue
start cue “ENGINE”
delay theduration + 0.1
set thevalues to notes of cue “SETLEV” as text
cue id setlevelcue setLevel row 0 column 0 db (do shell script “echo ” & notes of cue “SETLEV” & ” | awk ‘{s+=$1}END{print 20*log(s/(NR-1))/log(10)}’ RS=\” \” “)
stop cue “ENGINE”
set q name of cue id setlevelcue to theoldcuename
start cue id setlevelcue
end try
end tell

This gets the unique id of the cue we are setting the level of and playing from the notes field of cue “CALC” (which is where the NOALA script stored it)

it then sets a variable called the duration from the action time of the cue numbered “SETLEV” (which was set from the post-wait of the NOALA script cue)

It saves the q name of the cue we are changing the level of so that we can substitute our flashing indicator during the sampling period.

It then starts the group cue numbered  “ENGINE”  which contains the network cues from the previous examples which contain the OSC messages that take the measurements and store them, together with a little script cue which drives the flashing of the cue name

The main script then waits for the ENGINE group to do its work before doing the maths.

Rich Walsh has condensed all the maths in the previous version, to enable the averaging of all the values and conversion of the average to a dB level we can use to set the fader to a single instruction.

In the previous script, it took 16 lines to perform. Everything  in those 16 lines is now achieved with :

(do shell script “echo ” & notes of cue “SETLEV” & ” | awk ‘{s+=$1}END{print 20*log(s/(NR-1))/log(10)}’ RS=\” \” “)

The important bit of that code is awk. AWK is a programming language designed for text processing and typically used as a data extraction and reporting tool. It is a standard feature of most Unix-like operating systems. It is run outside of AppleScript as a shell script.

And that’s the great thing about scripting QLab. You can pretty much achieve anything by writing simple verbose scripts which pretty much read like everyday language, or if you know AWK or UNIX, or are willing to learn them, you can produce optimized compact code like the example above or combinations of the two.

After the maths is done, the script stops any cues it isn’t using anymore, sets the cue name back to what it was, sets the level based on the measurement, and plays the cue.

You can download the workspace and the audio from this final version of the project (for the purposes of this tutorial,  at least) here

Chapter Author: Mic Pool

Chapter Graphic: by Capture The Uncapturable
licensed under a Creative Commons Attribution 2.0 Generic License.
Creative Commons License

Music in the final demo: “We Got Trouble”, “Welcome to the Show”
Kevin MacLeod (
Licensed under a Creative Commons Attribution 3.0 Unported License.
Creative Commons License