Way Off Grid

Spreadsheets can be enormously helpful for preparing data for use in QLab workspaces, and also for presenting data from QLab workspaces in a clear, at-a-glance form which does not require spelunking through inspector tabs.

This is the third of three chapters on this subject and expands the example in the Off Grid chapter to demonstrate more techniques for using a spreadsheet side by side with QLab. Readers are advised to read the Off Grid chapter before this one, as many of the techniques used in this tutorial are introduced there.

Note: The examples in these tutorials are primarily demonstrations of how QLab can read from and write to spreadsheets. The downloadable examples should be consider proofs of concept, and should not be regarded as show-ready solutions!

The demo in this chapter shows a six-channel cue light system, automated from a spreadsheet cue list that shows all the cue light states in a single view. As all the data manipulation features of a spreadsheet are available, the cue light plot can be edited rapidly.

Previous cookbook chapters haven’t included any practical electronics. However, because this proof of concept project doesn’t make much sense without physical output devices, this chapter shows how to hook up QLab to an Arduino single-board microcontroller and some homemade cue lights.

QLab controls the spreadsheet through a cart, triggering the activation of the cue lights and providing navigation buttons to control the spreadsheet.

Here it is in action

At the top of the screen recording is the CONTROL cart with cues to mimic the state of the cue lights, set cue light states from QLab, update existing spreadsheet rows, and create new cues. The cues in the bottom row are very similar to the cart cues introduced in the Off Grid chapter.

In the spreadsheet, the green marker in the left column indicates the state that will be fired on the next GO, and the blue marker in the MEM column indicates the current state.

The demo begins with the operator jumping to MEM 0 which is the first cue in the spreadsheet.

When a GO is received, the cue light state is updated to reflect the indicators shown in the corresponding row of the spreadsheet. The mimic buttons in the CONTROL cart display the state in QLab, and QLab outputs the required messages to control the cue lights. In this proof-of-concept project, these are simple traffic light LEDs controlled by an Arduino.

The Increment cue is then started automatically, the green and blue indicators increment one cell down, and the cue on that line is read to “preload” the next cue light state so it will activate immediately when the next GO is received.

The GOs, triggered by NUM 0 on the numeric keypad in the demo, step through the spreadsheet cue light rows one at a time.

After this, some new cues are added and the cue light states are set using the QLab mimic buttons. QLab updates the current spreadsheet row.

How it Works

Spreadsheet Setup

Microsoft Excel is a mature software package; the first version was published in 1985. It has an extensive AppleScript dictionary and its own macro language, Visual Basic for Applications (VBA). This makes it ideal for this project. However, the normal operation of the program, its AppleScript dictionary, and its VBA implementation haven’t been kept consistent with each other as the program has evolved over the years, meaning that care and caution are necessary to ensure they play nicely together.

It’s important to note that any alterations to a spreadsheet workbook by AppleScript or VBA do not appear in the undo stack, and in fact making changes via scripting will clear the existing undo stack. This means it is quite easy to erase rows of mute data quickly without the option of undoing that action!

Implementing undo stacks is way beyond the scope of this tutorial so, as a safety feature, this example includes a method to optionally autosave a copy of the spreadsheet every minute, limiting the amount of work that can be lost.

This will result in potentially hundreds of files which will have to be carefully managed by manually deleting those no longer required as necessary. Autosaving can be disabled when the programming is finished (and a backup copy is saved and readily available to revert to.)

Cue lights can either be set in the CONTROL cart in QLab or manually edited by right-clicking in the cells in columns D through I. Each click cycles through the possible settings: “S” on a red background for STANDBY (warn), “G” on a green background for GO, or blank for off.

Included in the downloadable example is an Excel workbook named QLights6 which is set up as follows:

  • In the header row: all cells text alignment center;
  • Top row frozen (from the View menu on the Ribbon.)
  • All cells bordered.
  • Headings switched off (from the View menu on the Ribbon.) This is to avoid double-clicking on an entire mute column, which would toggle all the mutes in that column but also extend the active cells in the sheet to a very large row number, which would slow down the operation of the spreadsheet considerably.

Excel setup

The file extension of the blank workbook is .xlsm; the “m” in that extension denotes a workbook that contains macros. The macros can be accessed and edited from the Visual Basic Editor, which is accessed from the Macros sub-menu in the Tools menu in the menu bar.

Visual Basic for Applications (VBA) is a programming language integrated into Microsoft Office applications, including Excel. It allows users to automate tasks, enhance functionality, and create custom solutions within spreadsheets. VBA is used to write macros, which are sequences of instructions that perform repetitive tasks, manipulate data, interact with Excel objects (like worksheets and cells), and extend the application’s capabilities beyond its standard features. It is accessible via the Visual Basic Editor (VBE) and is particularly useful for users looking to streamline workflows and build complex data-driven tools.

Visual Basic

You can see from the screenshot that there are three sets of code. One for the sheet, another for the workbook, and a third that is a code module. The workbook and module code are used for the autosave features, and the sheet code is used for the right-click toggling of the cue light cells and the double-click toggling of the AUTOSAVE cell (A1).

Here’s the sheet code:

Private Sub Worksheet_BeforeRightClick(ByVal Target As Range, Cancel As Boolean)
    Dim AllowedRange As Range
    Dim cell As Range
    Dim ValidCells As Range


    ' Define the allowed range where changes are permitted
    Set AllowedRange = Me.Range("D2:I9999") ' Adjust this range as needed

    ' Limit the Target range to cells that are within the AllowedRange
    Set ValidCells = Intersect(Target, AllowedRange)

    ' Only proceed if there are valid cells within the allowed range
    If Not ValidCells Is Nothing Then
     ' Cancel the default right-click menu immediately
    Cancel = True
        ' Loop through each valid cell inside the AllowedRange
        For Each cell In ValidCells
            ' Toggle the cell value between  states
              Select Case cell.Interior.ColorIndex
               Case xlNone: cell.Interior.ColorIndex = 3
                  cell.Value = "S"
                  Case 3: cell.Interior.ColorIndex = 4
                  cell.Value = "G"
               Case Else: cell.Interior.ColorIndex = xlNone
                  cell.Value = ""
             End Select

        Next cell
        Else
         ' Enable the default right-click menu immediately
    Cancel = False
    End If
End Sub

This VBA subroutine toggles the background color and text in a right-clicked cell between an “S” on a red background, a “G” on a green background, and a blank cell. It tests to see whether the cell being right clicked is a mute cell i.e. a cell in AllowedRange. If it is, it suppresses the normal right click action (displaying a contextual menu.) It then tests the contents of the cell and cycles through the three options accordingly.

If the cell is not a cue light cell then the normal right click behavior is allowed.

Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
' Define the allowed range where changes are permitted
    Set AllowedRange = Me.Range("J1:J1") ' Adjust this range as needed

    ' Limit the Target range to cells that are within the AllowedRange
    Set ValidCells = Intersect(Target, AllowedRange)

    ' Only proceed if there are valid cells within the allowed range
    If Not ValidCells Is Nothing Then
     ' Cancel the default right-click menu immediately
    Cancel = True
        ' Loop through each valid cell inside the AllowedRange
        For Each cell In ValidCells
            ' Toggle the cell value between "Clicked" and an empty string
             Select Case cell.Value
                Case "OFF":  cell.Value = "AUTOSAVE"
                Case Else: cell.Value = "OFF"
             End Select
        Next cell
        Else
         ' Enable the default right-click menu immediately
    Cancel = False
    End If
End Sub

This VBA subroutine toggles the text in cell J1 between “AUTOSAVE” and “OFF” when the cell is double-clicked. It tests to see whether the cell being double-clicked is cell J1. If it is, it suppresses the normal double-click action. It then tests if the cell value is “AUTOSAVE”, and if it is, toggles it to “OFF”. If it isn’t, toggles it to “AUTOSAVE”.

If the double-click isn’t in cell J1 then double-clicking reverts to the normal behavior and displays the contextual menu.

Unless you are doing regular complex work with spreadsheets, learning VBA is a lot of effort for very limited usefulness, and it’s certainly beyond the scope of this tutorial to explore in any depth. However, because VBA has so much published material to support it, ChatGPT and other so-called AI systems can answer most questions about using it. A combination of the commenting within the script and ChatGPT should enable you to find out as much (or as little) as you want to know. It is important to remember, though, that ChatGPT nor any other so-called “artificial intelligence” LLM-based tool can be relied upon to be factually correct. They present completely fictional answers as confidently as real ones, so you need to check the answers you receive from them.

The only part of the code that you may need to edit is the AllowedRange for the right-click action. e.g. If you modified the spreadsheet to deal with twelve cue lights (by duplicating columns D to I), you would change the code to:

 Set AllowedRange = Me.Range("D2:09999") ' Adjust this range as needed

and move the Autosave cell to cell P1 and adjust the code in the other scripts to match.

Worksheet Code:

Private Sub Workbook_Open()

Dim backupPath As String
Dim originalPath As String

  'Check that a backup folder exists for this file and save a copy of thefile (regardless of the setting of the AUTOSAVE)
    ' Get the path of the original workbook
    originalPath = ThisWorkbook.Path

' Create the backup path at the same level
    backupPath = originalPath & "/" & ThisWorkbook.Name & " Backups" & "/"
     ' If folder does not exist, create it
     If Dir(backupPath, vbDirectory) = "" Then
        MkDir backupPath
    End If
    
    'Make the file name without the extension
    SaveCopyAsFileName = "Copy of " & ThisWorkbook.Name & " " & Format(Now, "dd-mmm-yy h-mm-ss")

    'Get the file extension of the activeworkbook
    FileExtStr = "." & LCase(Right(ThisWorkbook.Name, Len(ThisWorkbook.Name) - InStrRev(ThisWorkbook.Name, ".", , 1)))

    'Save the copy of the ActiveWorkbook
    ThisWorkbook.SaveCopyAs backupPath & SaveCopyAsFileName & FileExtStr
    
    saveTimer = Now + TimeValue("00:01:00")
    Application.OnTime saveTimer, "Save1"
   
End Sub

This code runs once when the workbook is opened, and makes a backup copy of the workbook regardless of the AUTOSAVE toggle in Cell J1.

If no backup folder for the specific workbook exists, one is created and the backup is stored there.

At the end of the script, the code sets a timer for one minute. When that minute has elapsed, it runs the subroutine “Save1”, which is contained in the code for the module (Module 1):

Global saveTimer As Variant

Sub Save1()
 Dim cell As Range
    Dim targetColorIndex As Integer
Dim backupPath As String
    Dim originalPath As String
    ' Define the target color index (for example, 6 represents Yellow in the Excel default color palette)
    targetValue = "AUTOSAVE"
    
    ' Set the cell you want to check (for example, J1 on the active sheet)
    Set cell = ThisWorkbook.Sheets(ActiveSheet.Name).Range("J1")
    
    ' Check if the background color index of the cell matches the target color index
    If cell.Value = targetValue Then
        'Check if the file have a path
    If ActiveWorkbook.Path = vbNullString Then
        MsgBox "The ActiveWorkbook have no file path, save it first and try again"
        Exit Sub
    End If

    ' Get the path of the original workbook
    originalPath = ThisWorkbook.Path

' Create the backup path at the same level
    backupPath = originalPath & "/" & ThisWorkbook.Name & " Backups" & "/"
     ' If folder does not exist, create it
     If Dir(backupPath, vbDirectory) = "" Then
        MkDir backupPath
    End If
    
    'Make the file name without the extension
    SaveCopyAsFileName = "Copy of " & ThisWorkbook.Name & " " & Format(Now, "dd-mmm-yy h-mm-ss")

    'Get the file extension of the activeworkbook
    FileExtStr = "." & LCase(Right(ThisWorkbook.Name, Len(ThisWorkbook.Name) - InStrRev(ThisWorkbook.Name, ".", , 1)))

    'Save the copy of the ActiveWorkbook
    ThisWorkbook.SaveCopyAs backupPath & SaveCopyAsFileName & FileExtStr

    saveTimer = Now + TimeValue("00:01:00")
    Application.OnTime saveTimer, "Save1"
 
    Else
        saveTimer = Now + TimeValue("00:01:00")
        Application.OnTime saveTimer, "Save1"
    End If
    
End Sub

This checks the value of the background of cell J1, which is toggle-able by double clicking (as driven by the sheet code.) If the text is “AUTOSAVE”, it saves the workspace as a copy in the backups folder and re-starts the one minute timer.

If the text isn’t “AUTOSAVE”, then the timer starts again; if autosave is toggled later, autosaving will resume.

This coded autosave must not be confused with Excel’s built-in autosave, which only works for files stored in cloud services like OneDrive or SharePoint. Because of this, Excels built-in autosave is less suitable for this project, but it could be used if preferred. For locally saved files, Excel provides a feature called AutoRecover. If Excel crashes, AutoRecover can restore the last saved version of the workbook which is stored in a temporary AutoRecover directory. However, access to that directory is only made available if Excel has crashed, so this tool cannot be used to revert a change that was deliberately made.

If you use the autosave feature written in this script, it’s best to close Excel if you are changing to a different spreadsheet. The reason for this is that VBA code in the module continues running even when the workbook is closed. There are ways of stopping it, but these seem to cause Excel to hang if you try to quit without saving. At the moment, restarting Excel when switching away from using this workbook is the least problematic option.

The engine

The cues that are primarily responsible for the operation of the Workspace are contained in the cue list named ENGINE.

Cue light engine

A Group cue numbered CLEAR disarms all the cue light STANDBY, GO, and OFF cues in cue STATE.

A Group cue numbered STATE contains a Timeline Group cue containing a set of cues which set the six cue lights to STANDBY (red), another Group cue containing a set of cues which sets them to GO (green), and a third which sets them to OFF.

Each STANDBY Group cue contains a Network cue with an OSC message which sets the color of a cue in the CONTROL cart to red to mimic the current cue light state, together with the cues to control the corresponding red cue light. In teh example this means sending serial data to the Arduino, but depending on the control arrangements for the cue lights these cues might be OSC, MIDI, or Script cues.

Each GO Group cue contains a network cue with an OSC message which sets the color of a cue in the CONTROL cart to green to mimic the current cue light state, together with the cues to control the corresponding green cue light.

Each OFF Group cue contains a Network cue with an OSC message which sets the color of a cue in the CONTROL cart to none to mimic the current cue light state, together with the cues to switch off the corresponding cue light.

The light state is set by disarming the GO and OFF Groups and arming the STANDBY Groups for cue lights that are set to STANDBY, disarming the STANDBY and OFF Groups and arming the GO Groups for cue lights that are set to GO, and disarming the STANDBY and GO Groups and arming the OFF Groups for cue Lights that are set to OFF.

The heart of the ENGINE is the Script cue numbered LOAD. This reads the spreadsheet and sets the arms and disarms of the cues in the STATE Group to match the cue light cells in the spreadsheet.

--**********************Load the  next QLight State*******************************************************
---Translate Mute Data from an Excel spreadsheet to control Q lights
--RUN WITH SCRIPT CUE  INSPECTOR WINDOW ON BASICS TAB REGARDLESS OF WHETHER THE INSPECTOR IS OPEN OR NOT!!! 10 TIMES FASTER.
--try
set thestate to {}
tell application id "com.figure53.QLab.5" to tell front workspace
  set theCount to count of (cues whose q number begins with "QQ") --number of cuelights
  start cue "CLEAR" --disarm all Mute Unmute Cues
  set theRow to notes of cue "MEM"
end tell
set thetids to AppleScript's text item delimiters
set AppleScript's text item delimiters to ","
tell application "Microsoft Excel" to tell front sheet
  set theNotes to value of cell ("C" & theRow)
  set theMem to value of cell ("B" & theRow)
  repeat with eachLight from 1 to theCount
    set the end of thestate to value of cell (eachLight + 3) of row (theRow as integer)
  end repeat
end tell
--Parse the data and perform actions depending on the value of the cell 
tell application id "com.figure53.QLab.5" to tell front workspace
  set theIndex to 1
  repeat with eachitem in (text items of thestate)
    if text of eachitem is "G" then
      set the armed of cue ("ql" & theIndex & "GO") to true
    else if text of eachitem is "S" then
      set the armed of cue ("ql" & theIndex & "SB") to true
    else
      set the armed of cue ("ql" & theIndex & "OFF") to true
    end if
    set theIndex to theIndex + 1
  end repeat
  
  set AppleScript's text item delimiters to thetids
  --Store Notes
  set the notes of cue "GO" to theNotes
end tell

This script:

  • Gets the number of cue lights in use from the CUE LIGHTS cue list;
  • Disarms all STANDBY GO and OFF Group cues in cue STATE by starting cue CLEAR;
  • Gets the number stored in the notes of cue MEM, which is the row number (not the MEM number) of the spreadsheet row that will be actioned on the next GO trigger.

The script then gets the following information from that row number of the spreadsheet.

  • The notes
  • The MEM number
  • The state of each cue light

It then processes this data in QLab to arm the appropriate Group cues in cue STATE so that the cue lights will immediately be actioned when the next GO is triggered. It also sets the notes of cue “GO” to theNotes.

The CONTROL cart

The CONTROL cart is the main controller for playback of the cue light MEM presets.

Control cart

DECREMENT, triggered in the demo workspace by hotkey NUM 1, is a Script cue with this script:

--get the current row number
tell application id "com.figure53.QLab.5" to tell front workspace to set theRow to notes of cue "MEM" as integer
--decrement the row number
if theRow is greater than 2 then set theRow to theRow - 1
tell application "Microsoft Excel" to tell front sheet
  set theRangeString to ("A" & theRow as text)
  set theRange to (range theRangeString)
  set color index of interior object of column 1 to 0 --clear the green indicator (State to be set when next GO is triggered )
  set color index of interior object of range theRangeString to 4 --set the green indicator  to the new row
  select range theRangeString
  set theValue to value of cell ("B" & theRow as text) --get the MEM number for this MEM
  
end tell
tell application id "com.figure53.QLab.5" to tell front workspace
  set the notes of cue "MEM" to theRow --store the Excel row number for the next GO in the notes of cue MEM
  --display the MEM number of the mute state to be actioned on the next GO on the GO and SET MUTES CART BUTTON Mutes 
  set the q name of cue "GO" to theValue
  set the q name of cue "SET QLights from Spreadsheet" to theValue
  start cue "LOAD"
end tell

INCREMENT, triggered in the demo workspace by hotkey NUM 3, is a Script cue with this script:

--get the current row number
tell application id "com.figure53.QLab.5" to tell front workspace to set theRow to notes of cue "MEM" as integer
--increment the row number
set theRow to theRow + 1
tell application "Microsoft Excel" to tell front sheet
  set theRangeString to ("A" & theRow as text) --& ":A" & theRow as text
  set theRange to (range theRangeString)
  set color index of interior object of column 1 to 0 --clear the green indicator (State to be set when next GO is triggered )
  set color index of interior object of range theRangeString to 4 --set the green indicator  to the new row
  select range theRangeString
  set theValue to value of cell ("B" & theRow as text) --get the MEM number for this MEM
end tell
tell application id "com.figure53.QLab.5" to tell front workspace
  set the notes of cue "MEM" to theRow --store the Excel row number for the next GO in the notes of cue MEM
  --display the MEM number of the mute state to be actioned on the next GO on the GO and SET MUTES CART BUTTON Mutes 
  set the q name of cue "GO" to theValue
  set the q name of cue "SET QLights from Spreadsheet" to theValue
  start cue "LOAD"
end tell

MEM is a Memo cue which displays the MEM number of the current state and stores the row number (not the MEM number) of the state that will be actioned when the next GO is triggered

Mem

GO, triggered in the demo workspace by hotkey NUM 0, is a Script cue with this script:

--*********************DO MUTES***********************
tell application id "com.figure53.QLab.5" to tell front workspace
  set theRow to notes of cue "MEM"
  set theRowRef to "B" & theRow
  start cue "STATE"
end tell
--Highlight the last memory data recalled in the excel spreadsheet 
tell application "Microsoft Excel" to tell front sheet
  set color index of interior object of column 2 to 0
  set color index of interior object of range theRowRef to 8
  select range theRowRef
  set theValue to value of cell ("B" & theRow as text)
  --****************************DO DIALOG******************	--Display Dialog
  tell application id "com.figure53.QLab.5" to tell front workspace
    start cue "INCREMENT"
    set the q name of cue "MEM" to theValue
  end tell
end tell

The order of events in this script prioritizes the immediate activation of the new cue light state by starting cue STATE.

It then clears the background color from all cells in column 2 and sets the background color of the current MEM to indicate the current cue light state.

Finally, the script updates the name of cue MEM to display the current MEM.

JUMP (hotkey J) is a Script cue with this script:

set theMaxCues to 9999 --maximum search range for cues
set theQ to text returned of (display dialog "Spreadsheet Memory to Jump To" default answer 1)
tell application "Microsoft Excel" to tell front sheet
  set searchRange to range ("B1:B" & (theMaxCues as text))
  try
    set foundrange to (find searchRange what theQ look at whole)
  on error
    tell application id "com.figure53.QLab.5" to display alert "MEM does not exist"
    return
  end try
  set theRow to first row index of foundrange
  set theRowRef to "A" & theRow
  set color index of interior object of column 1 to 0
  set color index of interior object of range theRowRef to 4
  select range theRowRef
end tell
tell application id "com.figure53.QLab.5" to tell front workspace
  set the notes of cue "MEM" to theRow
  set the q name of cue "GO" to theQ
  set the q name of cue "SET QLights from Spreadsheet" to theQ
  start cue "LOAD"
end tell

All the other CONTROL cues use the number of the spreadsheet row stored in the notes of cue MEM as the row reference, but JUMP finds a cue from it’s MEM number.

The MEM number is entered by the user in a dialog. The reply to this dialog is searched for using Excel’s AppleScript search function:

set foundrange to (find searchRange what theQ look at whole)

It then sets the green marker to the found cue

SET Q lights from Spreadsheet is a Script cue:

tell application id "com.figure53.QLab.5" to tell front workspace
  set theRow to notes of cue "MEM"
  set theRowRef to "B" & theRow
  start cue "LOAD"
  repeat while (running of cue "LOAD") is true
    delay 0.1
  end repeat
  start cue "STATE"
end tell
--Highlight the last memory data recalled in the excel spreadsheet 
tell application "Microsoft Excel" to tell front sheet
  set color index of interior object of column 2 to 0
  set color index of interior object of range theRowRef to 8
  select range theRowRef
  set theValue to value of cell ("B" & theRow as text)
  
end tell

Its operation is similar to GO, except it does not advance the playhead.

The three CLEAR cues all work in a similar way and set the STANDBYs, GOs, or both to off.

CLEAR SB:

tell application id "com.figure53.QLab.5" to tell front workspace
  set thecues to cues whose (q number begins with "CL") and (q color is "red")
  repeat with eachcue in thecues
    set the q color of eachcue to "none"
  end repeat
end tell

CLEAR GO:

tell application id "com.figure53.QLab.5" to tell front workspace
  set thecues to cues whose (q number begins with "CL") and (q color is "green")
  repeat with eachcue in thecues
    set the q color of eachcue to "none"
  end repeat
end tell

CLEAR BOTH:

tell application id "com.figure53.QLab.5" to tell front workspace
set thecues to cues whose q number begins with "CL" and (q color is "red" or q color is "green")
  repeat with eachcue in thecues
    set the q color of eachcue to "none"
  end repeat
end tell

The SET ALL cues, similarly, set all cue lights to either STANDBY or GO.

ALL SB:

ell application id "com.figure53.QLab.5" to tell front workspace
  set theCount to count of (cues whose q number begins with "QQ") --number of cuelights
  repeat with eachcue from 1 to theCount
    set the q colour of cue ("CL" & eachcue) to "red"
  end repeat
end tell

ALL GO:

tell application id "com.figure53.QLab.5" to tell front workspace
  set theCount to count of (cues whose q number begins with "QQ") --number of cuelights
  repeat with eachcue from 1 to theQLights
    set the q color of cue ("CL" & eachcue) to "green"
  end repeat
end tell

UPDATE is a Script cue which sets the spreadsheet row with the green marker to the cue light state of the mimic buttons in the top row of the control cart.

set thestate to {}
set theLegend to {}
tell application id "com.figure53.QLab.5" to tell front workspace
  set theCount to count of (cues whose q number begins with "QQ") --number of cuelights
  set theMem to notes of cue "MEM" as integer
  repeat with eachcue from 1 to theCount
    set theCue to cue ("CL" & eachcue)
    if q color of theCue is "red" then
      set the end of thestate to 3
      set the end of theLegend to "S"
    else if q color of theCue is "green" then
      set the end of thestate to 4
      set the end of theLegend to "G"
    else
      set the end of thestate to 0
      set the end of theLegend to ""
    end if
  end repeat
  
end tell
tell application "Microsoft Excel" to tell front sheet
  repeat with eachcell from 1 to theCount
    set the color index of interior object of ((cell (eachcell + 3)) of row theMem) to item eachcell of thestate
    set the value of ((cell (eachcell + 3)) of row theMem) to item eachcell of theLegend
    
  end repeat
end tell

tell application id "com.figure53.QLab.5" to tell front workspace to start cue "LOAD"

NEW is a Script cue which sets the spreadsheet row with the green marker to the Q light state of the mimic buttons in the top row of the control cart.

set thestate to {}
set theLegend to {}
tell application id "com.figure53.QLab.5" to tell front workspace
  set theCount to count of (cues whose q number begins with "QQ") --number of cuelights
  set theMem to notes of cue "MEM" as integer
  repeat with eachcue from 1 to theCount
    set theCue to cue ("CL" & eachcue)
    if q color of theCue is "red" then
      set the end of thestate to 3
      set the end of theLegend to "S"
    else if q color of theCue is "green" then
      set the end of thestate to 4
      set the end of theLegend to "G"
    else
      set the end of thestate to 0
      set the end of theLegend to ""
    end if
  end repeat
  
end tell
tell application "Microsoft Excel" to tell front sheet
  if theMem > 1 then
    set theMem to theMem + 1
    insert into range (row theMem)
    set the color index of interior object of row (theMem) to 0
    
    --generate new mem number 
    set x to the value of cell 2 of row (theMem - 1)
    set y to the value of cell 2 of row (theMem + 1)
    if y > x then
      set newMem to x + (y - x) / 2
      set the value of cell 2 of row theMem to newMem
    else
      set the value of cell 2 of row theMem to ((x + 1) as integer)
    end if
    
    
    repeat with eachcell from 1 to theCount
      set the color index of interior object of ((cell (eachcell + 3)) of row theMem) to item eachcell of thestate
      set the value of ((cell (eachcell + 3)) of row theMem) to item eachcell of theLegend
    end repeat
  end if
end tell

CL1 through CL6 are the cue light mimic cues and all have the same script apart from an identifying variable mycue.

tell application id "com.figure53.QLab.5" to tell front workspace
  set mycue to cue "CL1"--change to cue number of this button
  if q color of mycue is "red" then
    set q color of mycue to "green"
  else if q color of mycue is "green" then
    set q color of mycue to "none"
  else
    set q color of mycue to "red"
  end if
end tell

CUE LIGHTS cue list

Cue lights

This cue list lists the cue light names.

Scripts cue list

The QLab workspace has cues that are set to start when the workspace opens and closes. These are set in Workspace Settings → General.

Settings general

Cue LAUNCH is a Script cue with this script:

--open excel Spreadsheet
set theFile to "QLights6.xlsm"
tell application id "com.figure53.QLab.5" to tell front workspace to set thePath to path
tell application "System Events"
  set thePath to path of container of file thePath
  open file (thePath & theFile)
end tell
--Open any helper applications here
tell application "Chataigne" to activate
--other OSC to serial options
--tell application "OSC to Serial Arduino" to activate
delay 3
tell application "Microsoft Excel" to tell front sheet
  set theRow to 2
  set theRowRef to "A" & theRow
  set color index of interior object of column 1 to 0
  set color index of interior object of range theRowRef to 4
  select range theRowRef
end tell
tell application id "com.figure53.QLab.5" to tell front workspace
  set the notes of cue "MEM" to theRow
  set the q name of cue "GO" to " "
  set the q name of cue "MEM" to " "
  set the q name of cue "SET QLights from Spreadsheet" to " "
  start cue "LOAD"
end tell

The script opens all the helper apps, sets the green cursor to the top of the cue list in Excel, and sets the names of some cues in the CONTROL cart to the corresponding cue number.

The helper app in the demo is a custom app named “OSC to Serial Arduino”. This program converts OSC messages to serial data to send to an Arduino.

Cue CLOSE is a Script cue with this script:

--Close any helper appplications here
tell application "Chataigne" to quit
--tell application "OSC to Serial Arduino" to quit
tell application "Microsoft Excel" to quit

which closes any helper apps that are open.

Cue NAMES is a Script cue with this script:

tell application id "com.figure53.QLab.5" to tell front workspace
  set theCount to count of (cues whose q number begins with "QQ") --number of cuelights
  set theNames to {}
  repeat with eachName from 1 to theCount
    set the end of theNames to q list name of cue ("QQ" & eachName)
  end repeat
  repeat with theName from 1 to theCount
    set thisName to item theName of theNames
    set the q name of cue ("QQ" & theName) to thisName
    tell application "Microsoft Excel" to tell front sheet to set the value of cell (theName + 3) of row 1 to thisName
  end repeat
end tell

This script copies the cue names from the CUE LIGHTS cue list to the relevant column headings in the spreadsheet and to the cue names of the top row of buttons (CL1-CL6) in the CONTROL cart.

Output

A project like this wouldn’t be complete without including some real-world output as proof of concept. The output in this demo is to an Arduino Uno R3, which is an inexpensive single-board microcontroller, and commonly available 5V LED traffic lights. In a real system, these would probably be replaced by relays that control traditional cue lights.

Arduino Uno

Arduino is designed as a fast prototyping system and can connect your computer to many real-world input and output devices, including sensors, motors, indicators, etc. An Arduino starter kit, which includes an Arduino, breadboards and wires, and a selection of input and output devices, is a great way to experiment with controlling QLab with data from sensors and switches and for enabling QLab to output to physical devices.

The Arduino Uno has a USB connector for serial communication and 14 pins (numbered 0-13) for digital input/output. Pins 0 and 1 are needed for serial communication, so there are 12 pins available for driving the LEDs. In this example:

  • Pin 2 is connected to cue light 1 red
  • Pin 3 is connected to cue light 1 green
  • Pin 4 is connected to cue light 2 red
  • Pin 5 is connected to cue light 2 green
  • Pin 12 is connected to cue light 6 red
  • Pin 13 is connected to cue light 6 green

All the cue light common terminals are connected to GND.

(For projects requiring more channels, the Arduino Mega has 54 digital I/O pins.)

The Arduino is programmed from the Mac using the Arduino integrated development environment, or IDE.

Arduino IDE

At the top left of the window is a drop down menu to select the connected Arduino device.

Here is the code that needs to be uploaded to the Arduino:

int r1 = 2;
int g1 = 3;
int r2 = 4;
int g2 = 5;
int r3 = 6;
int g3 = 7;
int r4 = 8;
int g4 = 9;
int r5 = 10;
int g5 = 11;
int r6 = 12;
int g6 = 13;

char key = 0;

void setup() {
  Serial.begin(9600);

  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
  pinMode(10, OUTPUT);
  pinMode(11, OUTPUT);
  pinMode(12, OUTPUT);
  pinMode(13, OUTPUT);

  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
  digitalWrite(5, LOW);
  digitalWrite(6, LOW);
  digitalWrite(7, LOW);
  digitalWrite(8, LOW);
  digitalWrite(9, LOW);
  digitalWrite(10, LOW);
  digitalWrite(11, LOW);
  digitalWrite(12, LOW);
  digitalWrite(13, LOW);
}

void loop() {
  if (Serial.available()) {
    char key = Serial.read();
    switch (key) {
      case 'a':
        digitalWrite(r1, HIGH);
        digitalWrite(g1, LOW);
        break;
      case 'b':
        digitalWrite(r1, LOW);
        digitalWrite(g1, HIGH);
        break;
      case 'c':
        digitalWrite(r1, LOW);
        digitalWrite(g1, LOW);
        break;
      case 'd':
        digitalWrite(r2, HIGH);
        digitalWrite(g2, LOW);
        break;
      case 'e':
        digitalWrite(r2, LOW);
        digitalWrite(g2, HIGH);
        break;
      case 'f':
        digitalWrite(r2, LOW);
        digitalWrite(g2, LOW);
        break;
      case 'g':
        digitalWrite(r3, HIGH);
        digitalWrite(g3, LOW);
        break;
      case 'h':
        digitalWrite(r3, LOW);
        digitalWrite(g3, HIGH);
        break;
      case 'i':
        digitalWrite(r3, LOW);
        digitalWrite(g3, LOW);
        break;
      case 'j':
        digitalWrite(r4, HIGH);
        digitalWrite(g4, LOW);
        break;
      case 'k':
        digitalWrite(r4, LOW);
        digitalWrite(g4, HIGH);
        break;
      case 'l':
        digitalWrite(r4, LOW);
        digitalWrite(g4, LOW);
        break;
      case 'm':
        digitalWrite(r5, HIGH);
        digitalWrite(g5, LOW);
        break;
      case 'n':
        digitalWrite(r5, LOW);
        digitalWrite(g5, HIGH);
        break;
      case 'o':
        digitalWrite(r5, LOW);
        digitalWrite(g5, LOW);
        break;
      case 'p':
        digitalWrite(r6, HIGH);
        digitalWrite(g6, LOW);
        break;
      case 'q':
        digitalWrite(r6, LOW);
        digitalWrite(g6, HIGH);
        break;
      case 'r':
        digitalWrite(r6, LOW);
        digitalWrite(g6, LOW);
        break;
    }
  }
}

Arduinos execute code in setup() upon boot. In this program, setup() does the following:

  • Sets a variable for each pin so it can be referred to by its cue light number and color;
  • Starts serial communication at 9600 baud;
  • Configures pins 2 through 13 as digital outputs;
  • Initializes all pins to off (LOW).

Code inside loop() is run in a continuous loop as fast as the Arduino can process it.

Our program reads a new character in from the serial port when one is available, and selects an action based on the character that is received.

If the character is a, it sets r1 (the variable associate with pin 2) to HIGH and g1 (the variable associated with pin 3) to LOW, toggling cue light 1 to red.

The Arduino can be fitted with an ethernet board and can be configured to receive and send OSC, so it could be driven by QLab directly, but in this proof of concept, we are just using the onboard serial communication.

The Arduino IDE has a serial monitor built in which can be opened by clicking the magnifying glass icon in the top right of the window. This enables a quick initial test of the board and code by just typing characters in the message box and seeing if the expected LEDs switch on. For example, if you type “adgknq” into the serial monitor message box and hit return, it will send those characters to the Arduino and the cue lights should display RRRGGG.

Once your test is complete, the Arduino IDE can be closed. It does not need to be open or even installed, since the code resides on the Arduino hardware which runs independently.

In QLab, cue STATE contains cues to drive external devices. In the demo workspace, these are Network cues with simple OSC messages:

/led {a-r}

where the argument is a single lowercase letter representing an LED in the physical output.

Cue light 1 red   = a
Cue light 1 green = b
Cue light 1 off   = c

Cue light 2 red   = d
Cue light 2 green = e
Cue light 2 off   = f

etc.

As we are not communicating with the Arduino directly using OSC, the final part of the project requires the OSC output from QLab to be converted to serial.

This will require another app to be running alongside QLab, and you may recall that in the startup cue LAUNCH, we had a line of AppleScript that opens a program called Chataigne.

Chataigne is a free, open-source software capable of connecting and synchronizing a wide range of show control protocols and technologies.

Although Chataigne is capable of creating some truly mind-boggling setups, at its simplest, it can just be used to quickly translate one protocol to another.

Chataigne

At the top left of the Chataigne window, two modules have been added for OSC and serial communication.

The Module Router in the center pane is set to translate OSC messages to serial.

The Inspector on the right shows the setup for the OSC module. This shows the port number (23232) for OSC input.

The OSC message /led has already been added to the Values section by checking the Auto Add checkbox and sending an OSC message to port 23232 from QLab. The Auto Add box was then unchecked.

If the Serial module is selected, the Inspector will show the settings for that module.

Serial inspector

The Protocol is set to “Direct” and the Baud Rate is set to 9600.

In the Logger pane, there are entries to show that Chataigne has found the Arduino and has opened serial communication with it, and that Chataigne is receiving OSC on port 23232.

Selecting Preferences from the File menu will display project and app preferences in the Inspector.

Preferences

The preferences for this project should be:

  • Launch minimized set to ON. This means Chataigne will open with its interface minimized to the Dock.
  • File to load on Startup set to “OSC to Arduino 23232.noisette” (This is selected using the Browse button.)

Over in QLab, in Workspace Settings → Network → Network Outputs, a network patch is configured to send OSC to IP address 127.0.0.1 (the local computer) on port 23232.

Network settings

In cue STATE, the OSC messages that control the button colors in QLab’s CONTROL cart are routed to the “QLab” network patch. The messages to Chataigne are sent to the “OSC to Serial” network patch.

State

An alternative method for converting OSC message to serial messages is included in the downloadable example; a stand-alone app called “OSC to Serial Arduino.” This can be used by editing the scripts in cue LAUNCH and cue CLOSE to use this app instead of Chataigne. (Only one serial connection can be in use at any one time.) This app has no controls and is configured to use the same port 23232 and send serial to the first connected serial device.

This app was built using Vuo, and the source composition is also included so that you can modify it for your own needs if you wish.

Vuo

OSC is received on port 23232. Messages are dumped if their address is not /led. Valid messages are converted to serial data and sent to the first serial device.

Similar apps could be built quickly in programming environments like Max or PureData or, of course, coded from scratch.

Chataigne is free, open-source software written by Benjamin Kuperberg.

Arduino is a product of Arduino S.r.l.

Excel is a product and registered trademark of Microsoft, Inc.