Mind-bending MIDI Binding

Many digital audio workstations (DAWs) and audio performance tools like MainStage or Live use MIDI to allow you to bind a hardware control like button, fader, or knob to a particular software control like a volume slider or an on/off button for an effects bus.

QLab allows some use of MIDI controls, but it’s limited to several specific areas and is not comprehensive. QLab has a whole audio mixer in every Audio cue, and many parameters across all cue types. The methods that other programs use to bind MIDI controllers to software parameters would be difficult to incorporate in a way that would be applicable for every use.

OSC is far more suitable for external control of QLab parameters. Through pattern matching, wildcards, and special QLab addresses such as selected and active, control can be directed precisely where it is required.

Unfortunately, although there are many ways to produce OSC messages using software with virtual faders and switches, most hardware controllers still use MIDI, not OSC. To use these controllers but still claim the benefits of OSC, third-party tools like OSCulator can accept MIDI messages and convert them to arguments in OSC messages. For example, you could configure OSCulator to translate MIDI control change 1 to a /cue/1/sliderLevel command, allowing the MIDI controller to adjust the master audio level of cue 1.

This chapter describes a method for controlling audio level sliders in Audio and Fade cues using standard MIDI control surfaces an no additional software. It begins with a simple method for controlling the master audio level slider of a selected cue using fixed-travel rotary MIDI controls. This method is then expanded upon so that the controller will pick up the QLab slider only when the controller value matches the equivalent slider level, avoiding level jumps. It then develops this technique to control the master audio level using an endless-travel encoder, and shows an example of a MIDI device controlling stems of a multitrack audio cue. It goes on to explore MIDI control of other cue parameters.

Example 1: Akai LPD8 controlling the master audio level of the selected cue

The Akai LPD8 is a simple controller, with eight fixed-travel rotary controls (i.e. knobs) and eight pads (i.e. buttons). In this video, the rotary control is configured to send control change 1 messages on MIDI channel 11. Any MIDI controller with rotary controls can be used in the same way.

Here it is in action:

A cue is played and its master audio level slider is controlled with an external MIDI controller. The Akai LPD8 is set up like this:

Akai LPD8 setup

A configuration file for the Akai editor is included in the example download.

How it works

The workspace acquires MIDI data from the controller and processes it into QLab slider values by using MIDI triggers assigned to Network cues. The Network cues send OSC messages such as this one to QLab:

/cue/selected/level/0/1 -10

The knob on the MIDI controller can send 128 possible values, as defined by the MIDI specification. So, one Network cue is created for each possible controller value, with the MIDI trigger for each cue set to the corresponding value. Each Network cue outputs an OSC message setting the master audio level slider in the selected Audio cue to the equivalent level in dB.

Akai LPD8 setup

If the minimum audio level of the workspace is set to the default -60 dB and the maximum is the default 10 dB, that equates to a 70 dB range across the 128 possible MIDI controller values.

The conversion between each increment of the MIDI value to a corresponding change in dB can be calculated with this formula:

the step value = (the maximum - the minimum) / 127

In this case, a 70 dB range results in a step value of 0.551181102362, which is 70 divided by 127.

A resolution of 1 dB is adequate, so we can create a rounded value for the slider using this formula:

slider level in dB = round(minimum + ( step value * controller value))

As an example, if the knob is turn to a value of 50, we calculate the level of the slider like this:

round(-60 + 50 * 0.551181102362) = -32dB

Some other examples:

  • MIDI value 0 = slider level -60 dB
  • MIDI value 108 = slider level 0 dB
  • MIDI value 127 = slider level +10 dB

Effectively, we’ve created a lookup table inside QLab consisting of 128 Network cues, each responding to a single specific level from the incoming MIDI CC value and sending a single specific dB level to the selected cue in QLab. In actual table form, it might look like this:

Cue MIDI trigger Outgoing OSC message
cc1_0 CC 1 0 /cue/selected/level/0/0 -60
cc1_64 CC 1 64 /cue/selected/level/0/0 -25
cc1_127 CC 1 127 /cue/selected/level/0/0 10

The full table would have all 128 rows; this just shows the first, middle, and last rows.

Here’s a video of the Network cues in QLab, triggered by incoming MIDI CC messages. For the purposes of the video, a 0.1 second post-wait has been added to all the cues to make it easier to follow visually.

This works very well for the currently selected cue, but what happens if, for example, we set a cue’s master audio level to +5 dB and then select another cue whose master audio level is currently set at -30 dB?

As soon as the knob is turned, the slider immediately jumps to the current controller level, which is undesirable, disruptive, and potentially dangerous.

What is required is a method that only allows a MIDI controller to take control of a QLab level when the incoming MIDI value precisely matches the selected cue’s equivalent current level in dB.

Example 2: Akai LPD8 controlling the master audio level of the selected cue with pickup

When a cue is selected, tapping a button on the MIDI controller, in this case pad #4, triggers a Script cue in QLab. This Script cue ensures that the knob will only change the master audio level of the selected cue once it has matched the current level, avoiding any jumps.

How it works:

The downloadable example workspace contains a cue list called “MIDI to OSC translate” which contains two Group cues.

The first Group cue, numbered SetS1, contains the same 128 Network cues as in the first example. These translate incoming MIDI messages to OSC messages which set the master audio level of the selected cue in QLab. These Network cues are numbered cc1_0, cc1_1, cc1_2, etc.

The second Group cue, numbered Pick, contains a second set of 128 Network cues with the same MIDI triggers as the cues in SetS1. In this Group, every Network cue sends an identical OSC message:

/cue/SetS1/armed 1

This message arms the Group cue SetS1 and therefore all of the Network cues inside that Group. These Network cues are numbered Pcc1_0, Pcc1_1, Pcc1_2, etc., and by default they are all disarmed.

When the button on the MIDI controller is pressed, it sends a Note On message (note 39 in this example) which triggers a Script cue numbered TAKE. The script in that cue is as follows:

set theminimum to -60 --minimum audio level in QLab general/settings
set themaximum to 10 --maximum audio level in QLab general/settings

-------Do not alter anything below this line-----

set thestep to (themaximum - theminimum) / 127

tell application id "com.figure53.QLab.4" to tell front workspace
    try
        --find the currently selected cue
        set theselected to last item of (selected as list) -- selected audio cue
        --Disarm all level setting and level matching cues
        set the armed of cue "Pick" to false
        set the armed of cue "SetS1" to false
        --get the current level of the master slider of the selected cue
        set thelevel to getLevel theselected row 0 column 0
        --calculate the equivalent MIDI cc controller value
        set thelevel to round ((thelevel - theminimum) / thestep)
        --convert the controller value to the required Pickup cue number
        set thecue to "Pcc1_" & (thelevel as string)
        delay 0.1
        --arm the cue
        set the armed of cue thecue to true
    end try
end tell

This script:

  • Finds out which audio cue is currently selected (If no audio cue is selected, the try block will cause the cue to be ignored).
  • Disarms the group numbered Pick, and therefore all 128 cues in the group.
  • Disarms the group numbered SetS1, and therefore all 128 cues in the group.
  • Gets the current level of the master slider of the selected cue.
  • Converts the slider level to the equivalent MIDI controller value using this formula: MIDI cc value=round ((thelevel - theminimum) / thestep)
  • Derives a cue number from the MIDI value, e.g. Pcc1_72
  • Arms that cue.

When the knob on the MIDI controller is rotated, it triggers one cue in each group: a cue in Group SetS1, and a cue in Group Pick which arms Group SetS1.

When the pad is tapped, the Script cues disarms every cue in SetS1 and every cue in Pick, then re-arms the single cue in Pick which corresponds to the current master audio level of the selected cue. Until the MIDI controller sends the MIDI value corresponding to the sole armed cue, nothing happens.

In the screen shot below, the master audio level of the selected cue is -15 dB which corresponds to MIDI value 82. So, cue Pcc1_82 is the only cue that is armed.

Matching CC values

When the knob on the controller is turned to 82 that cue is triggered, rearming all the cues in Group SetS1 and allowing the MIDI controller to once again adjust levels freely.

Example 3: Behringer X-Touch Mini controlling the master audio level of the selected cue

The Behringer X-Touch Mini is an inexpensive, commonly available controller with eight endless-travel rotary encoders. This example can be easily adapted for other MIDI devices with rotary encoders, or motorized faders.

A rotary encoder is a knob with an endless track. They are usually used for level setting applications in one of two ways:

  • In relative mode, a counter-clockwise rotation of the control produces one control change value, usually 127, and a clockwise rotation produces another, usually 1. Some controllers will vary the actual value depending on the speed at which the encoder is turned.
  • In absolute mode, the encoder behaves like a fixed-travel knob, but the current value can be updated via a MIDI message sent from QLab, so that the encoder’s current virtual position can change to match the slider position in QLab, and rotation will start at that point the next time the control is adjusted.

Relative Mode

On the face of it, the simplest way of interfacing an encoder to a QLab level is to use relative mode. This would only require two cues per control, one for rising levels and one for falling, instead of the 128 cues required for the absolute method.

For example, to control the master audio level of the selected cue, you’d need one Network cue triggered by a MIDI value of 127, sent when the encoder rotates counter-clockwise, which would send this OSC message:

/cue/selected/level/0/0/- 1

This message subtracts 1 from the current level.

Then you’d need a second Network cue triggered by a MIDI value of 1, sent when the encoder rotates clockwise, which would send this OSC message:

/cue/selected/level/0/0/+ 1

This message adds 1 to the current level.

Here it is in action:

The example demonstrates a major problem with this method. Despite vigorous rotation of the encoder for eight seconds, the slider has only increased in level by 20 dB. The encoder has actually sent over 150 MIDI messages, so the slider should have traveled its full range of 70 dB in only four seconds, which is nearly eight times faster.

The Logs tab in the Workspace Status window shows that every MIDI message the controller sends is triggering the Network cue, but less than 20 percent of those triggers results in an OSC message being sent.

The reason this is happening is that QLab protects itself from being overrun by OSC data by throttling the number of identical OSC messages that it will accept in a given period of time. Because of that, this method is not ideal for this kind of control, although in the next example we will use it for fine adjustments.

Absolute Mode

With the encoder in absolute mode, it behaves essentially just like the knobs on the Akai. The entire 70 dB range can be traversed, still with 1 dB resolution, by rotating the encoder by a single full revolution. This feels much more appropriate to the current task, as long as we can solve the “pickup” issue.

Fortunately, most endless encoders have a built-in switch to signal to the receiving application that a knob has been touched. In more expensive controllers this is frequently a touch sensor built into the knob. On the Behringer, and some other low-cost controllers, this is achieved by physically depressing the knob. We can use the MIDI message generated by that switch to trigger the update.

Here it is in action:

The first Audio cue is played, and the switch on encoder 1 is depressed, triggering a Script cue in QLab to read the master audio level of the selected cue, convert that to an equivalent MIDI value, and send that value to the MIDI controller to update the position of the encoder. The LEDs around the encoder display the new level, and adjustments proceed from there. The process is repeated for the second track, and moving between the two is seamless.

How it works

Encoder cue list

The downloadable example workspace contains a cue list called “MIDI to OSC translate” which contains 128 Network cues triggered by the 128 possible values of the encoder, just as in the previous examples. It also contains a Script cue numbered SL0 which is triggered by the MIDI message sent by clicking the encoder. Here’s the script in that cue:

--set MIDI patch 8 to your controller in Workspace Settings/MIDI
set theslider to 0 -- slider to control, 0 is Master
set theCC to 1 -- MIDI cc number for this slider
set theminimum to -60
set themaximum to 10
set thestep to (themaximum - theminimum) / 127
tell application id "com.figure53.QLab.4" to tell front workspace
    try
        set theselected to last item of (selected as list)
        set thelevel to getLevel theselected row 0 column theslider
        set thelevel to round ((thelevel - theminimum) / thestep)
        set byte one of cue "MOP" to theCC
        set byte two of cue "MOP" to thelevel
        start cue "MOP"
    end try
end tell

The script gets the master audio level of the currently selected cue, converts the level to a MIDI value, and then inserts that value into a MIDI cue numbered MOP which is pre-configured to send MIDI messages to the hardware controller. Finally, the script triggers MOP, thus updating the controller.

Example 4: Native Instruments Kore controlling multiple audio stems

The Kore controller, which is no longer in production but readily available second hand, is a device with a very solid construction and high quality components. It has twenty switches and nine encoders, eight of which have touch-sensitive switches. When one of those knobs is touched, the Kore sends a MIDI message which can be used to trigger QLab to send the existing level of the corresponding slider. The encoders are very responsive, which means it is easy to achieve large slider travels.

While the previous examples have all focused on adjusting the master audio level only, this example will explore using the various knobs on the controller to adjust a variety of levels within the selected Audio cue or Fade cue.

QLab can be programmed to either transfer all the levels at once when any knob is touched, or to just transfer the level corresponding to that knob. Since the level indicators on this controller are fairly rudimentary, the example will focus on transferring just the levels corresponding to the knob that is touched.

The Kore has a good macOS editor, so we can configure the mapping of MIDI messages on the Kore exactly as we like:

  • MIDI channel 11
  • Knobs 1 - 8 send control change 1 - 8
  • Knob switches send Note on 0 to 7
  • Additionally, the large encoder wheel that doesn’t have a touch switch (knob number 9) is set up for precise adjustment of the master audio level in .5dB increments.

Kore setup for stem mixing

Here it is in action adjusting levels on some cues with separate stems for drums, piano, strings, bass, flute, and vibraphone. The workspace has all cue outputs routed to a stereo pair of device outputs.

There are three stereo tracks, each on a pair of ganged outputs, and three mono tracks on single outputs. These nine cue outputs are mixed down to stereo in the Device Routing tab of the audio device editor.

Mixdown

Encoder number 8, in the lower right corner of the grid of eight knobs, is configured to adjust the master audio level of the selected cue, and encoders 1 through 6 are configured to adjust the level of cue outputs 1, 3, 5, 7, 8, and 9.

Audio levels in QLab follow the controller movements very closely even though the visual interface appears to lag. The movements of on-screen sliders is particularly sluggish, but the actual levels update more or less instantly. It is best to concentrate on the level indicators on the Kore controller itself or adjust purely by ear.

As each encoder is touched, it updates the controller to match the current level of the corresponding output in QLab. Subsequent movement of the controller adjusts the level from that starting point.

Multiple levels can be adjusted simultaneously.

How it works

The workspace contains a cue list called “MIDI to OSC translate” which contains a number of cues.

Stems cue list

A Memo cue numbered MIN stores the minimum audio level as set in Workspace Settings → Audio in its notes field.

A Memo cue numbered STEP stores the change in dB level that corresponds to a MIDI value of 1, as discussed and calculated above.

A Midi cue numbered MOP sends MIDI messages to the Kore set the encoder levels as in the previous example.

A Group cue numbered SetS contains one Group cue and one Script cue for each knob on the Kore that we’re using. Group SetS1 and Script SL1 correspond to the first knob, SetS2 and SL2 correspond to second, and so on.

The Group cues all contain 128 Network cues which operate the same way as in the previous examples.

The Script cues are each triggered when the corresponding knob is touched, and they all contain essentially the same script. The only differences are which cue output they query, and which knob they update. Here’s the script for SL7:

set theslider to 7 -- slider to control, 0 is Master
set theCC to 4 -- MIDI cc number that controls slider
tell application id "com.figure53.QLab.4" to tell front workspace
    try
        set theselected to last item of (selected as list)
        set thelevel to getLevel theselected row 0 column theslider
        set thelevel to round ((thelevel - (notes of cue "MIN")) / (notes of cue "STEP"))
        set byte one of cue "MOP" to theCC
        set byte two of cue "MOP" to thelevel
        start cue "MOP"
    end try
end tell

The script gets the level of the relevant cue output, converts it to the equivalent MIDI value, sets MIDI cue MOP to send that value to the appropriate knob on the Kore, and starts the MIDI cue.

In a separate cue list called “MIDI delta”, two Network cues utilize the “relative mode” approach above to use the large knob on the Kore to make single dB adjustments to the master audio level of the selected cue.

Example 5: cue list generator

Example 4 required over 600 cues in QLab. If we expanded that example to control eight cue outputs, it would take more than 2000 cues. Building those cues could easily take hours.

In all the examples in this tutorial, however, the cues that interface with the hardware controllers have all been automatically generated from a single script in a matter of minutes.

This example explores that single script in detail. The actual mechanics of the script are fairly straightforward, but it can look quite daunting at first glance. This is because the script has the versatility to generate cue lists for any number of fixed-travel knobs or endless-travel encoders addressing any number of level controls in QLab. Since it can save days of programming, it’s worth studying in detail.

Here it is in action:

One thousand cues are generated in in this example. The video has been sped up to show the process in about 45 seconds; depending on the speed of your computer, it would actually take between two and five minutes or so.

Here’s the script:

--CLOSE INSPECTOR BEFORE RUNNING FOR UP TO 10 TIMES SPEED INCREASE
--Script to generate utility cues, 128 cues per controller to translate MIDI cc values to OSC to set slider levels, and cues to feedback current level of sliders of selected cue in dB to MIDI controller as a cc value.
--set MIDI Patch 8 to MIDI controller device
-- set script cue template with 'run in separate process' UNCHECKED
--set Network Cue template to MIDI ch of controller in triggers
--set Script Cue template to MIDI ch of controller in triggers


--user adjustable variables

set encoders to true --(boolean) true if controller has motorized fader or is rotary encoder, false for dumb fader or knob.
set allatonce to true -- if true any encoder switch will update levels on all encoders. If false a switch will only updte it's own encoder (only used for encoders)

set theminimum to -60 --minimum audio level as set in settings/audio
set themaximum to 10 --maximum audio level as set in settings/audio
set theMIDIcc to 1 -- MIDI cc of first MIDI cc controller
set theMIDInote to 0 --MIDI note to read levels of selected cue for takeover or note sent when first MIDI encoder touched (0 for Behringer X-Touch compatibility out of box)
set theMIDIch to 11 -- Midi Channel to send feedback to encoders (only needed for encoders) (11 for Behringer X touch compatibility out of box)
set numberOfControllers to 8 -- number of consecutive controllers
set listOfSliders to {0, 1, 3, 5, 7, 8, 9, 10} -- list of sliders controlled by midi controllers. Master is 0. Number of items in list must match number of controllers. 


---do not change anything below this line-----

--error check routines for variables above

if (count of items of listOfSliders) is not equal to numberOfControllers then
    display dialog "Slider list does not match number of controllers"
    return
end if

--calculate the step value

set thestep to (themaximum - theminimum) / 127 --step value for slider in dB in QLab, per increase of 1 in MIDI cc value.

--make text formatted list of sliders for use in script sources below

set formattedlist to "{"
repeat with x from 1 to numberOfControllers
    set formattedlist to formattedlist & item x of listOfSliders
    if x is not numberOfControllers then set formattedlist to formattedlist & ","
end repeat
set formattedlist to formattedlist & "}"
set thelistofsliders to formattedlist

--make text for preflight check dialog display

set thechecklist to ""
if encoders is true then
    set thechecklist to thechecklist & "GENERATING CUES FOR ENCODERS" & return & return & "MIDI ch to send to encoders is: " & theMIDIch & return & return
    if allatonce is false then
        set thechecklist to thechecklist & "An encoder's switch only updates its own encoder" & return & return
    else
        set thechecklist to thechecklist & "All encoders updated with any encoder switch" & return & return
        
    end if
else
    set thechecklist to thechecklist & "GENERATING CUES FOR POTS" & return & return
end if

set thechecklist to thechecklist & "Minimum audio level as set in settings audio is: " & theminimum & return & return & "Maximum audio level as set in settings audio is: " & themaximum & return & return & numberOfControllers & " controllers starting with cc" & theMIDIcc & return & "Controlling sliders: " & thelistofsliders & return & return


if encoders is false then set thechecklist to thechecklist & "MIDI note to take over levels: " & theMIDInote & return & return

-- main program

tell application id "com.figure53.QLab.4" to tell front workspace
    
    --check user actually wants to generate a list and present the preflight check list
    
    display dialog "QLab will Generate cues to convert MIDI cc messages to OSC" & return & return & "Are you sure you want to do this?" & return & return & "Delete all cues created previously by this script before running" & return & return & "Some settings cannot be managed through scripting!" & return & return & "In settings:" & return & "Set MIDI Patch 8 to MIDI controller device" & return & return & "Set script cue template with 'run in separate process' UNCHECKED" & return & return & "Set Network Cue template Triggers tab to MIDI ch of controller" & return & return & "CUE GENERATOR WILL RUN UP TO 10 TIMES FASTER IF INSPECTOR IS CLOSED" & return & return & thechecklist with icon "warning"
    
    
    --make memo cues to store variables
    
    --Make Memo cue to store data from preflight check list in notes field
    make type "Memo"
    set theselected to last item of (selected as list)
    set the q number of theselected to "SUM"
    set the notes of theselected to thechecklist
    set the q name of theselected to thechecklist
    
    --Make Memo cue to store minimum audio level for use in calculations in notes field
    make type "Memo"
    set theselected to last item of (selected as list)
    set the q number of theselected to "MIN"
    set the notes of theselected to theminimum
    set q name of theselected to theminimum
    
    --Make Memo cue to store step level for use in calculations, in notes field
    make type "Memo"
    set theselected to last item of (selected as list)
    set the q number of theselected to "STEP"
    set the notes of theselected to thestep
    set the q name of theselected to thestep
    
    --make a MIDI cue to feedback current level of a slider to MIDI controller
    make type "MIDI" --set up cue template to set patch etc.
    set theselected to last item of (selected as list)
    set the patch of theselected to 8
    set the q number of theselected to "MOP"
    set the message type of theselected to voice
    set the command of theselected to control_change
    set the channel of theselected to theMIDIch
    
    
    --Create Take cue to store current slider level and script to prepare level takeover
    if encoders is false then
        make type "Script"
        set theselected to last item of (selected as list)
        set the q number of theselected to "TAKE"
        set the q name of theselected to "Prepare level takeover"
        set the midi trigger of theselected to enabled
        set the midi command of theselected to note_on
        set the midi byte one of theselected to theMIDInote
        set the midi byte two string of theselected to ">0"
        set the script source of theselected to "set thelistofsliders to " & thelistofsliders & return & "tell application id \"com.figure53.QLab.4\" to tell front workspace" & return & "try" & return & "set theselected to last item of (selected as list)" & return & "set the armed of cue \"Pick\" to false" & return & "set the armed of cue \"SetS\" to false" & return & "repeat with aSlider from 1 to " & numberOfControllers & return & "set thelevel to getLevel theselected row 0 column (item aSlider of thelistOfSliders)" & return & "set thelevel to round ((thelevel - (notes of cue \"MIN\")) / (notes of cue \"STEP\"))" & return & "set thecue to \"Pcc\" & (aSlider as string) & \"_\" & (thelevel as string)" & return & "delay 0.1" & return & "set the armed of cue thecue to true" & return & "end repeat" & return & "end try" & return & "end tell"
    end if
    
    --make a group cue to enclose all the group cues relating level setting of a MIDI controller
    make type "Group"
    set theselected to last item of (selected as list)
    set the mode of theselected to fire_first_go_to_next_cue
    set the q name of theselected to "set slider cues"
    set the q number of theselected to "SetS"
    
    --make a group cue to enclose all the group cues relating to level matching of a current slider position to MIDI controller value for take over
    if encoders is false then
        make type "Group"
        set theselected to last item of (selected as list)
        set the mode of theselected to fire_first_go_to_next_cue
        set the q name of theselected to "Pickup level Mechanism"
        set the q number of theselected to "Pick"
    end if
    
    
    if encoders is true and allatonce is true then
        --make a script that sends all the current slider levels in use, all at once, to the MIDI controllers
        make type "SCRIPT"
        set theselected to last item of (selected as list)
        set the q name of theselected to "Feedback all levels to MIDI controller"
        set the q number of theselected to "AAO"
        set the script source of theselected to "set thesliderslist to " & thelistofsliders & return & "set numberofsliders to " & numberOfControllers & return & "set theCC to " & theMIDIcc & return & "tell application id \"com.figure53.QLab.4\" to tell front workspace" & return & "try" & return & "set theselected to last item of (selected as list)" & return & "repeat with aSlider from 1 to numberofsliders" & return & "set thelevel to getLevel theselected row 0 column (item aSlider of thesliderslist)" & return & "set thelevel to round ((thelevel - (notes of cue \"MIN\")) / (notes of cue \"STEP\"))" & return & "set byte one of cue \"MOP\" to theCC - 1 + aSlider" & return & "set byte two of cue \"MOP\" to thelevel" & return & "start cue \"MOP\"" & return & "end repeat" & return & "end try" & return & "end tell"
        
        --make a start cue for each encoder switch to start the group cue that feeds back all slider levels to the controller.      
        repeat with astartcue from 1 to numberOfControllers
            make type "start"
            set theselected to last item of (selected as list)
            set the cue target of theselected to cue "AAO"
            set the q name of theselected to "Triggered by encoder switch MIDI note " & theMIDInote - 1 + astartcue
            set the q number of theselected to "AAO" & astartcue
            
            
            set the midi command of theselected to note_on
            set the midi byte one of theselected to theMIDInote - 1 + astartcue
            set the midi byte two string of theselected to ">0"
            set the midi trigger of theselected to enabled
        end repeat
        
    end if
    --
    
    repeat with cc from 1 to numberOfControllers
        --make a group cue for 128 cues that will control slider levels
        make type "Group"
        set theselected to last item of (selected as list)
        set the mode of theselected to fire_first_go_to_next_cue
        set the q name of theselected to "Controller " & (cc as string)
        set the q number of theselected to "SetS" & (cc as string)
        move cue id (uniqueID of theselected) of parent of theselected to end of cue "SetS"
        
        --make 128 cues triggered by cc values 0-127 to set slider levels via OSC and put them in the newly created group cue
        repeat with x from 0 to 127
            make type "Network"
            set theselected to last item of (selected as list)
            --set the correct triggerfor the new cue
            set the midi command of theselected to control_change
            set the midi byte one of theselected to theMIDIcc + (cc - 1)
            set the midi byte two of theselected to x
            set the midi trigger of theselected to enabled
            set the osc message type of theselected to custom
            --set the OSC message to be sent by the new cue
            set thelevel to theminimum + x * thestep
            set thelevel to round (thelevel) rounding to nearest
            set the custom message of theselected to "/cue/selected/level/0/" & item cc of listOfSliders & " " & thelevel
            set the q number of theselected to "cc" & theMIDIcc + (cc - 1) & "_" & x
            move cue id (uniqueID of theselected) of parent of theselected to end of cue ("SetS" & (cc as string))
        end repeat
        
        if allatonce is false then
            -- make a MIDI input script to get current value of QLab parameter and send the equivalent MIDI level to the MIDI controller)
            make type "SCRIPT"
            set theselected to last item of (selected as list)
            set the q number of theselected to "SL" & item cc of listOfSliders
            set the q name of theselected to "send slider" & (item cc of listOfSliders) & " value to MIDI controller"
            set the midi trigger of theselected to enabled
            set the midi command of theselected to note_on
            set the midi byte one of theselected to (theMIDInote + (cc - 1))
            set the midi byte two string of theselected to ">0"
            set the script source of theselected to "set theslider to " & (item cc of listOfSliders) & " -- slider to control, 0 is Master" & return & "set theCC to " & (theMIDIcc + (cc - 1)) & " -- MIDI cc number for this slider" & return & "tell application id \"com.figure53.QLab.4\" to tell front workspace" & return & "try" & return & "set theselected to last item of (selected as list)" & return & "set thelevel to getLevel theselected row 0 column theslider" & return & "set thelevel to round ((thelevel - (notes of cue \"MIN\")) / (notes of cue \"STEP\"))" & return & "set byte one of cue \"MOP\" to theCC" & return & "set byte two of cue \"MOP\" to thelevel" & return & " start cue \"MOP\"" & return & "end try" & return & "end tell"
            
            move cue id (uniqueID of theselected) of parent of theselected to end of cue "SetS"
            
        end if
        
        
        if encoders is false then
            --make 128 cues triggered by cc values 0-127 to enable a group of 128 level setting cues when the encoder level matches the slider level and move to the group numbered Pick
            repeat with x from 0 to 127
                make type "network"
                set theselected to last item of (selected as list)
                --set the correct triggerfor the new cue
                set the midi command of theselected to control_change
                set the midi byte one of theselected to theMIDIcc + (cc - 1)
                set the midi byte two of theselected to x
                set the midi trigger of theselected to enabled
                set the osc message type of theselected to custom
                --set the OSC for the newcue
                set the custom message of theselected to "/cue/SetS" & (theMIDIcc + (cc - 1)) & "/armed 1"
                set the q number of theselected to "Pcc" & theMIDIcc + (cc - 1) & "_" & x
                set q name of theselected to "compare cc" & theMIDIcc + (cc - 1) & " value " & x & " to slider level for takeover"
                move cue id (uniqueID of theselected) of parent of theselected to end of cue "Pick"
            end repeat
        end if
    end repeat
    
    --disarm all level setting groups and cues
    if encoders is false then set the armed of cue "SetS" to false
    
    --disarm this generator script to guard against accidental operation
    set the armed of cue "GEN" to false
    
end tell

How it works

The top of the script has some variables to let you configure the cue list that will be automatically generated.

For the purposes of this example, the variables are set to build a cue list that controls the selected audio cue using a Behringer X-Touch Mini.

Audio cues in the demo workspace are routed like this:

Routing

There are three ganged pairs of cue outputs followed by four mono cue outputs. We can control all the outputs by setting the levels of outputs 0 (for master), 1, 3, 5, 7, 8, 9, and 10.

Certain workspace settings can’t be adjusted via scripting, so the following need to be set manually. (You can build a template workspace with these included.)

First, in Workspace Settings → Audio, set the volume limits to +10 dB maximum and -60 dB minimum:

Audio settings

Next, in Workspace Settings → MIDI, assign MIDI Patch 8 to the hardware controller, in this case the X-Touch Mini:

MIDI settings

Next, in Workspace Settings → Cue Templates, for both Audio cues and Network cues, set the Triggers tab to use a MIDI trigger on MIDI channel 11:

Trigger settings

Last, still in Cue Templates, set the Script cue template to not run scripts in a separate process:

Script settings

When the script is run, the following actions are automated:

  • The user-adjusted variables are checked. In this example, the only thing to check is to ensure that the number of cue outputs and the number of items in the sliders list match. More in-depth error checking can be added here if required.
  • The step value is calculated using the formula step = (the maximum audio level - the minimum audio level ) / 127. This calculates the level change in dB for each MIDI value change of 1. The 70 dB range of the workspace divided by the 128(-1) possible values of a 7-bit MIDI controller gives a change of .5518 dB per controller increment.
  • The list of sliders is formatted into a string to use in script sources later in the script.
  • A “preflight checklist” is compiled, which takes data from the variables set by the user. This is presented to the user in the form of a dialog box so that they can review before committing.

For the variables in the example script the checklist looks like this:

Preflight

When OK is pressed the cue list is generated as seen below. For the purpose of illustrating this example:

  • Controllers CC 3 through CC 7 are omitted
  • MIDI values 2 through 126 are omitted
  • All of the options that the generator is capable of producing are shown, even though not all of these options would really be needed for the example scenario. The cues in orange and purple would not be generated by the user input variables for our example workspace, but they’re included here to show the range of the script.

Generated list

Memo cues are created which store values in their notes fields. The three Memo cues store a summary of the user-set variables, the minimum audio level and the step value. These are used in calculations throughout the script.

A MIDI cue is created, numbered MOP, which will send MIDI messages to the hardware controller when directed to by other parts of the script.

If a controller with fixed-travel knobs were being used, a Script cue would be created, numbered TAKE, to get all the current cue output levels of the selected cue, and arm only the appropriate cues in a Group numbered Pick, as we saw in example 1. These are the purple cues in the screen shot. The script looks like this:

set thelistofsliders to {0, 1, 3}
tell application id "com.figure53.QLab.4" to tell front workspace
    try
        set theselected to last item of (selected as list)
        set the armed of cue "Pick" to false
        set the armed of cue "SetS" to false
        repeat with aSlider from 1 to 3
            set thelevel to getLevel theselected row 0 column (item aSlider of thelistofsliders)
            set thelevel to round ((thelevel - (notes of cue "MIN")) / (notes of cue "STEP"))
            set thecue to "Pcc" & (aSlider as string) & "_" & (thelevel as string)
            delay 0.1
            set the armed of cue thecue to true
        end repeat
    end try
end tell    

A Group cue is created, numbered SetS, which contains a Group cue containing 128 Network cues for each knob on the controller. These Network cues will set the level of cue outputs in the selected cue using OSC.

If a controller with fixed-travel knobs were being used, a Group cue would be created, numbered Pick, which would contain a Group of 128 Network cues for each knob on the controller. This would allow cues in Group SetS to be armed when the matching encoder levels are received. These are the purple cues in the screen shot.

A script cue is created numbered AAO which will send all the audio levels to the corresponding knob on the controller using this script:

set thesliderslist to {0, 1, 3, 5, 7, 8, 9, 10}
set numberofsliders to 8
set theCC to 1
tell application id "com.figure53.QLab.4" to tell front workspace
    try
        set theselected to last item of (selected as list)
        repeat with aSlider from 1 to numberofsliders
            set thelevel to getLevel theselected row 0 column (item aSlider of thesliderslist)
            set thelevel to round ((thelevel - (notes of cue "MIN")) / (notes of cue "STEP"))
            set byte one of cue "MOP" to theCC - 1 + aSlider
            set byte two of cue "MOP" to thelevel
            start cue "MOP"
        end repeat
    end try
end tell

Here’s that script in action:

Each time a cue is selected and any encoder switch is pressed, all the audio levels for the selected cue are translated to their equivalent MIDI values, and those values are sent to the hardware controller to indicate that value, and cause changes in level to begin immediately from that point.

One Start cue is created for each switch on the hardware controller to start Script cue AAO.

There are then some nested repeating loops.

The cc loop creates cues for each knob, e.g. cues SetS1 to SetS8.

The x loop is nested within the cc loop, and creates 128 cues, one for each MIDI value, e.g. cues cc1_0 to cc1_127.

If the “all at once” user variable was set to false at the top of the script, a Script cue is created for each knob being used. The Script cues are numbered e.g. SL1. These scripts use the MIDI MOP cue to set individual levels when individual knob switches are pressed. These are the orange cues in the screen shot. Here’s the script:

set theslider to 10 -- slider to control, 0 is Master
set theCC to 8 -- MIDI cc number for this slider
tell application id "com.figure53.QLab.4" to tell front workspace
    try
        set theselected to last item of (selected as list)
        set thelevel to getLevel theselected row 0 column theslider
        set thelevel to round ((thelevel - (notes of cue "MIN")) / (notes of cue "STEP"))
        set byte one of cue "MOP" to theCC
        set byte two of cue "MOP" to thelevel
        start cue "MOP"
    end try
end tell

The last thing the generator script does is disarm itself so that it cannot not be triggered accidentally.

Example 6: MIDI control of Rate of Selected Audio Cue

Now that we have level controls well in hand, the generator script can be modified to enable MIDI control of other QLab parameters.

The usefulness of this is probably going to be restricted to parameters that can be meaningfully controlled with a range of 128 values. For example, if you are using an HD video projector for video output, a 128-step MIDI controller is a poor option for controlling translation, since the X-axis is 1,920 pixels and the Y-axis is 1080 pixels. Each “click” of the knob would relate to at least 8 pixels, which is likely to be insufficient.

Large ranges could be controlled with 14-bit MIDI controllers, such as the pitch bend control. But it would be unwieldy to do this entirely within QLab, and some external program would be needed to do the translation.

For this example, we’ll use a MIDI knob to adust the playback rate of an Audio cue.

Here it is in action with endless-travel encoders:

The current rate is requested by the controller when the encoder is touched, and rate adjustment can take place immediately starting at that value.

Here it is in action with fixed-travel knobs:

As with the audio level examples, the fixed-travel knob picks up the current playback rate when it passes the equivalent MIDI value.

The combination of MIDI triggering and OSC control again gives a very responsive performance.

The script for generating the cues to control playback rate is easily adapted for other parameters and different types of MIDI controller.

--Script to generate cues to convert MIDI cc messages to OSC to control audio cue sliders
--Delete all cues created previously by this script before running
--Some settings cannot be managed through scripting!
--In settings: 
--Set MIDI Patch 8 to MIDI controller device
--Set script cue template with 'run in separate process' UNCHECKED
--Set Network Cue template Triggers tab to MIDI ch of controller
--CUE GENERATOR WILL RUN UP TO 10 TIMES FASTER IF INSPECTOR IS CLOSED

--set parameters for cue list generation here.

set thedescription to "Change Rate of the selected cue" -- description of control you want to generate
set encoders to false --(boolean) true if controller has motorized fader or is rotary encoder, false for standard fixed travel fader or knob.
set theminimum to 0.25 --minimum rate
set themaximum to 3 --maximum audio level as set in settings audio
set thestep to (themaximum - theminimum) / 127 --step value for slider in dB in QLab per increase of 1 in MIDI cc value.
set theMIDIcc to 1 -- MIDI cc of MIDI cc controller for this parameter
set theMIDInote to 43 --MIDI note to read levels of selected cue for takeover or note sent when first MIDI encoder touched (0 for Behringer X touch compatibility out of box)
set theMIDIch to 11 -- Midi Channel to send feedback to encoders (only needed for encoders) (11 for Behringer X touch compatibility out of box)
set theOSCaddress to "/cue/selected/rate" -- string containing OSC address
set appleScriptEquivalent to "rate" --string containing the equivalent property name for that address

---Do not change anything below this line-------------

set allatonce to false

--generate text for preflight check list dialog display
set thechecklist to ""
if encoders is true then
    set thechecklist to thechecklist & "GENERATING CUES FOR ENCODERS" & return & return & "MIDI ch to send to encoders is: " & theMIDIch & return & return
else
    set thechecklist to thechecklist & "GENERATING CUES FOR POTS" & return & return
end if

set thechecklist to thechecklist & thedescription & " from: " & theminimum & " to: " & themaximum & return & return & "Controller: " & theMIDIcc & return

if encoders is false then set thechecklist to thechecklist & "MIDI note to take over levels: " & theMIDInote & return & return



tell application id "com.figure53.QLab.4" to tell front workspace
    
    --check user actually wants to generate a list and show preflight check list
    
    display dialog "QLab will Generate cues to convert MIDI cc messages to OSC" & return & return & "Are you sure you want to do this?" & return & return & "Delete all cues for this controller created previously by this script before running" & return & return & "Some settings cannot be managed through scripting!" & return & return & "In settings:" & return & "Set MIDI Patch 8 to MIDI controller device" & return & return & "Set script cue template with 'run in separate process' UNCHECKED" & return & return & "Set Network Cue template Triggers tab to MIDI ch of controller" & return & return & "CUE GENERATOR WILL RUN UP TO 10 TIMES FASTER IF INSPECTOR IS CLOSED" & return & return & thechecklist with icon "warning"
    
    --make memo cues to store variables
    
    -- create a memo cue to store a summary of the cuelist that is generated, in its notes field)   
    make type "Memo"
    set theselected to last item of (selected as list)
    set the q number of theselected to "SUM"
    set the notes of theselected to thechecklist
    set the q name of theselected to thechecklist
    
    -- make a memo cue to store a variable, minimum level
    make type "Memo"
    set theselected to last item of (selected as list)
    set the q number of theselected to "MIN"
    set the notes of theselected to theminimum
    
    -- make a memo cue to store a variable, step per cc value
    
    make type "Memo"
    set theselected to last item of (selected as list)
    set the q number of theselected to "STEP"
    set the notes of theselected to thestep
    
    --make a MIDI cue to feedback current parameter setting to MIDI controller. MIDI Patch 8 must be set in settings/MIDI to the MIDI controller device in use.
    make type "MIDI" --set up cue to set patch etc.
    set theselected to last item of (selected as list)
    set the patch of theselected to 8
    set the q number of theselected to "MOP"
    set the message type of theselected to voice
    set the command of theselected to control_change
    set the channel of theselected to theMIDIch
    
    
    --Create a Script cue numbered TAKE to prepare level takeover (only used for standard pots, not encoders)
    if encoders is false then
        make type "Script" --arm pickup cues for level takeover
        set theselected to last item of (selected as list)
        set the q number of theselected to "TAKE"
        set the q name of theselected to "Prepare level takeover"
        set the midi trigger of theselected to enabled
        set the midi command of theselected to note_on
        set the midi byte one of theselected to theMIDInote
        set the midi byte two string of theselected to ">0"
        set the script source of theselected to "tell application id \"com.figure53.QLab.4\" to tell front workspace" & return & "try" & return & "set theselected to last item of (selected as list)" & return & "set the armed of cue \"Pick\" to false" & return & "set the armed of cue \"SetS\" to false" & return & "set thevalue to round (((" & appleScriptEquivalent & " of theselected) - (" & notes of cue "MIN" & ")) / (" & notes of cue "STEP" & "))" & return & "set thecue to \"Pcc" & theMIDIcc & "_\" & (thevalue as string)" & return & "delay 0.1" & return & "set the armed of cue thecue to true" & return & "end try" & return & "end tell"
    end if
    
    --make a group cue
    make type "Group"
    set theselected to last item of (selected as list)
    set the mode of theselected to fire_first_go_to_next_cue
    set the q name of theselected to "set slider cues"
    set the q number of theselected to "SetS"
    
    --make a group cue
    if encoders is false then
        make type "Group"
        set theselected to last item of (selected as list)
        set the mode of theselected to fire_first_go_to_next_cue
        set the q name of theselected to "Pickup level Mechanism"
        set the q number of theselected to "Pick"
    end if
    
    
    
    
    --make a group cue
    make type "Group"
    set theselected to last item of (selected as list)
    set the mode of theselected to fire_first_go_to_next_cue
    set the q name of theselected to "Controller " & (theMIDIcc as string)
    set the q number of theselected to "SetS" & theMIDIcc as string
    move cue id (uniqueID of theselected) of parent of theselected to end of cue "SetS"
    
    --make 128 cues triggered by cc values 0-127 to set parameter via OSC
    repeat with x from 0 to 127
        make type "Network"
        set theselected to last item of (selected as list)
        --set the correct triggerfor the new cue
        set the midi command of theselected to control_change
        set the midi byte one of theselected to theMIDIcc
        set the midi byte two of theselected to x
        set the midi trigger of theselected to enabled
        set the osc message type of theselected to custom
        --set the OSC message to be sent by the new cue
        set thelevel to theminimum + x * thestep
        set the custom message of theselected to theOSCaddress & " " & thelevel
        set the q number of theselected to "cc" & theMIDIcc & "_" & x
        move cue id (uniqueID of theselected) of parent of theselected to end of cue ("SetS" & theMIDIcc)
    end repeat
    
    
    -- make a MIDI input script to get current value of QLab parameter and send the equivalent MIDI level to the MIDI controller
    make type "SCRIPT"
    set theselected to last item of (selected as list)
    set the q number of theselected to "P_" & appleScriptEquivalent
    set the q name of theselected to "send " & appleScriptEquivalent & " value to MIDI controller"
    set the midi trigger of theselected to enabled
    set the midi command of theselected to note_on
    set the midi byte one of theselected to theMIDInote
    set the midi byte two string of theselected to ">0"
    set the script source of theselected to "set theCC to " & (theMIDIcc) & " -- MIDI cc number for this slider" & return & "tell application id \"com.figure53.QLab.4\" to tell front workspace" & return & "try" & return & "set theselected to last item of (selected as list)" & return & "set thevalue to " & appleScriptEquivalent & " of theselected" & return & "set byte one of cue \"MOP\" to theCC" & return & "set byte two of cue \"MOP\" to thevalue" & return & " start cue \"MOP\"" & return & "end try" & return & "end tell"
    
    move cue id (uniqueID of theselected) of parent of theselected to end of cue "SetS"
    
    
    if encoders is false then
        
        --make 128 cues triggered by cc values 0-127 to set slider levels via OSC
        repeat with x from 0 to 127
            make type "network"
            set theselected to last item of (selected as list)
            --set the correct trigger for the new cue
            set the midi command of theselected to control_change
            set the midi byte one of theselected to theMIDIcc
            set the midi byte two of theselected to x
            set the midi trigger of theselected to enabled
            set the osc message type of theselected to custom
            --set the OSC for the newcue
            set the custom message of theselected to "/cue/SetS" & (theMIDIcc) & "/armed 1"
            set the q number of theselected to "Pcc" & theMIDIcc & "_" & x
            set q name of theselected to "compare cc" & theMIDIcc & " value " & x & " to slider level for takeover"
            move cue id (uniqueID of theselected) of parent of theselected to end of cue "Pick"
        end repeat
    end if
    
    if encoders is false then set the armed of cue "SetS" to false
    set the armed of cue "GEN" to false
end tell

How it works

The script works almost identically to the script for generating audio level controls in example 5. It is slightly simpler since we are only dealing with one knob controlling one parameter.

The settings in this script are set up for conventional rotary controls.

set thedescription to "Change Rate of the selected cue" -- description of control you want to generate
set encoders to false --(boolean) true if controller has motorized fader or is rotary encoder, false for standard fixed travel fader or knob.
set theminimum to 0.25 --minimum rate
set themaximum to 3 --maximum audio level as set in settings audio
set thestep to (themaximum - theminimum) / 127 --step value for slider in dB in QLab per increase of 1 in MIDI cc value.
set theMIDIcc to 1 -- MIDI cc of MIDI cc controller for this parameter
set theMIDInote to 43 --MIDI note to read levels of selected cue for takeover or note sent when first MIDI encoder touched (0 for Behringer X touch compatibility out of box)
set theMIDIch to 11 -- Midi Channel to send feedback to encoders (only needed for encoders) (11 for Behringer X touch compatibility out of box)
set theOSCaddress to "/cue/selected/rate" -- string containing OSC address
set appleScriptEquivalent to "rate" --string containing the equivalent property name for that address

The minimum and maximum variables are arbitrary values specifying the knob’s range, and there are two more variables which are set according to what is being controlled. These are for the OSC address and its AppleScript equivalent, which is used when calculating the equivalent MIDI value for a current QLab parameter value.

The preflight check list for the above settings looks like this:

Preflight rate

Thereafter, the cues generated are almost identical to those generated by the script in example 5.

Music used in this chapter:

“Porch Blues” Kevin MacLeod

“Wah Game Loop” Kevin MacLeod

(incompetech.com)

Licensed under Creative Commons: By Attribution 4.0 License

Scarborough Fair stems

Arranged and performed by Mic Pool based on original MIDI files by Mick Jones (public domain)