User tools

Site tools


QLab macros v4

Please attribute this work if you share it, and please report any bugs or issues you encounter

This version: 17/01/19

Thanks due to Figure 53, Gareth Fry, Mic Pool and the QLab forum

All of this code has been hand crafted by me – occasionally in response to requests from others – but some of the concepts arose from collective research on the forum

Unless marked otherwise, all macros have been tested with QLab 4.4.1 in Mac OS X 10.12.6.

Hotkeys

Macros that are useful to keep on a hotkey so they can act on selected cues, or in the current location – or will be needed often.

Levels

Level bump

Bump the Master on the Levels tab of the selected Audio, Mic, Video or Fade Cue(s) -2.5dB (it should be obvious how to adjust that number):

/cue/selected/level/0/0/- 2.5

Make mono

Set crosspoints of selected Audio, Mic or Video Cue(s) for mono routing (crosspoints will be set at 0dB for mono files and -6dB for stereo files):

-- Best run as a separate process so it can be happening in the background, as it is quite slow
 
set qLabMaxAudioChannels to 64
 
tell application id "com.figure53.QLab.4" to tell front workspace
	repeat with eachCue in (selected as list)
		try
			if audio input channels of eachCue is 1 then
				repeat with i from 1 to qLabMaxAudioChannels
					setLevel eachCue row 1 column i db 0
				end repeat
			else if audio input channels of eachCue is 2 then
				repeat with i from 1 to qLabMaxAudioChannels
					setLevel eachCue row 1 column i db -6
					setLevel eachCue row 2 column i db -6
				end repeat
			end if
		end try
	end repeat
end tell

Times

Adjust Pre Wait

Add 1s to the Pre Wait of the selected cue(s):

/cue/selected/preWait/+ 1

Adjust Duration

Add 1s to the Duration of the selected cue(s):

/cue/selected/duration/+ 1

Adjust Post Wait

Add 1s to the Post Wait of the selected cue(s):

/cue/selected/postWait/+ 1

Effective duration

Set Post Wait(s) of selected Group Cue(s) to effective duration (for those times when a countdown display is useful…); the functionality of this script is now built into QLab for “Timeline” Group Cues:

-- The effective duration of a Group Cue is the time it will take for the longest of its children to complete, ie: the longest effective duration of one of its children
-- Each child cue has an effective Pre Wait, which is the cumulative time that must elapse before this child's Pre Wait is triggered
-- In a "Start first child and go to next cue" Group Cue this effective Pre Wait is the sum of all the Pre Waits and Post Waits that have come before
-- Note that auto-follow cues use their Durations as Post Waits (the actual Post Wait property is ignored); this is the value that must be added to the sum above
-- The effective duration of a single cue that does not auto-follow is the sum of its effective Pre Wait and the longer of its Duration or Post Wait
-- The effective duration of a single cue that does auto-follow is the sum of its effective Pre Wait & Duration
 
tell front workspace
	repeat with eachCue in (selected as list)
		try
			set eachMode to mode of eachCue
			set longestChildEffectiveDuration to 0
			if eachMode is fire_first_go_to_next_cue then
				set effectivePreWait to 0
				repeat with eachChild in cues of eachCue
					set eachPre to pre wait of eachChild
					set effectivePreWait to effectivePreWait + eachPre
					set eachContinueMode to continue mode of eachChild
					if eachContinueMode is auto_follow then
						set eachPost to duration of eachChild
						set eachChildEffectiveDuration to effectivePreWait + eachPost
					else
						set eachDuration to duration of eachChild
						set eachPost to post wait of eachChild
						if eachPost > eachDuration then
							set eachChildEffectiveDuration to effectivePreWait + eachPost
						else
							set eachChildEffectiveDuration to effectivePreWait + eachDuration
						end if
					end if
					set effectivePreWait to effectivePreWait + eachPost
					if eachChildEffectiveDuration > longestChildEffectiveDuration then
						set longestChildEffectiveDuration to eachChildEffectiveDuration
					end if
					if eachContinueMode is do_not_continue then
						exit repeat -- No point looking at any further children that aren't part of the sequence
					end if
				end repeat
			end if
			set post wait of eachCue to longestChildEffectiveDuration
		end try
	end repeat
end tell

Fades

Fade in

Create a 5s default fade in of the selected Audio, Mic or Video Cue as the next cue in the cue list (also set the Master on the Levels tab of the selected cue to -INF and copy the Pre Wait time from the selected cue – for use in a “Timeline” Group Cue):

set userDuration to 5
set userMinVolume to -120 -- Set what level you mean by "faded out" (you can adjust this to match the workspace "Min Volume Limit" if necessary)
 
tell front workspace
	try -- This protects against no selection (can't get last item of (selected as list))
		set originalCue to last item of (selected as list)
		if q type of originalCue is in {"Audio", "Mic", "Video"} then
			set originalCueLevel to originalCue getLevel row 0 column 0
			originalCue setLevel row 0 column 0 db userMinVolume
			set originalPreWait to pre wait of originalCue
			make type "Fade"
			set newCue to last item of (selected as list)
			set cue target of newCue to originalCue
			set pre wait of newCue to originalPreWait
			set duration of newCue to userDuration
			newCue setLevel row 0 column 0 db originalCueLevel
			set q name of newCue to "Fade in: " & q list name of originalCue
		end if
	end try
end tell

Fade in with follow-on

Create a 5s default fade in of the selected Audio, Mic or Video Cue as the next cue in the cue list (also set the Master on the Levels tab of the selected cue to -INF and create a follow-on); doesn't fire in a cart:

set userDuration to 5
set userMinVolume to -120 -- Set what level you mean by "faded out" (you can adjust this to match the workspace "Min Volume Limit" if necessary)
 
tell front workspace
	if q type of current cue list is "Cart" then return -- This will stop the script if we're in a cart, as it doesn't make sense to continue!
	try -- This protects against no selection (can't get last item of (selected as list))
		set originalCue to last item of (selected as list)
		if q type of originalCue is in {"Audio", "Mic", "Video"} then
			set originalCueLevel to originalCue getLevel row 0 column 0
			originalCue setLevel row 0 column 0 db userMinVolume
			set continue mode of originalCue to auto_continue
			make type "Fade"
			set newCue to last item of (selected as list)
			set cue target of newCue to originalCue
			set duration of newCue to userDuration
			newCue setLevel row 0 column 0 db originalCueLevel
			set q name of newCue to "Fade in: " & q list name of originalCue
		end if
	end try
end tell

Fade out

Create a 5s default fade out (and stop) of the selected Group, Audio, Mic or Video Cue (or target of selected Fade Cue) as the next cue in the cue list (the script does not stop (targeted) Video Cues):

set userDuration to 5
set userMinVolume to -120 -- Set what level you mean by "faded out" (you can adjust this to match the workspace "Min Volume Limit" if necessary)
 
tell front workspace
	try -- This protects against no selection (can't get last item of (selected as list))
		set originalCue to last item of (selected as list)
		set originalCueType to q type of originalCue
		if originalCueType is in {"Group", "Audio", "Mic", "Video"} then
			make type "Fade"
			set newCue to last item of (selected as list)
			set cue target of newCue to originalCue
			set duration of newCue to userDuration
			newCue setLevel row 0 column 0 db userMinVolume
			if originalCueType is not "Video" then
				set stop target when done of newCue to true
			end if
			set q name of newCue to "Fade out: " & q list name of originalCue
		else if originalCueType is "Fade" then
			set originalCueTarget to cue target of originalCue
			make type "Fade"
			set newCue to last item of (selected as list)
			set cue target of newCue to originalCueTarget
			set duration of newCue to userDuration
			newCue setLevel row 0 column 0 db userMinVolume
			if q type of originalCueTarget is not "Video" then
				set stop target when done of newCue to true
			end if
			set q name of newCue to "Fade out: " & q list name of originalCueTarget
		end if
	end try
end tell

Build

Create a 5s default fade of the selected Group, Audio, Mic or Video Cue (or target of selected Fade Cue) as the next cue in the cue list, building the Master +5dB (the script does nothing if the selected Fade Cue targets a Group Cue):

set userDuration to 5
set userLevel to 5
set userKindString to "Build: "
 
tell front workspace
	try -- This protects against no selection (can't get last item of (selected as list))
		set originalCue to last item of (selected as list)
		set originalCueType to q type of originalCue
		if originalCueType is "Group" then
			make type "Fade"
			set newCue to last item of (selected as list)
			set cue target of newCue to originalCue
			set duration of newCue to userDuration
			newCue setLevel row 0 column 0 db userLevel
			set q name of newCue to userKindString & q list name of originalCue
		else if originalCueType is in {"Audio", "Mic", "Video"} then
			make type "Fade"
			set newCue to last item of (selected as list)
			set cue target of newCue to originalCue
			set duration of newCue to userDuration
			set currentLevel to originalCue getLevel row 0 column 0
			newCue setLevel row 0 column 0 db (currentLevel + userLevel)
			set q name of newCue to userKindString & q list name of originalCue
		else if originalCueType is "Fade" then
			set originalCueTarget to cue target of originalCue
			if q type of originalCueTarget is not "Group" then
				make type "Fade"
				set newCue to last item of (selected as list)
				set cue target of newCue to originalCueTarget
				set duration of newCue to userDuration
				set currentLevel to originalCue getLevel row 0 column 0
				newCue setLevel row 0 column 0 db (currentLevel + userLevel)
				set q name of newCue to userKindString & q list name of originalCueTarget
			end if
		end if
	end try
end tell

Dip

Create a 5s default fade of the selected Group, Audio, Mic or Video Cue (or target of selected Fade Cue) as the next cue in the cue list, dipping the Master -5dB (the script does nothing if the selected Fade Cue targets a Group Cue):

set userDuration to 5
set userLevel to -5
set userKindString to "Dip: "
 
tell front workspace
	try -- This protects against no selection (can't get last item of (selected as list))
		set originalCue to last item of (selected as list)
		set originalCueType to q type of originalCue
		if originalCueType is "Group" then
			make type "Fade"
			set newCue to last item of (selected as list)
			set cue target of newCue to originalCue
			set duration of newCue to userDuration
			newCue setLevel row 0 column 0 db userLevel
			set q name of newCue to userKindString & q list name of originalCue
		else if originalCueType is in {"Audio", "Mic", "Video"} then
			make type "Fade"
			set newCue to last item of (selected as list)
			set cue target of newCue to originalCue
			set duration of newCue to userDuration
			set currentLevel to originalCue getLevel row 0 column 0
			newCue setLevel row 0 column 0 db (currentLevel + userLevel)
			set q name of newCue to userKindString & q list name of originalCue
		else if originalCueType is "Fade" then
			set originalCueTarget to cue target of originalCue
			if q type of originalCueTarget is not "Group" then
				make type "Fade"
				set newCue to last item of (selected as list)
				set cue target of newCue to originalCueTarget
				set duration of newCue to userDuration
				set currentLevel to originalCue getLevel row 0 column 0
				newCue setLevel row 0 column 0 db (currentLevel + userLevel)
				set q name of newCue to userKindString & q list name of originalCueTarget
			end if
		end if
	end try
end tell

Force fades

Force selected Fade Cue(s) to completion (the script will also optionally act on Fade Cues within selected Group Cues):

set userDuration to 1 -- This is the time the fade(s) will be forced to complete in
set userEnterIntoGroups to true -- Set this to false if you don't want the script to act on Fade Cues within selected Group Cues
 
tell front workspace
	set cuesToProcess to (selected as list)
	set processedIDs to {}
	set fadeCues to {}
	set originalPreWaits to {}
	set originalDurations to {}
	set originalContinueModes to {}
	set i to 0
	repeat until i = (count cuesToProcess) -- Extract just the Fade Cues
		set eachCue to item (i + 1) of cuesToProcess
		set eachType to q type of eachCue
		if eachType is "Fade" then
			set eachID to uniqueID of eachCue
			if eachID is not in processedIDs then
				set end of fadeCues to eachCue
				set end of processedIDs to eachID
			end if
		else if userEnterIntoGroups is true and eachType is "Group" then
			set cuesToProcess to cuesToProcess & cues of eachCue
		end if
		set i to i + 1
	end repeat
	repeat with eachCue in fadeCues
		stop eachCue
		set end of originalPreWaits to pre wait of eachCue
		set end of originalDurations to duration of eachCue
		set end of originalContinueModes to continue mode of eachCue
		set pre wait of eachCue to 0
		set duration of eachCue to userDuration
		set continue mode of eachCue to do_not_continue
		start eachCue
	end repeat
	delay userDuration + 0.1 -- Give the cue(s) time to complete before resetting to the original variables
	repeat with i from 1 to count fadeCues
		set eachCue to item i of fadeCues
		stop eachCue -- In case of Post Wait…
		set pre wait of eachCue to item i of originalPreWaits
		set duration of eachCue to item i of originalDurations
		set continue mode of eachCue to item i of originalContinueModes
	end repeat
end tell

Force running fades

Force running Fade Cue(s) to completion:

set userDuration to 1 -- This is the time the fade(s) will be forced to complete in
 
tell front workspace
	set fadeCues to {}
	set originalPreWaits to {}
	set originalDurations to {}
	set originalContinueModes to {}
	repeat with eachCue in (active cues as list) -- Extract just the Fade Cues
		if q type of eachCue is "Fade" then
			set end of fadeCues to eachCue
		end if
	end repeat
	repeat with eachCue in fadeCues
		stop eachCue
		set end of originalPreWaits to pre wait of eachCue
		set end of originalDurations to duration of eachCue
		set end of originalContinueModes to continue mode of eachCue
		set pre wait of eachCue to 0
		set duration of eachCue to userDuration
		set continue mode of eachCue to do_not_continue
		start eachCue
	end repeat
	delay userDuration + 0.1 -- Give the cue(s) time to complete before resetting to the original variables
	repeat with i from 1 to count fadeCues
		set eachCue to item i of fadeCues
		stop eachCue -- In case of Post Wait…
		set pre wait of eachCue to item i of originalPreWaits
		set duration of eachCue to item i of originalDurations
		set continue mode of eachCue to item i of originalContinueModes
	end repeat
end tell

Crash fades

Crash selected Fade Cue(s) to just before completion, starting their target cues (the script will start the target cue(s) for the selected Fade Cue(s), load the Fade Cue(s) to just before completion and start them):

set userDuration to 1 -- This is the time remaining to which to load the Fade Cue(s) before starting them
 
tell front workspace
	repeat with eachCue in (selected as list)
		if q type of eachCue is "Fade" then
			set eachCueTarget to cue target of eachCue
			if running of eachCueTarget is false then
				load eachCueTarget time pre wait of eachCueTarget
				start eachCueTarget
			end if
			stop eachCue -- In case the Fade Cue is a follow-on from its target
			set eachDuration to ((pre wait of eachCue) + (duration of eachCue)) -- Include the Pre Wait for effective duration!
			if eachDuration > userDuration then
				load eachCue time eachDuration - userDuration
			end if
			start eachCue
		end if
	end repeat
end tell

Prepare fades

Prepare selected Fade Cue(s) for preview by starting their target cues:

tell front workspace
	repeat with eachCue in (selected as list)
		if q type of eachCue is "Fade" then
			set eachCueTarget to cue target of eachCue
			if running of eachCueTarget is false then
				preview eachCueTarget
			end if
		end if
	end repeat
end tell

Audio

Turn on infinite loop

Turn on infinite loop in selected Audio or Video Cue(s):

/cue/selected/infiniteLoop 1

Loop last slice

Set last slice of selected Audio or Video Cue(s) to loop:

/cue/selected/lastSliceInfiniteLoop 1

Set rate

Set playback rate of selected Audio, Video or Fade Cue(s) (also acts on MIDI File Cues); this functionality is now built-in too:

set userDefaultToVarispeed to true -- Change this to false if you prefer to default to no pitch shift
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Set playback rate"
 
-- Main routine
 
if userDefaultToVarispeed is true then
	set customButtons to {"Cancel", "No pitch shift", "Varispeed"}
else
	set customButtons to {"Cancel", "Varispeed", "No pitch shift"}
end if
 
set {theRate, theOption} to enterANumberWithCustomButtons("Enter the playback rate:", "", customButtons, 3, 1)
 
tell front workspace
	repeat with eachCue in (selected as list)
		try
			set rate of eachCue to theRate
			if theOption is "Varispeed" then
				set pitch shift of eachCue to enabled
			else
				set pitch shift of eachCue to disabled
			end if
		end try
	end repeat
end tell
 
-- Subroutines
 
(* === INPUT === *)
 
on enterANumberWithCustomButtons(thePrompt, defaultAnswer, theButtons, defaultButton, cancelButton) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set {theQuestion, theButton} to {text returned, button returned} of (display dialog thePrompt with title dialogTitle ¬
				default answer defaultAnswer buttons theButtons default button defaultButton cancel button cancelButton)
			try
				set theAnswer to theQuestion as number
			on error
				set theQuestion to ""
			end try
		end repeat
		return {theAnswer, theButton}
	end tell
end enterANumberWithCustomButtons

Change target

Change selected Audio Cue's target (keeping start/end times); it will also keep the cue's name, if it has been changed from the default:

-- QLab retains slice points within the duration of a new File Target but resets the start & end times (this script maintains start & end times)
 
tell front workspace
	try -- This protects against no selection (can't get last item of (selected as list))
		set selectedCue to last item of (selected as list)
		if q type of selectedCue is "Audio" then
			set currentStart to start time of selectedCue
			set currentEnd to end time of selectedCue
			set currentFileTarget to file target of selectedCue
			if currentFileTarget is not missing value then
				tell application "System Events"
					set whereToLook to (path of container of (currentFileTarget as alias)) as alias
				end tell
				set newFileTarget to choose file of type "public.audio" with prompt "Please select the new File Target:" default location whereToLook
			else
				set newFileTarget to choose file of type "public.audio" with prompt "Please select the new File Target:"
			end if
			set file target of selectedCue to newFileTarget
			set start time of selectedCue to currentStart
			set end time of selectedCue to currentEnd
		end if
	end try
end tell

Convert to wav

Convert selected Audio Cue's target to wav, if it isn't one already (using the “afconvert” command line utility: can be modified to convert to aiff instead; the script keeps start & end times but resets the cue's name to default; bit depth and sample rate are user-defined within the script):

-- QLab retains slice points within the duration of a new File Target but resets the start & end times (this script maintains start & end times)
 
set userFormat to item 1 of {"wav", "aif"} -- Change this to "item 2" to convert to aiff
set userBitDepth to 16
set userBitRate to 44100
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Convert to " & userFormat
 
-- Prepare some variables
 
if userFormat is "wav" then
	set acceptableTypes to {"com.microsoft.waveform-audio"}
	set formatString to "WAVE -d LEI"
else if userFormat is "aif" then
	set acceptableTypes to {"public.aifc-audio", "public.aiff-audio"}
	set formatString to "AIFF -d BEI"
else
	return -- Protection against erroneous user modification
end if
 
-- Convert the cue
 
tell front workspace
	try -- This protects against no selection (can't get last item of (selected as list))
		set selectedCue to last item of (selected as list)
		if q type of selectedCue is "Audio" then
			set currentFileTarget to file target of selectedCue as alias
			tell application "System Events"
				set currentType to type identifier of currentFileTarget
			end tell
			if currentType is not in acceptableTypes then
				set currentStart to start time of selectedCue
				set currentEnd to end time of selectedCue
				tell application "System Events"
					set theContainer to path of container of currentFileTarget
					set theExtension to name extension of currentFileTarget
					if theExtension is "" then
						set theName to name of currentFileTarget
					else
						set theFullName to name of currentFileTarget
						set theName to text 1 through (-1 - ((length of theExtension) + 1)) of theFullName
					end if
					set newFileTarget to theContainer & theName & "." & userFormat
					set fileExists to exists file newFileTarget
				end tell
				if fileExists is true then
					display dialog "The destination file for the conversion already exists. What now?" with title dialogTitle with icon 0 ¬
						buttons {"Cancel", "Replace"} default button "Replace" cancel button "Cancel"
				end if
				display dialog "Preparing to convert…" with title dialogTitle with icon 1 ¬
					buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 3 -- You have 3s to change your mind
				tell me to do shell script "afconvert -f " & formatString & userBitDepth & "@" & userBitRate & " " & ¬
					quoted form of POSIX path of currentFileTarget & " " & quoted form of POSIX path of newFileTarget
				set file target of selectedCue to newFileTarget
				set start time of selectedCue to currentStart
				set end time of selectedCue to currentEnd
				set q name of selectedCue to "" -- Remove this line if you don't want to reset the cue name too
				display dialog "Done." with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 5
			end if
		end if
	end try
end tell

Split at waveform cursor

Copy and paste the selected Audio Cue, setting the original to end at the cursor and the copy to start from there; it requires that UI scripting be allowed for QLab (Accessibility setting under Privacy in System Preferences):

-- Only works properly when run as a separate process!
 
tell application id "com.figure53.QLab.4" to tell front workspace
 
	try -- This protects against no selection (can't get last item of (selected as list))
 
		set selectedCue to last item of (selected as list)
 
		if q type of selectedCue is not "Audio" then error -- Need to escape the whole script
 
		set startTime to start time of selectedCue
		set endTime to end time of selectedCue
 
		set splitTime to startTime + ((percent action elapsed of selectedCue) * (duration of selectedCue)) * (rate of selectedCue)
		-- ###FIXME### As of 4.4.1, "action elapsed" reports differently between clicking in waveform and loading to time when rate ≠ 1
 
		if splitTime - startTime ≤ 1.0E-3 or endTime - splitTime ≤ 1.0E-3 then error -- No point splitting if within 1ms of top/tail
		-- "percent action elapsed" reports 99.999% not 100% when a cue is loaded to its end time, so 1ms is a limit imposed by this rounding error
 
		stop selectedCue -- Just to make it clearer what's going on
 
		-- Now, a slightly bodgy way of making sure that focus is not in the Inspector – so copy/paste works properly – and only one cue selected
 
		moveSelectionUp
		if last item of (selected as list) is not selectedCue then -- Selected cue was at the top of a cue list!
			moveSelectionDown
		end if
 
	on error
 
		return -- Don't go any further…
 
	end try
 
end tell
 
-- Use UI scripting to copy & paste (yuck!)
 
try
	tell application "System Events" to tell (first application process whose bundle identifier is "com.figure53.QLab.4")
		-- set frontmost to true -- ###TESTING### Need this line if testing from Script Editor!
		click menu item "Copy" of menu 1 of menu bar item "Edit" of menu bar 1
		click menu item "Paste" of menu 1 of menu bar item "Edit" of menu bar 1
	end tell
on error
	my displayAlert("UI scripting failed!", "You need to adjust your privacy settings to allow QLab to control your computer…", "critical", {"Cancel", "OK"}, 2, 1)
	tell application "System Preferences"
		activate
		reveal anchor "Privacy_Assistive" of pane id "com.apple.preference.security"
	end tell
	return
end try
 
-- Set the times
 
tell application id "com.figure53.QLab.4" to tell front workspace
	set copiedCue to last item of (selected as list)
	set end time of selectedCue to splitTime
	set start time of copiedCue to splitTime
end tell
 
-- Subroutines
 
(* === ERROR HANDLING === *)
 
on displayAlert(theWarning, theMessage, theIcon, theButtons, defaultButton, cancelButton) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		if cancelButton is "" then
			if theIcon is "critical" then -- Triangle with app icon
				display alert theWarning message theMessage as critical buttons theButtons ¬
					default button item defaultButton of theButtons
			else if theIcon is "informational" then -- App icon
				display alert theWarning message theMessage as informational buttons theButtons ¬
					default button item defaultButton of theButtons
			else if theIcon is "warning" then -- App icon
				display alert theWarning message theMessage as warning buttons theButtons ¬
					default button item defaultButton of theButtons
			end if
		else
			if theIcon is "critical" then
				display alert theWarning message theMessage as critical buttons theButtons ¬
					default button item defaultButton of theButtons cancel button item cancelButton of theButtons
			else if theIcon is "informational" then
				display alert theWarning message theMessage as informational buttons theButtons ¬
					default button item defaultButton of theButtons cancel button item cancelButton of theButtons
			else if theIcon is "warning" then
				display alert theWarning message theMessage as warning buttons theButtons ¬
					default button item defaultButton of theButtons cancel button item cancelButton of theButtons
			end if
		end if
	end tell
end displayAlert

Copy slice markers

Copy slice markers of selected Audio or Video Cue to the Clipboard as tab-delimited text:

set userHeaderRow to "Slice time" & tab & "Play count" -- Set this to false if you don't want a header
 
tell front workspace
	try -- This protects against no selection (can't get last item of (selected as list))
		set selectedCue to last item of (selected as list)
		set recordTable to my recordToDelimitedText(slice markers of selectedCue, tab, return)
		if userHeaderRow is not false then
			set recordTable to userHeaderRow & return & recordTable
		end if
		set the clipboard to recordTable as text
	end try
end tell
 
-- Subroutines
 
(* === TEXT WRANGLING === *)
 
on recordToDelimitedText(theRecord, theColumnDelimiter, theRowDelimiter) -- [Shared subroutine]
	set passedTIDs to AppleScript's text item delimiters
	set delimitedList to {}
	set AppleScript's text item delimiters to theColumnDelimiter
	repeat with eachItem in theRecord
		set end of delimitedList to (eachItem as list) as text
	end repeat
	set AppleScript's text item delimiters to theRowDelimiter
	set delimitedText to delimitedList as text
	set AppleScript's text item delimiters to passedTIDs
	return delimitedText
end recordToDelimitedText

Update all instances

Copy almost all the scriptable settings from the Time & Loops and Device & Levels tabs of the Inspector for the selected cue, and then apply them to every Audio Cue in the workspace that references the same file target; the script does not process the “integrated fade envelope toggle”, as it can't copy the fades themselves…

-- Best run as a separate process so it can be happening in the background
 
set userChannelCount to 64 -- Set how many outputs you are using (no point settings levels or gangs beyond this number)
set userLevels to true -- Set this to false if you don't want to update levels
set userGangs to true -- Set this to false if you don't want to update gangs
set userPatch to true -- Set this to false if you don't want to update the Audio Output Patch
set userTimes to true -- Set this to false if you don't want to update start/end times and loop settings
set userSlices to true -- Set this to false if you don't want to update the slice settings (including last slice play count)
set userRate to true -- Set this to false if you don't want to update rate & pitch shift settings
 
-- Declarations
 
global dialogTitle, startTime
set dialogTitle to "Update all instances"
 
-- Find the last Audio Cue in the selection and check it has a valid target, or give up
 
tell application id "com.figure53.QLab.4"
 
	tell front workspace
 
		try
			set selectedCue to last item of (selected as list)
		on error
			return -- No selection
		end try
 
		if q type of selectedCue is "Audio" then
			set theTarget to file target of selectedCue
		else
			return -- Not an Audio Cue
		end if
 
		if theTarget is missing value then
			display dialog "The selected Audio Cue does not have a valid target." with title dialogTitle with icon 0 ¬
				buttons {"OK"} default button "OK" giving up after 5
			return
		end if
 
		my startTheClock()
 
		-- Copy the general properties (get them all regardless as only getting the ones we need would take longer)
 
		set theID to uniqueID of selectedCue
		set thePatch to patch of selectedCue
		set theStart to start time of selectedCue
		set theEnd to end time of selectedCue
		set thePlayCount to play count of selectedCue
		set theInfiniteLoop to infinite loop of selectedCue
		set theLastSlicePlayCount to last slice play count of selectedCue
		set theLastSliceInfiniteLoop to last slice infinite loop of selectedCue
		set theSliceRecord to slice markers of selectedCue
		set theRate to rate of selectedCue
		set thePitchShift to pitch shift of selectedCue
		set howManyChannels to audio input channels of selectedCue
 
		-- Copy the levels
 
		if userLevels is true then
			set theLevels to {}
			repeat with i from 0 to howManyChannels
				repeat with j from 0 to userChannelCount
					set end of theLevels to getLevel selectedCue row i column j
				end repeat
			end repeat
			set theLevelsRef to a reference to theLevels
		end if
 
		-- Copy the gangs
 
		if userGangs is true then
			set theGangs to {}
			repeat with i from 0 to howManyChannels
				repeat with j from 0 to userChannelCount
					set end of theGangs to getGang selectedCue row i column j
				end repeat
			end repeat
			set theGangsRef to a reference to theGangs
		end if
 
		-- Find the other instances
 
		set allInstances to cues whose broken is false and q type is "Audio" and file target is theTarget and uniqueID is not theID
		set allInstancesRef to a reference to allInstances
		set countInstances to count allInstancesRef
 
		repeat with i from 1 to countInstances
 
			set eachCue to item i of allInstancesRef
 
			if userLevels is true then
				repeat with j from 0 to howManyChannels
					repeat with k from 0 to userChannelCount
						setLevel eachCue row j column k db (item (1 + j * (userChannelCount + 1) + k) of theLevelsRef)
					end repeat
				end repeat
			end if
 
			if userGangs is true then
				repeat with j from 0 to howManyChannels
					repeat with k from 0 to userChannelCount
						set storedValue to item (1 + j * (userChannelCount + 1) + k) of theGangsRef
						if storedValue is missing value then set storedValue to ""
						setGang eachCue row j column k gang storedValue
					end repeat
				end repeat
			end if
 
			if userPatch is true then
				set patch of eachCue to thePatch
			end if
 
			if userTimes is true then
				set start time of eachCue to theStart
				set end time of eachCue to theEnd
				set play count of eachCue to thePlayCount
				set infinite loop of eachCue to theInfiniteLoop
			end if
 
			if userSlices is true then
				set last slice play count of eachCue to theLastSlicePlayCount
				set last slice infinite loop of eachCue to theLastSliceInfiniteLoop
				set slice markers of eachCue to theSliceRecord
			end if
 
			if userRate is true then
				set rate of eachCue to theRate
				set pitch shift of eachCue to thePitchShift
			end if
 
			if i < countInstances then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countInstances, "instances")
			end if
 
		end repeat
 
		my finishedDialog()
 
	end tell
 
end tell
 
-- Subroutines
 
(* === OUTPUT === *)
 
on startTheClock() -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		display dialog "One moment caller…" with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 1
	end tell
	set startTime to current date
end startTheClock
 
on countdownTimer(thisStep, totalSteps, whichCuesString) -- [Shared subroutine]
	set timeTaken to round (current date) - startTime rounding as taught in school
	set timeString to my makeMSS(timeTaken)
	tell application id "com.figure53.QLab.4"
		if frontmost then
			display dialog "Time elapsed: " & timeString & " – " & thisStep & " of " & totalSteps & " " & whichCuesString & ¬
				" done…" with title dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 1
		end if
	end tell
end countdownTimer
 
on finishedDialog() -- [Shared subroutine]
	set timeTaken to round (current date) - startTime rounding as taught in school
	set timeString to my makeNiceT(timeTaken)
	tell application id "com.figure53.QLab.4"
		activate
		display dialog "Done." & return & return & "(That took " & timeString & ".)" with title dialogTitle with icon 1 ¬
			buttons {"OK"} default button "OK" giving up after 60
	end tell
end finishedDialog
 
(* === TIME === *)
 
on makeMSS(howLong) -- [Shared subroutine]
	set howManyMinutes to howLong div 60
	set howManySeconds to howLong mod 60 div 1
	return (howManyMinutes as text) & ":" & my padNumber(howManySeconds, 2)
end makeMSS
 
on makeNiceT(howLong) -- [Shared subroutine]
	if howLong < 1 then
		return "less than a second"
	end if
	set howManyHours to howLong div 3600
	if howManyHours is 0 then
		set hourString to ""
	else if howManyHours is 1 then
		set hourString to "1 hour"
	else
		set hourString to (howManyHours as text) & " hours"
	end if
	set howManyMinutes to howLong mod 3600 div 60
	if howManyMinutes is 0 then
		set minuteString to ""
	else if howManyMinutes is 1 then
		set minuteString to "1 minute"
	else
		set minuteString to (howManyMinutes as text) & " minutes"
	end if
	set howManySeconds to howLong mod 60 div 1
	if howManySeconds is 0 then
		set secondString to ""
	else if howManySeconds is 1 then
		set secondString to "1 second"
	else
		set secondString to (howManySeconds as text) & " seconds"
	end if
	set theAmpersand to ""
	if hourString is not "" then
		if minuteString is not "" and secondString is not "" then
			set theAmpersand to ", "
		else if minuteString is not "" or secondString is not "" then
			set theAmpersand to " and "
		end if
	end if
	set theOtherAmpersand to ""
	if minuteString is not "" and secondString is not "" then
		set theOtherAmpersand to " and "
	end if
	return hourString & theAmpersand & minuteString & theOtherAmpersand & secondString
end makeNiceT
 
(* === TEXT WRANGLING === *)
 
on padNumber(theNumber, minimumDigits) -- [Shared subroutine]
	set paddedNumber to theNumber as text
	repeat while (count paddedNumber) < minimumDigits
		set paddedNumber to "0" & paddedNumber
	end repeat
	return paddedNumber
end padNumber

Add files from iTunes

Add selected files from iTunes and name the cues accordingly (the script will also attempt to remove track numbers from the start of the names); remember to bundle the workspace at some point to get the audio files in the right place:

set userAttemptToRemoveTrackNumbers to true -- Set this to false if you don't mind having track numbers in your cue descriptions
set useriTunesSelectionCountLimit to 100 -- Protect against inadvertently trying to import entire playlists selected in the iTunes sidebar (you can increase this limit)
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Add files from iTunes"
 
-- Check iTunes is running
 
tell application "System Events"
	set iTunesIsOpen to count (processes whose name is "iTunes")
end tell
if iTunesIsOpen is 0 then
	display dialog "iTunes is not running." with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after 5
	return
end if
 
-- Test for an acceptable selection
 
tell application "iTunes"
	set iTunesSelectionCount to count (selection as list)
end tell
if iTunesSelectionCount is 0 then
	display dialog "There is no selection in iTunes." with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after 5
	return
else if iTunesSelectionCount > useriTunesSelectionCountLimit then
	display dialog "The selection in iTunes (" & iTunesSelectionCount & ") is larger than the limit (" & useriTunesSelectionCountLimit & ")." with title dialogTitle ¬
		with icon 0 buttons {"OK"} default button "OK" giving up after 10
	return
end if
 
-- Offer escape hatch
 
if iTunesSelectionCount is 1 then
	set countString to "file"
else
	set countString to "files"
end if
 
display dialog "Adding " & iTunesSelectionCount & " selected " & countString & " from iTunes…" with title dialogTitle with icon 1 ¬
	buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 5 -- You have 5s to change your mind
 
-- Get the files
 
tell application "iTunes"
	set selectedFiles to (location of selection) as list
end tell
 
-- Get the names
 
set selectedNames to {}
tell application "System Events"
	repeat with eachItem in selectedFiles
		set end of selectedNames to name of eachItem
	end repeat
end tell
 
-- Attempt to remove track numbers, as necessary
 
if userAttemptToRemoveTrackNumbers is true then
	set cleanedNames to {}
	set currentTIDs to AppleScript's text item delimiters
	set AppleScript's text item delimiters to space
	repeat with eachName in selectedNames
		if first character of first text item of eachName is in "01234567890" then
			set end of cleanedNames to rest of text items of eachName as text
		else
			set end of cleanedNames to eachName
		end if
	end repeat
	set AppleScript's text item delimiters to currentTIDs
else
	set cleanedNames to selectedNames
end if
 
-- Add the files
 
tell front workspace
	repeat with i from 1 to count selectedFiles
		make type "Audio"
		set newCue to last item of (selected as list)
		set file target of newCue to item i of selectedFiles
		set q name of newCue to item i of cleanedNames
	end repeat
end tell
 
display dialog "Done." with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 5

Making

Add Start Cue(s) based on elapsed time

Add Start Cue below the selected cue with Pre Wait based on its elapsed time:

tell front workspace
 
	try -- This protects against no selection (can't get last item of (selected as list))
 
		set selectedCue to last item of (selected as list)
 
		if q type of selectedCue is not "Audio" or running of selectedCue is false then error -- Need to escape the whole script
 
		set elapsedTime to (percent action elapsed of selectedCue) * (duration of selectedCue)
		-- ###FIXME### As of 4.4.1, "action elapsed" reports differently between clicking in waveform and loading to time when rate ≠ 1
 
		make type "Start"
		set newCue to last item of (selected as list)
		set pre wait of newCue to elapsedTime
 
		set selected to selectedCue
 
	end try
 
end tell

Add Start Cue(s)

Add triggers to the end of the Main Cue List for cues you have selected in another cue list, and then switch back:

set userCueList to "Main Cue List" -- Use this to specify the name of the cue list that receives the Start Cue(s)
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Add Start Cues"
 
-- Main routine
 
tell front workspace
	try -- Check destination cue list exists
		set startCueList to first cue list whose q name is userCueList
	on error
		display dialog "The destination cue list \"" & userCueList & "\" does not exist." with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK"
		return
	end try
	set startingSelection to selected
	set startingCueList to current cue list
	set originalCueList to q name of current cue list
	if originalCueList is not userCueList then
		set selectedCues to (selected as list)
		set current cue list to startCueList
		repeat with eachCue in selectedCues
			make type "Start"
			set newCue to last item of (selected as list)
			set cue target of newCue to eachCue
			set nameString to "Start"
			if originalCueList is not "" then
				set nameString to nameString & " | " & originalCueList
			end if
			set eachNumber to q number of eachCue
			if eachNumber is not "" then
				set nameString to nameString & " | Q " & eachNumber
			end if
			set eachName to q list name of eachCue
			if eachName is not "" then
				set nameString to nameString & " | " & eachName
			end if
			set q name of newCue to nameString
		end repeat
		delay 1 -- Let you see it
		set selected to startingSelection
		set current cue list to startingCueList
	end if
 
end tell

Arm

Create Arm Cue targeting selected cue as previous cue; doesn't fire in a cart:

tell front workspace
	if q type of current cue list is "Cart" then return -- This will stop the script if we're in a cart, as it doesn't make sense to continue!
	try -- This protects against no selection (can't get last item of (selected as list))
		set originalCue to last item of (selected as list)
		make type "Arm"
		set newCue to last item of (selected as list)
		set cue target of newCue to originalCue
		set targetName to q list name of originalCue
		if targetName is "" then
			set targetName to q display name of originalCue
		end if
		set q name of newCue to "Arm: " & targetName
		set originalCueIsIn to parent of originalCue
		if parent of newCue is originalCueIsIn then -- Only reorder the cues if they are in the same group/cue list
			set originalCueID to uniqueID of originalCue
			set newCueID to uniqueID of newCue
			move cue id originalCueID of originalCueIsIn to after cue id newCueID of originalCueIsIn
		end if
	end try
end tell

Disarm

Create Disarm Cue targeting selected cue as next cue; doesn't fire in a cart:

tell front workspace
	if q type of current cue list is "Cart" then return -- This will stop the script if we're in a cart, as it doesn't make sense to continue!
	try -- This protects against no selection (can't get last item of (selected as list))
		set originalCue to last item of (selected as list)
		make type "Disarm"
		set newCue to last item of (selected as list)
		set cue target of newCue to originalCue
		set targetName to q list name of originalCue
		if targetName is "" then
			set targetName to q display name of originalCue
		end if
		set q name of newCue to "Disarm: " & targetName
	end try
end tell

Go ahead make MIDI

Make a series of MIDI Cues which increment the parameter you specify; doesn't fire in a cart:

-- Best run as a separate process so it can be happening in the background
 
-- This script can optionally convert note numbers in MIDI Cues to note names: use the variables below to set the behaviour
 
set userMIDIPatch to false -- Set to false to use QLab default for the cues made, or set to 1-8 for MIDI patch 1-8
set userMinimumTimeBetweenCues to 0.01 -- How long to allow between MIDI Cues when sending clusters (eg: Bank/PC)?
set userHowManyPossible to 1000 -- Limit series with increment of 0 to this many cues!
set userMIDIConversionMode to item 3 of {"Numbers", "Names", "Both"} -- Use this to choose how MIDI note values will be included in cue names
set userNote60Is to item 1 of {"C3", "C4"} -- Use this to set which note name corresponds to note 60
 
-- Explanations
 
set theExplanation to "This script will create a series of MIDI Cues which increment the parameter you specify.
 
For example, you can generate quickly a series of Program Changes for recalling scenes on a mixing desk, " & ¬
	"or a series of Note Ons for triggering a sampler or some other device."
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Go ahead make MIDI"
 
global userMIDIConversionMode, noteNames, octaveConvention
set noteNames to {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}
if userNote60Is is "C3" then
	set octaveConvention to -2
else
	set octaveConvention to -1
end if
 
set kindChoices to {"Note On: fixed note number, incrementing velocity", "Note On: fixed velocity, incrementing note number", ¬
	"Control Change: fixed controller number, incrementing value", "Control Change: fixed value, incrementing controller number", "Program Change", ¬
	"Bank Select (Coarse) / Program Change, ie: CC00 + Program Change"}
set kindCommands to {"Note On", "Note On", "Control Change", "Control Change", "Program Change", "CC00/PC"}
set kindByteOne to {"fixed", "variable", "fixed", "variable", "variable", "variable"}
set kindByteTwo to {"variable", "fixed", "variable", "fixed", "", ""}
set kindQuestionHeader to {"Making Note On cues with incrementing velocity.", "Making Note On cues with incrementing note number.", ¬
	"Making Control Change cues with incrementing value.", "Making Control Change cues with incrementing controller number.", ¬
	"Making Program Change cues.", "Making Bank Select (Coarse) / Program Change cues."}
set kindQuestionOne to {"Enter the fixed note number:", "Enter the note number for the first cue:", "Enter the fixed controller number:", ¬
	"Enter the controller number for the first cue:", "Enter the Program Change number for the first cue:", ¬
	"Enter the combined Bank Select (Coarse) / Program Change number for the first cue (eg: for Bank 2 Program 3 enter 2 * 128 + 3 = 259):"}
set kindQuestionTwo to {"Enter the velocity for the first cue:", "Enter the fixed velocity:", "Enter the value for the first cue:", "Enter the fixed value:", "", ""}
set kindQuestionInc to "Enter the increment:"
set kindQuestionMany to "Enter the number of cues to create (maximum "
set channelChoices to {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
 
-- Find out what we are doing
 
tell application id "com.figure53.QLab.4" to tell front workspace
 
	if q type of current cue list is "Cart" then return -- This will stop the script if we're in a cart, as it doesn't make sense to continue!
 
	set theKind to my pickFromList(kindChoices, theExplanation & return & return & "Choose which kind of series would you like to make:")
 
	repeat with i from 1 to count kindChoices
		if theKind is item i of kindChoices then
			set commandType to item i of kindCommands
			if commandType is "CC00/PC" then
				set byteOneMax to 127 * 128 + 127
			else
				set byteOneMax to 127
			end if
			set byteOne to item i of kindByteOne
			set byteTwo to item i of kindByteTwo
			set questionHeader to item i of kindQuestionHeader
			set questionOne to item i of kindQuestionOne
			set questionTwo to item i of kindQuestionTwo
			set questionInc to kindQuestionInc
			set questionMany to kindQuestionMany
			exit repeat
		end if
	end repeat
 
	set theChannel to my pickFromList(channelChoices, questionHeader & return & return & "Choose the MIDI channel for the cues:")
 
	set byteOneStart to my enterANumberWithRangeWithCustomButton(questionHeader & return & return & questionOne, "", ¬
		0, true, byteOneMax, true, true, {}, "OK")
	set currentByteOne to byteOneStart
	set byteOneIncrement to 0
 
	if byteOne is "variable" then
		set byteOneIncrement to my enterANumberWithRangeWithCustomButton(questionHeader & return & return & questionInc, "", ¬
			-byteOneStart, true, (byteOneMax - byteOneStart), true, true, {}, "OK")
		if byteOneIncrement < 0 then
			set howManyPossible to 1 - (round (byteOneStart / byteOneIncrement) rounding up)
		else if byteOneIncrement > 0 then
			set howManyPossible to 1 + (round ((byteOneMax - byteOneStart) / byteOneIncrement) rounding down)
		else
			set howManyPossible to userHowManyPossible
		end if
	end if
 
	if byteTwo is not "" then
		set byteTwoStart to my enterANumberWithRangeWithCustomButton(questionHeader & return & return & questionTwo, "", ¬
			0, true, 127, true, true, {}, "OK")
	else
		set byteTwoStart to 0
	end if
	set currentByteTwo to byteTwoStart
	set byteTwoIncrement to 0
 
	if byteTwo is "variable" then
		set byteTwoIncrement to my enterANumberWithRangeWithCustomButton(questionHeader & return & return & questionInc, "", ¬
			-byteTwoStart, true, (127 - byteTwoStart), true, true, {}, "OK")
		if byteTwoIncrement < 0 then
			set howManyPossible to 1 - (round (byteTwoStart / byteTwoIncrement) rounding up)
		else if byteTwoIncrement > 0 then
			set howManyPossible to 1 + (round ((127 - byteTwoStart) / byteTwoIncrement) rounding down)
		else
			set howManyPossible to userHowManyPossible
		end if
	end if
 
	set howMany to my enterANumberWithRangeWithCustomButton(questionHeader & return & return & questionMany & howManyPossible & "):", "", ¬
		1, true, howManyPossible, true, true, {}, "OK")
 
	set customNaming to text returned of (display dialog questionHeader & return & return & ¬
		"Enter a base string for naming the cues, or press return to use the default naming option:" with title ¬
		dialogTitle default answer "" buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel")
	if customNaming is not "" then
		if customNaming does not end with " " and (byteOneIncrement is not 0 or byteTwoIncrement is not 0) then
			set customNaming to customNaming & " "
		end if
	end if
 
	-- Do it
 
	display dialog "One moment caller…" with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 1
 
	if commandType is not "CC00/PC" then
 
		set theCommand to commandType
 
		repeat howMany times
 
			make type "MIDI"
			set newCue to last item of (selected as list)
			if userMIDIPatch is not false then
				set patch of newCue to userMIDIPatch
			end if
			set channel of newCue to theChannel
			set byte one of newCue to currentByteOne
			set nameString to "Channel " & theChannel & " | " & theCommand & " | "
			if theCommand is "Note On" then
				set command of newCue to note_on
				set byte two of newCue to currentByteTwo
				set nameString to nameString & my convertMIDINoteValues(currentByteOne, currentByteTwo)
			else if theCommand is "Control Change" then
				set command of newCue to control_change
				set byte two of newCue to currentByteTwo
				set nameString to nameString & currentByteOne & " @ " & currentByteTwo
			else if theCommand is "Program Change" then
				set command of newCue to program_change
				set nameString to nameString & currentByteOne
			end if
			if customNaming is "" then
				set q name of newCue to nameString
			else if byteOneIncrement is not 0 then
				set q name of newCue to customNaming & currentByteOne
			else if byteTwoIncrement is not 0 then
				set q name of newCue to customNaming & currentByteTwo
			else
				set q name of newCue to customNaming
			end if
			set currentByteOne to currentByteOne + byteOneIncrement
			set currentByteTwo to currentByteTwo + byteTwoIncrement
 
		end repeat
 
	else
 
		repeat howMany times
 
			set currentBank to currentByteOne div 128
			set currentProgram to currentByteOne mod 128
 
			make type "Group"
			set groupCue to last item of (selected as list)
			set mode of groupCue to timeline
 
			make type "MIDI"
			set newCue to last item of (selected as list)
			if userMIDIPatch is not false then
				set patch of newCue to userMIDIPatch
			end if
			set channel of newCue to theChannel
			set command of newCue to control_change
			set byte one of newCue to 0
			set byte two of newCue to currentBank
			set q name of newCue to "Channel " & theChannel & " | Control Change | 0 @ " & currentBank
			set newCueID to uniqueID of newCue
			move cue id newCueID of parent of newCue to end of groupCue
 
			make type "MIDI"
			set newCue to last item of (selected as list)
			if userMIDIPatch is not false then
				set patch of newCue to userMIDIPatch
			end if
			set channel of newCue to theChannel
			set command of newCue to program_change
			set byte one of newCue to currentProgram
			set q name of newCue to "Channel " & theChannel & " | Program Change | " & currentProgram
			set newCueID to uniqueID of newCue
			move cue id newCueID of parent of newCue to end of groupCue
			set pre wait of newCue to userMinimumTimeBetweenCues
 
			if customNaming is "" then
				set q name of groupCue to "Channel " & theChannel & ¬
					" | Bank | " & currentBank & " | Program Change | " & currentProgram & " | Combined value: " & currentByteOne
			else if byteOneIncrement is not 0 then
				set q name of groupCue to customNaming & currentByteOne
			else
				set q name of groupCue to customNaming
			end if
 
			set currentByteOne to currentByteOne + byteOneIncrement
 
		end repeat
 
	end if
 
	display dialog "Done." with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 60
 
end tell
 
-- Subroutines
 
(* === INPUT === *)
 
on enterANumberWithRangeWithCustomButton(thePrompt, defaultAnswer, ¬
	lowRange, acceptEqualsLowRange, highRange, acceptEqualsHighRange, integerOnly, customButton, defaultButton) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set {theQuestion, theButton} to {text returned, button returned} of (display dialog thePrompt with title dialogTitle ¬
				default answer defaultAnswer buttons (customButton as list) & {"Cancel", "OK"} default button defaultButton cancel button "Cancel")
			if theButton is customButton then
				set theAnswer to theButton
				exit repeat
			end if
			try
				if integerOnly is true then
					set theAnswer to theQuestion as integer -- Detects non-numeric strings
					if theAnswer as text is not theQuestion then -- Detects non-integer input
						set theQuestion to ""
					end if
				else
					set theAnswer to theQuestion as number -- Detects non-numeric strings
				end if
				if lowRange is not false then
					if acceptEqualsLowRange is true then
						if theAnswer < lowRange then
							set theQuestion to ""
						end if
					else
						if theAnswer ≤ lowRange then
							set theQuestion to ""
						end if
					end if
				end if
				if highRange is not false then
					if acceptEqualsHighRange is true then
						if theAnswer > highRange then
							set theQuestion to ""
						end if
					else
						if theAnswer ≥ highRange then
							set theQuestion to ""
						end if
					end if
				end if
			on error
				set theQuestion to ""
			end try
		end repeat
		return theAnswer
	end tell
end enterANumberWithRangeWithCustomButton
 
on pickFromList(theChoice, thePrompt) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		choose from list theChoice with prompt thePrompt with title dialogTitle default items item 1 of theChoice
		if result is not false then
			return item 1 of result
		else
			error number -128
		end if
	end tell
end pickFromList
 
(* === TEXT WRANGLING === *)
 
on configureNoteNameString(noteNumber) -- [Shared subroutine]
	set theOctave to (noteNumber div 12) + octaveConvention
	set theNote to item (noteNumber mod 12 + 1) of noteNames
	set noteNameString to theNote & theOctave
	return noteNameString
end configureNoteNameString
 
on convertMIDINoteValues(noteNumber, noteVelocity) -- [Shared subroutine]
	if userMIDIConversionMode is "Numbers" then
		return noteNumber & " @ " & noteVelocity
	else if userMIDIConversionMode is "Names" then
		return my configureNoteNameString(noteNumber) & " @ " & noteVelocity
	else if userMIDIConversionMode is "Both" then
		return noteNumber & " @ " & noteVelocity & " | " & my configureNoteNameString(noteNumber)
	end if
end convertMIDINoteValues

Add a crashable wait cue

Add some ugly cues to make it possible to crash ahead before the next domino has toppled; or, looking at it another way, if you don't take the cue it will GO anyway when the timer (5s) runs out…; doesn't fire in a cart:

set userDuration to 5
 
tell front workspace
	if q type of current cue list is "Cart" then return -- This will stop the script if we're in a cart, as it doesn't make sense to continue!
	try -- This protects against no selection (can't get last item of (selected as list))
		set originalCue to last item of (selected as list)
		set continue mode of originalCue to auto_continue
		make type "Start"
		set newStartCue to last item of (selected as list)
		set cue target of newStartCue to current cue list
		set pre wait of newStartCue to userDuration
		set q name of newStartCue to "     stop…"
		make type "Stop"
		set newStopCue to last item of (selected as list)
		set cue target of newStopCue to newStartCue
		set q name of newStopCue to "     …carry on"
		set continue mode of newStopCue to auto_continue
	end try
end tell

Batch adjusting

Toggle arming

Toggle arming of selected cue(s) (the script will arm/disarm the current cue list if no cues are selected):

tell front workspace
	set selectedCues to (selected as list)
	if (count selectedCues) is 0 then -- If no cues are selected arm/disarm the current cue list
		set armed of current cue list to not armed of current cue list
	else
		repeat with eachCue in reverse of selectedCues -- Reversed so as to do a Group Cue's children before it
			set armed of eachCue to not armed of eachCue
		end repeat
	end if
end tell

Batch arm/disarm

Arm, disarm or toggle arming of all cues in the workspace whose name contains the string you enter (not case-sensitive); the string is copied to the Clipboard for you to paste in next time:

set userDefaultSearchString to "REHEARSAL" -- Use this to specify the default search string
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Batch disarm"
 
-- Get the search string
 
set {theText, theButton} to {text returned, button returned} of (display dialog ¬
	"Arm/disarm cues whose name contains (return an empty string to cancel):" with title dialogTitle with icon 1 ¬
	default answer userDefaultSearchString buttons {"Toggle", "Arm", "Disarm"} default button "Disarm")
 
-- Check for cancel
 
if theText is "" then
	error number -128
end if
 
-- Copy the search string to the Clipboard and arm/disarm the cues
 
set the clipboard to theText as text
 
tell front workspace
	set foundCues to cues whose q list name contains theText
	set foundCuesRef to a reference to foundCues
	repeat with eachCue in reverse of foundCuesRef -- Reversed so as to do a Group Cue's children before it
		if theButton is "Arm" then
			set armed of eachCue to true
		else if theButton is "Disarm" then
			set armed of eachCue to false
		else
			set armed of eachCue to not armed of eachCue
		end if
	end repeat
end tell

Clear Cue Number

Clear Cue Number of selected cue(s) that aren't directly in a cue list (the script will also optionally act on children of selected Group Cues):

set userEnterIntoGroups to true -- Set this to false if you don't want the script to act on children of selected Group Cues
 
tell front workspace
	set cuesToProcess to (selected as list)
	set selectedCount to count cuesToProcess
	set currentCueList to current cue list
	set i to 0
	repeat until i = (count cuesToProcess)
		set eachCue to item (i + 1) of cuesToProcess
		if i < selectedCount then -- Don't need to check parentage of cues added to the list as children of selected Group Cues
			if parent of eachCue is not currentCueList then
				set q number of eachCue to ""
			end if
		else
			set q number of eachCue to ""
		end if
		if userEnterIntoGroups is true then
			if q type of eachCue is "Group" then
				set cuesToProcess to cuesToProcess & cues of eachCue
			end if
		end if
		set i to i + 1
	end repeat
end tell

Renumber with a prefix

Renumber selected cue(s) using a sequence list, starting with the number you enter and incrementing the (integer) numerical part at the end; QLab's logic of skipping existing numbers is modelled:

set userDefaultIncrement to 1 -- Use this to specify the default increment between numbers presented in the dialog
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Renumber with a prefix"
 
-- Check the Clipboard for a previous prefix
 
try
	set clipboardContents to the clipboard as text
on error
	set clipboardContents to ""
end try
 
if clipboardContents contains return or clipboardContents contains linefeed then -- Slight protection against spurious Clipboard contents
	set clipboardContents to ""
end if
 
-- Main routine
 
set startingNumber to enterSomeTextWithIcon("Enter the Cue Number for the first selected cue:", clipboardContents, true)
 
set thePrefix to startingNumber
set theSuffix to ""
set nonNumberFound to false
 
repeat with i from (count characters of startingNumber) to 1 by -1
	if character i of startingNumber is not in characters of "0123456789" then
		set nonNumberFound to true
		set thePrefix to (characters 1 thru i of startingNumber) as text
		try -- If the last character is not a number then theSuffix remains as ""
			set theSuffix to (characters (i + 1) thru end of startingNumber) as text
		end try
		exit repeat
	end if
end repeat
 
if nonNumberFound is false then -- Edge case where the text entered is a number with no prefix
	set thePrefix to ""
	set theSuffix to startingNumber
end if
 
set theSuffix to theSuffix as integer
 
set theIncrement to enterANumberWithIcon("Enter the increment:", userDefaultIncrement)
 
tell front workspace
 
	set selectedCues to (selected as list)
 
	-- Clear existing Cue Numbers
 
	repeat with eachCue in selectedCues
		set q number of eachCue to ""
	end repeat
 
	-- Get a list of numbers that can't be used
 
	set allNumbers to q number of cues
	set allNumbersRef to a reference to allNumbers
 
	-- Renumber the cues
 
	repeat with eachCue in selectedCues
		set newNumber to (thePrefix & theSuffix) as text
		repeat until newNumber is not in allNumbersRef -- If the number is in use, then skip it
			set theSuffix to theSuffix + theIncrement
			set newNumber to (thePrefix & theSuffix) as text
		end repeat
		set q number of eachCue to newNumber
		set theSuffix to theSuffix + theIncrement
	end repeat
 
end tell
 
-- Copy the prefix to the Clipboard
 
set the clipboard to startingNumber as text
 
-- Subroutines
 
(* === INPUT === *)
 
on enterANumberWithIcon(thePrompt, defaultAnswer) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set theQuestion to text returned of (display dialog thePrompt with title dialogTitle with icon 1 ¬
				default answer defaultAnswer buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel")
			try
				set theAnswer to theQuestion as number
			on error
				set theQuestion to ""
			end try
		end repeat
		return theAnswer
	end tell
end enterANumberWithIcon
 
on enterSomeTextWithIcon(thePrompt, defaultAnswer, emptyAllowed) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theAnswer to ""
		repeat until theAnswer is not ""
			set theAnswer to text returned of (display dialog thePrompt with title dialogTitle with icon 1 ¬
				default answer defaultAnswer buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel")
			if emptyAllowed is true then exit repeat
		end repeat
		return theAnswer
	end tell
end enterSomeTextWithIcon

Add suffix to renumber

Append the string you enter to the Cue Numbers of the selected cue(s):

-- Declarations
 
global dialogTitle
set dialogTitle to "Add suffix to Cue Number"
 
-- Check the Clipboard for a previous suffix
 
try
	set clipboardContents to the clipboard as text
on error
	set clipboardContents to ""
end try
 
if clipboardContents contains return or clipboardContents contains linefeed then -- Slight protection against spurious Clipboard contents
	set clipboardContents to ""
end if
 
-- Main routine
 
set theSuffix to enterSomeTextWithIcon("Enter the suffix to append:", clipboardContents, true)
 
tell front workspace
	repeat with eachCue in (selected as list)
		set q number of eachCue to (q number of eachCue & theSuffix)
	end repeat
end tell
 
-- Copy the suffix to the Clipboard
 
set the clipboard to theSuffix as text
 
-- Subroutines
 
(* === INPUT === *)
 
on enterSomeTextWithIcon(thePrompt, defaultAnswer, emptyAllowed) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theAnswer to ""
		repeat until theAnswer is not ""
			set theAnswer to text returned of (display dialog thePrompt with title dialogTitle with icon 1 ¬
				default answer defaultAnswer buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel")
			if emptyAllowed is true then exit repeat
		end repeat
		return theAnswer
	end tell
end enterSomeTextWithIcon

Reset names

Reset name of selected cue(s) (the script will reset the name of an Audio, Video or MIDI File Cue to its File Target, name other types of cue based on their type and Cue Target, name MIDI Cues based on their parameters, or reset Network Cues to their default names; other cues that have neither File nor Cue Target are unaffected):

-- This script can optionally convert note numbers in MIDI Cues to note names and mark looped cues: use the variables below to set the behaviour
 
set userMIDIConversionMode to item 1 of {"Numbers", "Names", "Both"} -- Use this to choose how MIDI note values will be included in cue names
set userNote60Is to item 1 of {"C3", "C4"} -- Use this to set which note name corresponds to note 60
 
set userFlagInfiniteLoops to true -- Set whether to append the comments below to the default names of Audio & Video Cues that contain infinite loops
set userInfiniteCueFlag to "!! Infinite loop !!"
set userInfiniteSliceFlag to "!! Infinite slice !!"
 
-- Declarations
 
global userMIDIConversionMode, noteNames, octaveConvention
set noteNames to {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}
if userNote60Is is "C3" then
	set octaveConvention to -2
else
	set octaveConvention to -1
end if
 
set mscCommandFormat to {1, "Lighting (General)", 2, "Moving Lights", 3, "Color Changers", 4, "Strobes", 5, "Lasers", 6, "Chasers", ¬
	16, "Sound (General)", 17, "Music", 18, "CD Players", 19, "EPROM Playback", 20, "Audio Tape Machines", 21, "Intercoms", 22, "Amplifiers", ¬
	23, "Audio Effects Devices", 24, "Equalizers", 32, "Machinery (General)", 33, "Rigging", 34, "Flys", 35, "Lifts", 36, "Turntables", 37, "Trusses", ¬
	38, "Robots", 39, "Animation", 40, "Floats", 41, "Breakaways", 42, "Barges", 48, "Video (General)", 49, "Video Tape Machines", ¬
	50, "Video Cassette Machines", 51, "Video Disc Players", 52, "Video Switchers", 53, "Video Effects", 54, "Video Character Generators", ¬
	55, "Video Still Stores", 56, "Video Monitors", 64, "Projection (General)", 65, "Film Projectors", 66, "Slide Projectors", 67, "Video Projectors", ¬
	68, "Dissolvers", 69, "Shutter Controls", 80, "Process Control (General)", 81, "Hydraulic Oil", 82, "H2O", 83, "CO2", 84, "Compressed Air", ¬
	85, "Natural Gas", 86, "Fog", 87, "Smoke", 88, "Cracked Haze", 96, "Pyrotechnics (General)", 97, "Fireworks", 98, "Explosions", 99, "Flame", ¬
	100, "Smoke Pots", 127, "All Types"}
set mscCommandNumber to {1, "GO", 2, "STOP", 3, "RESUME", 4, "TIMED_GO", 5, "LOAD", 6, "SET", 7, "FIRE", 8, "ALL_OFF", ¬
	9, "RESTORE", 10, "RESET", 11, "GO_OFF", 16, "GO/JAM_CLOCK", 17, "STANDBY_+", 18, "STANDBY_-", 19, "SEQUENCE_+", ¬
	20, "SEQUENCE_-", 21, "START_CLOCK", 22, "STOP_CLOCK", 23, "ZERO_CLOCK", 24, "SET_CLOCK", 25, "MTC_CHASE_ON", ¬
	26, "MTC_CHASE_OFF", 27, "OPEN_CUE_LIST", 28, "CLOSE_CUE_LIST", 29, "OPEN_CUE_PATH", 30, "CLOSE_CUE_PATH"}
set mscCommandTakesQNumber to {1, 2, 3, 4, 5, 11, 16}
set mscCommandTakesQList to {1, 2, 3, 4, 5, 11, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28}
set mscCommandTakesQPath to {1, 2, 3, 4, 5, 11, 16, 29, 30}
set mscCommandTakesMacro to {7}
set mscCommandTakesControl to {6}
set mscCommandTakesTC to {4, 6, 24}
 
-- Main routine
 
tell front workspace
	repeat with eachCue in (selected as list)
		try -- Audio, Video or MIDI File Cues
			set eachFile to file target of eachCue as alias
			tell application "System Events"
				set eachNameList to {name of eachFile}
			end tell
			try
				if infinite loop of eachCue is true then
					set end of eachNameList to userInfiniteCueFlag
				end if
				if last slice infinite loop of eachCue is true then
					set end of eachNameList to userInfiniteSliceFlag
				end if
			end try
			set currentTIDs to AppleScript's text item delimiters
			set AppleScript's text item delimiters to " | "
			set q name of eachCue to eachNameList as text
			set AppleScript's text item delimiters to currentTIDs
		on error
			try -- Fade, Start, Stop, Pause, Load, Reset, Devamp, GoTo, Target, Arm or Disarm Cues
				set eachTarget to q list name of cue target of eachCue
				if eachTarget is "" then
					set eachTarget to q display name of cue target of eachCue
				end if
				set eachType to q type of eachCue
				if eachType is "Fade" then
					if stop target when done of eachCue is true then
						set q name of eachCue to "Fade out: " & eachTarget
					else
						set q name of eachCue to "Fade: " & eachTarget
					end if
				else
					set q name of eachCue to eachType & ": " & eachTarget
				end if
			on error
				try -- MIDI Cues
					set messageType to message type of eachCue
					if messageType is voice then
						set eachChannel to channel of eachCue
						set eachCommand to command of eachCue
						if eachCommand is note_on then
							set byteOne to byte one of eachCue
							set byteTwo to byte two of eachCue
							set q name of eachCue to "Channel " & eachChannel & " | Note On | " & my convertMIDINoteValues(byteOne, byteTwo)
						else if eachCommand is note_off then
							set byteOne to byte one of eachCue
							set byteTwo to byte two of eachCue
							set q name of eachCue to "Channel " & eachChannel & " | Note Off | " & my convertMIDINoteValues(byteOne, byteTwo)
						else if eachCommand is program_change then
							set byteOne to byte one of eachCue
							set q name of eachCue to "Channel " & eachChannel & " | Program Change | " & byteOne
						else if eachCommand is control_change then
							set byteOne to byte one of eachCue
							set byteTwo to byte two of eachCue
							if integrated fade of eachCue is disabled then
								set q name of eachCue to "Channel " & eachChannel & " | Control Change | " & byteOne & " @ " & byteTwo
							else
								set endValue to end value of eachCue
								set q name of eachCue to "Channel " & eachChannel & " | Control Change | " & byteOne & " @ " & byteTwo ¬
									& " … " & endValue
							end if
						else if eachCommand is key_pressure then
							set byteOne to byte one of eachCue
							set byteTwo to byte two of eachCue
							if integrated fade of eachCue is disabled then
								set q name of eachCue to "Channel " & eachChannel & " | Key Pressure | " & my convertMIDINoteValues(byteOne, byteTwo)
							else
								set endValue to end value of eachCue
								set q name of eachCue to ¬
									"Channel " & eachChannel & " | Key Pressure | " & my convertMIDINoteValues(byteOne, byteTwo & " … " & endValue)
							end if
						else if eachCommand is channel_pressure then
							set byteOne to byte one of eachCue
							set q name of eachCue to "Channel " & eachChannel & " | Channel Pressure | " & byteOne
							if integrated fade of eachCue is disabled then
							else
								set endValue to end value of eachCue
								set q name of eachCue to "Channel " & eachChannel & " | Channel Pressure | " & byteOne & " … " & endValue
							end if
						else if eachCommand is pitch_bend then
							set byteCombo to (byte combo of eachCue) - 8192
							if integrated fade of eachCue is disabled then
								set q name of eachCue to "Channel " & eachChannel & " | Pitch Bend | " & byteCombo
							else
								set endValue to (end value of eachCue) - 8192
								set q name of eachCue to "Channel " & eachChannel & " | Pitch Bend | " & byteCombo & " … " & endValue
							end if
						end if
					else if messageType is msc then
						set mscProperties to {}
						set mscEmptyQNumber to false
						set mscEmptyQList to false
						set end of mscProperties to my lookUp(command format of eachCue, mscCommandFormat)
						set end of mscProperties to {"Device ID " & deviceID of eachCue}
						set mscCommand to command number of eachCue
						set end of mscProperties to my lookUp(mscCommand, mscCommandNumber)
						if mscCommand is in mscCommandTakesQNumber then
							set mscQNumber to q_number of eachCue
							if mscQNumber is not "" then
								set end of mscProperties to "Q " & mscQNumber
							else
								set mscEmptyQNumber to true
							end if
						end if
						if mscCommand is in mscCommandTakesQList and mscEmptyQNumber is false then
							set mscQList to q_list of eachCue
							if mscQList is not "" then
								set end of mscProperties to "List " & mscQList
							else
								set mscEmptyQList to true
							end if
						end if
						if mscCommand is in mscCommandTakesQPath and mscEmptyQNumber is false and mscEmptyQList is false then
							set mscQPath to q_path of eachCue
							if mscQPath is not "" then set end of mscProperties to "Path " & mscQPath
						end if
						if mscCommand is in mscCommandTakesMacro then
							set end of mscProperties to macro of eachCue
						end if
						if mscCommand is in mscCommandTakesControl then
							set end of mscProperties to (control number of eachCue & " @ " & control value of eachCue) as text
						end if
						if mscCommand is in mscCommandTakesTC and send time with set of eachCue is true then
							set end of mscProperties to my padNumber(hours of eachCue, 2) & ¬
								":" & my padNumber(minutes of eachCue, 2) & ¬
								":" & my padNumber(seconds of eachCue, 2) & ¬
								":" & my padNumber(frames of eachCue, 2) & ¬
								"/" & my padNumber(subframes of eachCue, 2) -- Not sophisticated enough to use ";" for drop frame rates
						end if
						set currentTIDs to AppleScript's text item delimiters
						set AppleScript's text item delimiters to " | "
						set q name of eachCue to mscProperties as text
						set AppleScript's text item delimiters to currentTIDs
					else
						set q name of eachCue to "MIDI SysEx"
					end if
				on error -- Network Cues
					if q type of eachCue is "Network" then
						set q name of eachCue to "" -- Reset to default
					end if
				end try
			end try
		end try
	end repeat
end tell
 
-- Subroutines
 
(* === DATA WRANGLING === *)
 
on lookUp(lookUpValue, lookUpTable) -- [Shared subroutine]
	repeat with i from 1 to (count lookUpTable) by 2
		if lookUpValue is item i of lookUpTable then
			return item (i + 1) of lookUpTable
		end if
	end repeat
end lookUp
 
(* === TEXT WRANGLING === *)
 
on configureNoteNameString(noteNumber) -- [Shared subroutine]
	set theOctave to (noteNumber div 12) + octaveConvention
	set theNote to item (noteNumber mod 12 + 1) of noteNames
	set noteNameString to theNote & theOctave
	return noteNameString
end configureNoteNameString
 
on convertMIDINoteValues(noteNumber, noteVelocity) -- [Shared subroutine]
	if userMIDIConversionMode is "Numbers" then
		return noteNumber & " @ " & noteVelocity
	else if userMIDIConversionMode is "Names" then
		return my configureNoteNameString(noteNumber) & " @ " & noteVelocity
	else if userMIDIConversionMode is "Both" then
		return noteNumber & " @ " & noteVelocity & " | " & my configureNoteNameString(noteNumber)
	end if
end convertMIDINoteValues
 
on padNumber(theNumber, minimumDigits) -- [Shared subroutine]
	set paddedNumber to theNumber as text
	repeat while (count paddedNumber) < minimumDigits
		set paddedNumber to "0" & paddedNumber
	end repeat
	return paddedNumber
end padNumber

Update filenames

Update filename of File Target for selected cue(s) (the script will change the name of the File Target on disk for an Audio, Video or MIDI File Cue to be the same as the custom name of the cue; QLab's display won't update until you change the selection):

tell front workspace
	repeat with eachCue in (selected as list)
		try
			set eachName to q name of eachCue
			if eachName is "" then -- Skip blank names as otherwise you make invisible files!
				error
			end if
			set eachTarget to file target of eachCue as alias
			tell application "System Events"
				set eachExtension to name extension of eachTarget
				if eachName does not end with eachExtension then
					set name of eachTarget to eachName & "." & eachExtension
				else
					set name of eachTarget to eachName
				end if
			end tell
		end try
	end repeat
end tell

Clear Notes

Clear Notes of selected cue(s):

/cue/selected/notes ""

Move cut cues

Move selected cue(s) to “Cut” cue list:

set userCutList to "Cut" -- Use this to set the name of the cue list to which to move cut cues
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Move cut cues"
 
-- Main routine
 
tell front workspace
 
	try
		set cutList to first cue list whose q name is userCutList
	on error
		display dialog "The destination cue list can not be found…" with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after 5
		return
	end try
	if current cue list is not cutList then
		set selectedCues to (selected as list)
		set selectedCueIDs to {}
		repeat with eachCue in selectedCues
			set end of selectedCueIDs to uniqueID of eachCue
		end repeat
		repeat with eachCueID in selectedCueIDs
			set eachParentID to uniqueID of parent of cue id eachCueID
			if eachParentID is not in selectedCueIDs then -- If the parent cue is being cut too, keep the nested structure
				move cue id eachCueID of cue id eachParentID to end of cutList
			end if
		end repeat
	end if
end tell

Delete

Delete selected cue(s) including their File Targets (target files will be moved to the Trash):

tell front workspace
	repeat with eachCue in (selected as list)
		try
			set eachTarget to file target of eachCue
			if eachTarget as text does not contain ".Trash" then
				tell application "Finder"
					delete eachTarget
				end tell
			end if
		end try
		set eachCueID to uniqueID of eachCue
		delete cue id eachCueID of parent of eachCue
	end repeat
end tell

Batch adjust selected

This script will attempt to batch adjust properties of the currently selected cues according to your instructions… (it won't fire unless you have a cue selected, but once running you can choose to act on all cues):

-- ###FIXME### Impact of housing such a large script in the workspace unknown!
-- ###FIXME### Testing has not been _exhaustive_, for obvious reasons!
-- ###FIXME### Unclear whether script can run in the background while you continue working in QLab (or elsewhere)
-- ###TODO### Add Gangs, MSC command formats?
-- ###TODO### Include "considering case" option for searches
 
-- Best run as a separate process so it can be happening in the background
 
set userEscapeHatchInterval to 50 -- Set the number of cues to process between each progress report / opportunity to cancel
 
-- Explanations
 
set theExplanation to "This script will attempt to batch adjust properties of the currently selected cues according to your instructions.
 
There is some error protection, but it is impossible to make this process completely idiot-proof. You should be warned if something threw an error, " & ¬
	"but it's not possible to track exactly what it was."
 
-- Declarations
 
global dialogTitle, qLabMaxAudioInputs, qLabMaxAudioChannels, userEscapeHatchInterval, startTime, ohDear, abortAbort
set dialogTitle to "Batch adjust selected"
 
set qLabMaxAudioInputs to 24
set qLabMaxAudioChannels to 64
 
global subChoiceTimesParameter, subChoiceMIDIParameter, subChoiceMIDICommand, subChoiceAutoload, subChoiceArmed, subChoiceContinueMode, subChoiceMode
(* These lists are also used for lookup within the chosen handlers *)
 
set processChoices to {"Levels", "File Target", "File Target (keeping times)", "Name", "Number", "Notes", "Times", "MIDI", "MSC Device ID", ¬
	"Auto-load", "Armed", "Continue Mode", "Mode", "Patch", "Camera Patch", "Command Text", "…Finished adjusting", "** Set flag to process ALL CUES! **"}
(* Although batch adjusting of Continue Mode is now built-in, it only operates on selected cues – so keeping it for now as this script can affect ALL cues *)
 
set subChoiceName to {"Set/reset", "Basic search & replace", "Add prefix", "Add suffix", "Make series"}
set subChoiceNumber to {"Add prefix", "Add suffix", "Make series"}
set subChoiceNotes to {"Clear", "Set", "Basic search & replace", "Add prefix", "Add suffix"}
set subChoiceTimesParameter to {"Pre Wait", "Duration", "Post Wait"} -- These values can be customised as they are never used explicitly
set subChoiceTimes to {"Set", "Scale", "Add/subtract amount"}
set subChoiceMIDIParameter to {"Command", "Channel", "Byte One", "Byte Two", "Byte Combo", ¬
	"End Value"} -- These values can be customised as they are never used explicitly
set subChoiceMIDICommand to {"Note On", "Note Off", "Program Change", "Control Change", ¬
	"Key Pressure", "Channel Pressure", "Pitch Bend"} -- These values can be customised as they are never used explicitly
set subChoiceMIDI to {"Set", "Scale", "Add/subtract amount", "Make series"}
set subChoiceAutoload to {"On", "Off"} -- These values can be customised as they are never used explicitly
set subChoiceArmed to {"Armed", "Disarmed"} -- These values can be customised as they are never used explicitly
set subChoiceContinueMode to {"Do not continue", "Auto-continue", "Auto-follow"} -- These values can be customised as they are never used explicitly
set subChoiceMode to {"Timeline - Start all children simultaneously", "Start first child and enter into group", "Start first child and go to next cue", ¬
	"Start random child and go to next cue"} -- These values can be customised as they are never used explicitly
set subChoiceCameraPatch to {1, 2, 3, 4, 5, 6, 7, 8}
set subChoiceCommandText to {"Basic search & replace (all text)", ¬
	"Basic search & replace (Instrument Names only)"} -- These values can be customised as they are never used explicitly
 
-- NB: use "false " rather than "false" in lists presented to pickFromList()
 
-- Preamble
 
set theProcess to ""
set firstTime to true
repeat until theProcess is "…Finished adjusting"
 
	set ohDear to false -- A simple flag to detect problems
	set abortAbort to false -- A flag for aborting!
 
	-- Test for a selection; modify options if only one cue selected
 
	if firstTime is true then -- Only need to do this step once
 
		tell application id "com.figure53.QLab.4"
			set theSelection to (selected of front workspace as list)
		end tell
 
		set theSelectionRef to a reference to theSelection
		set countSelection to count theSelectionRef
		if countSelection is 0 then
			return
		end if
 
		if countSelection is 1 then
			set subChoiceName to items 1 through -2 of subChoiceName -- Remove "Make series"
			set subChoiceNumber to items 1 through -2 of subChoiceNumber -- Remove "Make series"
			set subChoiceMIDI to items 1 through -2 of subChoiceMIDI -- Remove "Make series"
		end if
 
	end if
 
	-- Choose a process
 
	set theProcess to pickFromList(processChoices, theExplanation & return & return & ¬
		"So that you can run more than one process, you'll keep coming back to this screen until you hit any \"Cancel\" button, " & ¬
		"or choose \"…Finished adjusting\"." & return & return & "Choose a property category:")
 
	-- Deal with "process ALL CUES" flag
 
	if firstTime is true then -- Only need to do this step once
 
		set processChoices to items 1 through -2 of processChoices -- Remove "** Set flag to process ALL CUES! **"
 
		if theProcess is "** Set flag to process ALL CUES! **" then
 
			tell application id "com.figure53.QLab.4"
				set theSelection to (cues of front workspace as list)
			end tell
 
			set theExplanation to "WARNING: acting on ALL CUES!" & return & "WARNING: acting on ALL CUES!" & return & "WARNING: acting on ALL CUES!"
 
		end if
 
		set firstTime to false
 
	end if
 
	-- Find out more about what we're doing, and then call a subroutine to do it…
 
	if theProcess is "Levels" then
 
		adjustLevels(theSelectionRef, "selected cues")
 
	else if theProcess is "File Target" then
 
		adjustFileTarget(theSelectionRef, "Set", "selected cues")
 
	else if theProcess is "File Target (keeping times)" then
 
		adjustFileTarget(theSelectionRef, "Change", "selected cues")
 
	else if theProcess is "Name" then
 
		set theChoice to pickFromList(subChoiceName, "Choose how you would like to adjust the names of the selected cues:")
 
		if theChoice is "Set/reset" then
			adjustSetName(theSelectionRef, "selected cues")
		else if theChoice is "Basic search & replace" then
			adjustSearchReplaceName(theSelectionRef, "selected cues")
		else if theChoice is "Add prefix" then
			adjustPrefixName(theSelectionRef, "selected cues")
		else if theChoice is "Add suffix" then
			adjustSuffixName(theSelectionRef, "selected cues")
		else if theChoice is "Make series" then
			adjustSeriesName(theSelectionRef, "selected cues")
		end if
 
	else if theProcess is "Number" then
 
		set theChoice to pickFromList(subChoiceNumber, "Choose how you would like to adjust the Cue Numbers of the selected cues:")
 
		if theChoice is "Add prefix" then
			adjustPrefixNumber(theSelectionRef, "selected cues")
		else if theChoice is "Add suffix" then
			adjustSuffixNumber(theSelectionRef, "selected cues")
		else if theChoice is "Make series" then
			adjustSeriesNumber(theSelectionRef, "selected cues")
		end if
 
	else if theProcess is "Notes" then
 
		set theChoice to pickFromList(subChoiceNotes, "Choose how you would like to adjust the Notes of the selected cues " & ¬
			"(NB: scripting of Notes is plain-text only):")
 
		if theChoice is "Clear" then
			adjustClearNotes(theSelectionRef, "selected cues")
		else if theChoice is "Set" then
			adjustSetNotes(theSelectionRef, "selected cues")
		else if theChoice is "Basic search & replace" then
			adjustSearchReplaceNotes(theSelectionRef, "selected cues")
		else if theChoice is "Add prefix" then
			adjustPrefixNotes(theSelectionRef, "selected cues")
		else if theChoice is "Add suffix" then
			adjustSuffixNotes(theSelectionRef, "selected cues")
		end if
 
	else if theProcess is "Times" then
 
		set parameterChoice to pickFromList(subChoiceTimesParameter, "Choose the time parameter to adjust:")
		set theChoice to pickFromList(subChoiceTimes, "Choose how you would like to adjust the " & parameterChoice & " of the selected cues:")
 
		if theChoice is "Set" then
			adjustSetTime(theSelectionRef, parameterChoice, "selected cues")
		else if theChoice is "Scale" then
			adjustScaleTime(theSelectionRef, parameterChoice, "selected cues")
		else if theChoice is "Add/subtract amount" then
			adjustAddSubractTime(theSelectionRef, parameterChoice, "selected cues")
		end if
 
	else if theProcess is "MIDI" then
 
		-- subChoiceMIDIParameter = {"Command", "Channel", "Byte One", "Byte Two", "Byte Combo", "End Value"}
 
		set parameterChoice to pickFromList(subChoiceMIDIParameter, ¬
			"Choose the MIDI parameter to adjust (\"" & item 5 of subChoiceMIDIParameter & "\" will only affect pitch bend commands):")
		if parameterChoice is item 1 of subChoiceMIDIParameter then
			set theChoice to item 1 of subChoiceMIDIParameter
		else if parameterChoice is item 2 of subChoiceMIDIParameter then
			set theChoice to "Set" -- The other options don't make a lot of sense for channel!
		else
			set theChoice to pickFromList(subChoiceMIDI, "Choose how you would like to adjust the " & parameterChoice & " of the selected cues:")
		end if
 
		if theChoice is item 1 of subChoiceMIDIParameter then
			adjustSetMIDICommand(theSelectionRef, "selected cues")
		else if theChoice is "Set" then
			adjustSetMIDI(theSelectionRef, parameterChoice, "selected cues")
		else if theChoice is "Scale" then
			adjustScaleMIDI(theSelectionRef, parameterChoice, "selected cues")
		else if theChoice is "Add/subtract amount" then
			adjustAddSubractMIDI(theSelectionRef, parameterChoice, "selected cues")
		else if theChoice is "Make series" then
			adjustSeriesMIDI(theSelectionRef, parameterChoice, "selected cues")
		end if
 
	else if theProcess is "MSC Device ID" then
 
		adjustDeviceID(theSelectionRef, "selected cues")
 
	else if theProcess is "Auto-load" then
 
		set parameterChoice to pickFromList(subChoiceAutoload, "Set Auto-load of the selected cues to:")
 
		adjustAutoload(theSelectionRef, parameterChoice, "selected cues")
 
	else if theProcess is "Armed" then
 
		set parameterChoice to pickFromList(subChoiceArmed, "Set Armed of the selected cues to:")
 
		adjustArmed(theSelectionRef, parameterChoice, "selected cues")
 
	else if theProcess is "Continue Mode" then
 
		set parameterChoice to pickFromList(subChoiceContinueMode, "Set the Continue Mode of the selected cues to:")
 
		adjustContinueMode(theSelectionRef, parameterChoice, "selected cues")
 
	else if theProcess is "Mode" then
 
		set parameterChoice to pickFromList(subChoiceMode, "Set the Mode of the selected cues to:")
 
		adjustMode(theSelectionRef, parameterChoice, "selected cues")
 
	else if theProcess is "Patch" then
 
		adjustPatch(theSelectionRef, "selected cues")
 
	else if theProcess is "Camera Patch" then
 
		set parameterChoice to pickFromList(subChoiceCameraPatch, "Set the Camera Patch of the selected cues to:")
 
		adjustCameraPatch(theSelectionRef, parameterChoice, "selected cues")
 
	else if theProcess is "Command Text" then
 
		set theChoice to pickFromList(subChoiceCommandText, "Choose how you would like to adjust the Command Text of the selected cues:")
 
		if theChoice is "Basic search & replace (all text)" then
			adjustSearchReplaceCommandText(theSelectionRef, "selected cues", false)
		else if theChoice is "Basic search & replace (Instrument Names only)" then
			adjustSearchReplaceCommandText(theSelectionRef, "selected cues", true)
		end if
 
	end if
 
end repeat
 
-- Subroutines
 
(* === INPUT === *)
 
on enterAnInteger(thePrompt, defaultAnswer) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set theQuestion to text returned of (display dialog thePrompt with title dialogTitle default answer defaultAnswer buttons {"Cancel", "OK"} ¬
				default button "OK" cancel button "Cancel")
			try
				set theAnswer to theQuestion as integer
			on error
				set theQuestion to ""
			end try
		end repeat
		return theAnswer
	end tell
end enterAnInteger
 
on enterANumber(thePrompt, defaultAnswer) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set theQuestion to text returned of (display dialog thePrompt with title dialogTitle default answer defaultAnswer buttons {"Cancel", "OK"} ¬
				default button "OK" cancel button "Cancel")
			try
				set theAnswer to theQuestion as number
			on error
				set theQuestion to ""
			end try
		end repeat
		return theAnswer
	end tell
end enterANumber
 
on enterANumberWithRangeWithCustomButton(thePrompt, defaultAnswer, ¬
	lowRange, acceptEqualsLowRange, highRange, acceptEqualsHighRange, integerOnly, customButton, defaultButton) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set {theQuestion, theButton} to {text returned, button returned} of (display dialog thePrompt with title dialogTitle ¬
				default answer defaultAnswer buttons (customButton as list) & {"Cancel", "OK"} default button defaultButton cancel button "Cancel")
			if theButton is customButton then
				set theAnswer to theButton
				exit repeat
			end if
			try
				if integerOnly is true then
					set theAnswer to theQuestion as integer -- Detects non-numeric strings
					if theAnswer as text is not theQuestion then -- Detects non-integer input
						set theQuestion to ""
					end if
				else
					set theAnswer to theQuestion as number -- Detects non-numeric strings
				end if
				if lowRange is not false then
					if acceptEqualsLowRange is true then
						if theAnswer < lowRange then
							set theQuestion to ""
						end if
					else
						if theAnswer ≤ lowRange then
							set theQuestion to ""
						end if
					end if
				end if
				if highRange is not false then
					if acceptEqualsHighRange is true then
						if theAnswer > highRange then
							set theQuestion to ""
						end if
					else
						if theAnswer ≥ highRange then
							set theQuestion to ""
						end if
					end if
				end if
			on error
				set theQuestion to ""
			end try
		end repeat
		return theAnswer
	end tell
end enterANumberWithRangeWithCustomButton
 
on enterARatio(thePrompt, defaultAnswer) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set theQuestion to text returned of (display dialog thePrompt with title dialogTitle default answer defaultAnswer buttons {"Cancel", "OK"} ¬
				default button "OK" cancel button "Cancel")
			try
				set theAnswer to theQuestion as number
				if theAnswer ≤ 0 then
					set theQuestion to ""
				end if
			on error
				set theQuestion to ""
			end try
		end repeat
		return theAnswer
	end tell
end enterARatio
 
on enterATimeWithCustomButton(thePrompt, defaultAnswer, customButton) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set {theQuestion, theButton} to {text returned, button returned} of (display dialog thePrompt with title dialogTitle ¬
				default answer defaultAnswer buttons (customButton as list) & {"Cancel", "OK"} default button "OK" cancel button "Cancel")
			if theButton is customButton then
				set theAnswer to theButton
				exit repeat
			end if
			try
				set theAnswer to theQuestion as number
				if theAnswer < 0 then
					set theQuestion to ""
				end if
			on error
				if theQuestion contains ":" then
					set theAnswer to my makeSecondsFromM_S(theQuestion)
					if theAnswer is false then
						set theQuestion to ""
					end if
				else
					set theQuestion to ""
				end if
			end try
		end repeat
		return theAnswer
	end tell
end enterATimeWithCustomButton
 
on enterSomeText(thePrompt, defaultAnswer, emptyAllowed) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theAnswer to ""
		repeat until theAnswer is not ""
			set theAnswer to text returned of (display dialog thePrompt with title dialogTitle default answer defaultAnswer buttons {"Cancel", "OK"} ¬
				default button "OK" cancel button "Cancel")
			if emptyAllowed is true then exit repeat
		end repeat
		return theAnswer
	end tell
end enterSomeText
 
on pickFromList(theChoice, thePrompt) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		choose from list theChoice with prompt thePrompt with title dialogTitle default items item 1 of theChoice
		if result is not false then
			return item 1 of result
		else
			error number -128
		end if
	end tell
end pickFromList
 
(* === OUTPUT === *)
 
on startTheClock() -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		display dialog "One moment caller…" with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 1
	end tell
	set startTime to current date
end startTheClock
 
on countdownTimer(thisStep, totalSteps, whichCuesString) -- [Shared subroutine]
	set timeTaken to round (current date) - startTime rounding as taught in school
	set timeString to my makeMSS(timeTaken)
	tell application id "com.figure53.QLab.4"
		if frontmost then
			display dialog "Time elapsed: " & timeString & " – " & thisStep & " of " & totalSteps & " " & whichCuesString & ¬
				" done…" with title dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 1
		end if
	end tell
end countdownTimer
 
on finishedDialogBespoke()
	set timeTaken to round (current date) - startTime rounding as taught in school
	set timeString to my makeNiceT(timeTaken)
	tell application id "com.figure53.QLab.4"
		activate
		if abortAbort is true then
			display dialog "Process aborted due to errors!" with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after 120
		else
			if ohDear is true then
				set ohDearString to " There were some errors, so you should check the results."
				set ohDearIcon to 0
			else
				set ohDearString to ""
				set ohDearIcon to 1
			end if
			display dialog "Done." & ohDearString & return & return & "(That took " & timeString & ".)" with title dialogTitle with icon ohDearIcon ¬
				buttons {"OK"} default button "OK" giving up after 60
		end if
	end tell
end finishedDialogBespoke
 
(* === PROCESSING === *)
 
on adjustLevels(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		-- Get the levels
 
		set validEntry to false
		set previousTry to ""
		repeat until validEntry is true
 
			set levelsString to my enterSomeText("Enter the levels you wish to adjust as
\"row/column/delta\" or \"row/column/@level\"; you can separate multiple entries with spaces.
 
For example, \"0/0/-10 0/2/@-20\" will take 10dB from the Master level (row 0 column 0) and set the Output 2 level (row 0 column 2) to -20dB.", ¬
				previousTry, false)
			set previousTry to levelsString
 
			-- Convert string to array
 
			set currentTIDs to AppleScript's text item delimiters
			set AppleScript's text item delimiters to space
			set levelsWords to text items of levelsString
			set howManyLevels to count levelsWords
			set AppleScript's text item delimiters to "/"
			set backToText to levelsWords as text
			set levelsArray to text items of backToText
			set countLevelsArray to count levelsArray
			set AppleScript's text item delimiters to currentTIDs
 
			-- Check for validity
 
			if howManyLevels * 3 is countLevelsArray then -- First hurdle
				set validEntry to true
				try
					repeat with i from 1 to countLevelsArray by 3
						set eachRow to (item i of levelsArray) as number
						set eachColumn to (item (i + 1) of levelsArray) as number
						set eachLevel to item (i + 2) of levelsArray
						if eachRow < 0 or eachRow > qLabMaxAudioInputs then -- Check for valid row
							set validEntry to false
							exit repeat
						end if
						if eachColumn < 0 or eachColumn > qLabMaxAudioChannels then -- Check for valid column
							set validEntry to false
							exit repeat
						end if
						if eachLevel does not start with "@" then -- Check for valid level
							if (eachLevel as number) is 0 then -- Delta level can't be 0 (also checks string is a number)
								set validEntry to false
								exit repeat
							end if
						else
							set finalCheck to (rest of characters of eachLevel as text) as number -- Rest of string after @ must be a number
						end if
					end repeat
				on error
					set validEntry to false
				end try
			end if
 
			-- Alert and go back if invalid
 
			if validEntry is false then
 
				display dialog "\"" & levelsString & "\" is not a valid entry! Try again." with title dialogTitle with icon 0 ¬
					buttons {"OK"} default button "OK" giving up after 5
 
			else
 
				-- Final check that the levels have been interpreted correctly
 
				set pleaseConfirm to ""
				repeat with i from 1 to countLevelsArray by 3
					set eachRow to (item i of levelsArray) as number
					set eachColumn to (item (i + 1) of levelsArray) as number
					set eachLevel to item (i + 2) of levelsArray
					if eachRow is 0 then
						if eachColumn is 0 then
							set eachLine to "Master Level"
						else
							set eachLine to "Output " & eachColumn & " Level"
						end if
					else
						if eachColumn is 0 then
							set eachLine to "Input " & eachRow & " Level"
						else
							set eachLine to "Crosspoint Level: Input " & eachRow & " to Output " & eachColumn
						end if
					end if
					if eachLevel does not start with "@" then
						set eachLine to "> Add " & eachLevel & "dB to " & eachLine
					else
						set eachLine to "> Set " & eachLine & " @ " & (rest of characters of eachLevel as text) & "dB"
					end if
					set pleaseConfirm to pleaseConfirm & eachLine
					if (i + 2) is not countLevelsArray then
						set pleaseConfirm to pleaseConfirm & return
					end if
				end repeat
 
				set goBack to button returned of (display dialog "Please confirm you wish to adjust levels thus for the " & whichCuesString & ":" & ¬
					return & return & pleaseConfirm with title dialogTitle with icon 0 buttons {"Cancel", "No, no! Stop! That's not right! Go back!", "OK"} ¬
					default button "OK" cancel button "Cancel")
				if goBack is "No, no! Stop! That's not right! Go back!" then
					set validEntry to false
				end if
 
			end if
 
		end repeat
 
		-- Now, to business
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- Skip over cues that don't take levels!
				repeat with j from 1 to countLevelsArray by 3
					set eachRow to (item j of levelsArray) as number
					set eachColumn to (item (j + 1) of levelsArray) as number
					set eachLevel to item (j + 2) of levelsArray
					set currentLevel to getLevel eachCue row eachRow column eachColumn ¬
						-- This check will throw an error and exit the j repeat if the cue doesn't take levels
					if eachLevel does not start with "@" then
						set eachLevel to currentLevel + eachLevel
					else
						set eachLevel to (rest of characters of eachLevel as text) as number
					end if
					try -- This try will throw a detectable error if the next line doesn't work
						setLevel eachCue row eachRow column eachColumn db eachLevel ¬
							-- We're relying on QLab's ability to ignore spurious levels like "-200" or "+50"
						-- ###FIXME### As of 4.4.1, QLab appears to accept any negative value for Min Volume Limit and hence here too
					on error
						set ohDear to true
					end try
				end repeat
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustLevels
 
on adjustFileTarget(cuesToProcess, changeType, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		if changeType is "Change" then
			set promptHeader to "CHANGE File Target: start/end times maintained…"
		else
			set promptHeader to "SET File Target: this process will not maintain the start/end times!"
		end if
 
		set theTarget to choose file of type "public.audio" with prompt promptHeader & return & return & ¬
			"Please select the File Target to set for the " & whichCuesString & ":" without invisibles
 
		my startTheClock()
 
		set countCues to count cuesToProcess
		set checkTheFirst to true
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			if q type of eachCue is not "Group" then -- Setting File Target on a Group Cue (or cue list) affects all Audio Cues in it!
				try -- Skip over cues that don't take File Targets!
 
					if changeType is "Change" then
						set currentStart to start time of eachCue
						set currentEnd to end time of eachCue
					end if
 
					set file target of eachCue to theTarget ¬
						-- QLab doesn't appear to throw any errors even if the cue doesn't take a File Target, so no detection is possible
 
					if checkTheFirst is true then -- Check the first one worked (ie: didn't break the cue!) before going any further
						if broken of eachCue is true then -- This protects against (some) inappropriate files
							set abortAbort to true
							exit repeat
						end if
					end if
 
					if changeType is "Change" then
						set start time of eachCue to currentStart
						set end time of eachCue to currentEnd
					end if
 
				end try
				set checkTheFirst to false -- We need to check the first cue that took a target, not the first cue we tried
			end if
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustFileTarget
 
on adjustSetName(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set theName to my enterSomeText("Enter the name you wish to set for the " & whichCuesString & ¬
			" (return an empty string to reset to default names):", "", true)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set q name of eachCue to theName
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSetName
 
on adjustSearchReplaceName(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set searchFor to my enterSomeText("Enter the search string you wish to replace in the names of the " & whichCuesString & " (not case-sensitive):", ¬
			"", false)
		set replaceWith to my enterSomeText("Enter the string with which you wish to replace all occurrences of \"" & ¬
			searchFor & "\" in the names of the " & whichCuesString & ":", "", true)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
		set currentTIDs to AppleScript's text item delimiters
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set currentName to q list name of eachCue
			set AppleScript's text item delimiters to searchFor
			set searchedName to text items of currentName
			set AppleScript's text item delimiters to replaceWith
			set theName to searchedName as text
			set AppleScript's text item delimiters to currentTIDs
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set q name of eachCue to theName
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSearchReplaceName
 
on adjustPrefixName(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set thePrefix to my enterSomeText("Enter the string you wish to add to the beginning of the names of the " & whichCuesString & ¬
			" (include a space at the end if you expect one):", "", false)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set currentName to q list name of eachCue
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set q name of eachCue to thePrefix & currentName
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustPrefixName
 
on adjustSuffixName(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set theSuffix to my enterSomeText("Enter the string you wish to add to the end of the names of the " & whichCuesString & ¬
			" (include a space at the beginning if you expect one):", "", false)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set currentName to q list name of eachCue
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set q name of eachCue to currentName & theSuffix
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSuffixName
 
on adjustSeriesName(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set validEntry to false
		set previousBaseName to ""
		set previousStartNumber to ""
		set previousIncrement to ""
		set previousPadding to ""
		repeat until validEntry is true
			set baseName to my enterSomeText("Enter the base of the name series you wish to set for the " & whichCuesString & ¬
				" (include a space at the end if you expect one):", previousBaseName, false)
			set startNumber to my enterAnInteger("Enter an integer with which to start the series:", previousStartNumber)
			set theIncrement to my enterAnInteger("Enter the increment for each step (an integer):", previousIncrement)
			set thePadding to my enterANumberWithRangeWithCustomButton("Enter the minimum number of digits – an integer between 1 & 10 " & ¬
				"(eg: entering 2 will result in the series 01, 02, etc):", previousPadding, 1, true, 10, true, true, {}, "OK")
			set previousBaseName to baseName
			set previousStartNumber to startNumber
			set previousIncrement to theIncrement
			set previousPadding to thePadding
			set counterConfirm1 to my padNumber(startNumber, thePadding)
			set pleaseConfirm1 to "> " & baseName & counterConfirm1
			set counterConfirm2 to my padNumber(startNumber + theIncrement, thePadding)
			set pleaseConfirm2 to "> " & baseName & counterConfirm2
			set counterConfirm3 to my padNumber(startNumber + 2 * theIncrement, thePadding)
			set pleaseConfirm3 to "> " & baseName & counterConfirm3
			set pleaseConfirm to pleaseConfirm1 & return & pleaseConfirm2 & return & pleaseConfirm3 & return & "> …"
			set goBack to button returned of (display dialog "Please confirm you wish to set the names as a series of this ilk for the " & whichCuesString & ¬
				":" & return & return & pleaseConfirm with title dialogTitle with icon 0 buttons {"Cancel", "No, no! Stop! That's not right! Go back!", "OK"} ¬
				default button "OK" cancel button "Cancel")
			if goBack is "OK" then
				set validEntry to true
			end if
		end repeat
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set theCounter to startNumber + (i - 1) * theIncrement
			set theCounter to my padNumber(theCounter, thePadding)
			set theName to baseName & theCounter
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set q name of eachCue to theName
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSeriesName
 
on adjustPrefixNumber(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set thePrefix to my enterSomeText("Enter the string you wish to add to the beginning of the Cue Numbers of the " & whichCuesString & ¬
			" (include a space at the end if you expect one):" & return & return & ¬
			"(NB: if the Cue Number proposed already exists it won't be changed.)", "", false)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set currentNumber to q number of eachCue
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set q number of eachCue to thePrefix & currentNumber
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustPrefixNumber
 
on adjustSuffixNumber(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set theSuffix to my enterSomeText("Enter the string you wish to add to the end of the Cue Numbers of the " & whichCuesString & ¬
			" (include a space at the beginning if you expect one):" & return & return & ¬
			"(NB: if the Cue Number proposed already exists it won't be changed.)", "", false)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set currentNumber to q number of eachCue
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set q number of eachCue to currentNumber & theSuffix
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSuffixNumber
 
on adjustSeriesNumber(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set validEntry to false
		set previousBaseName to ""
		set previousStartNumber to ""
		set previousIncrement to ""
		set previousPadding to ""
		repeat until validEntry is true
			set baseName to my enterSomeText("Enter the base of the Cue Number series you wish to set for the " & whichCuesString & ¬
				" (include a space at the end if you expect one):", previousBaseName, false)
			set startNumber to my enterAnInteger("Enter an integer with which to start the series:", previousStartNumber)
			set theIncrement to my enterAnInteger("Enter the increment for each step (an integer):", previousIncrement)
			set thePadding to my enterANumberWithRangeWithCustomButton("Enter the minimum number of digits – an integer between 1 & 10 " & ¬
				"(eg: entering 2 will result in the series 01, 02, etc):", previousPadding, 1, true, 10, true, true, {}, "OK")
			set previousBaseName to baseName
			set previousStartNumber to startNumber
			set previousIncrement to theIncrement
			set previousPadding to thePadding
			set counterConfirm1 to my padNumber(startNumber, thePadding)
			set pleaseConfirm1 to "> " & baseName & counterConfirm1
			set counterConfirm2 to my padNumber(startNumber + theIncrement, thePadding)
			set pleaseConfirm2 to "> " & baseName & counterConfirm2
			set counterConfirm3 to my padNumber(startNumber + 2 * theIncrement, thePadding)
			set pleaseConfirm3 to "> " & baseName & counterConfirm3
			set pleaseConfirm to pleaseConfirm1 & return & pleaseConfirm2 & return & pleaseConfirm3 & return & "> …"
			set goBack to button returned of (display dialog ¬
				"Please confirm you wish to set the Cue Numbers as a series of this ilk for the " & whichCuesString & ":" & return & return & ¬
				pleaseConfirm with title dialogTitle with icon 0 buttons {"Cancel", "No, no! Stop! That's not right! Go back!", "OK"} ¬
				default button "OK" cancel button "Cancel")
			if goBack is "OK" then
				set validEntry to true
			end if
		end repeat
 
		my startTheClock()
 
		set countCues to count cuesToProcess
		set j to 0
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set existingCueNumber to true
			repeat while existingCueNumber is true
				set theCounter to startNumber + j * theIncrement
				set theCounter to my padNumber(theCounter, thePadding)
				set theName to baseName & theCounter
				try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
					set q number of eachCue to theName
					if q number of eachCue is theName then -- Check the number has stuck; if it's already in use go round again
						set existingCueNumber to false
					else
						set j to j + 1
						set existingCueNumber to true
					end if
				on error
					set ohDear to true
				end try
			end repeat
			set j to j + 1
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSeriesNumber
 
on adjustClearNotes(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set notes of eachCue to ""
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustClearNotes
 
on adjustSetNotes(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set theNotes to my enterSomeText("Enter the Notes you wish to set for the " & whichCuesString & ":", "", false)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set notes of eachCue to theNotes
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSetNotes
 
on adjustSearchReplaceNotes(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set searchFor to my enterSomeText("Enter the search string you wish to replace in the Notes of the " & whichCuesString & " (not case-sensitive):", "", false)
		set replaceWith to my enterSomeText("Enter the string with which wish to replace all occurrences of \"" & ¬
			searchFor & "\" in the Notes of the " & whichCuesString & ":", "", true)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
		set currentTIDs to AppleScript's text item delimiters
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set currentNotes to notes of eachCue
			set AppleScript's text item delimiters to searchFor
			set searchedNotes to text items of currentNotes
			set AppleScript's text item delimiters to replaceWith
			set theNotes to searchedNotes as text
			set AppleScript's text item delimiters to currentTIDs
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set notes of eachCue to theNotes
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSearchReplaceNotes
 
on adjustPrefixNotes(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set thePrefix to my enterSomeText("Enter the string you wish to add to the beginning of the Notes of the " & whichCuesString & ¬
			" (include a space at the end if you expect one):", "", false)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set currentNotes to notes of eachCue
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set notes of eachCue to thePrefix & currentNotes
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustPrefixNotes
 
on adjustSuffixNotes(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set theSuffix to my enterSomeText("Enter the string you wish to add to the end of the Notes of the " & whichCuesString & ¬
			" (include a space at the beginning if you expect one):", "", false)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set currentNotes to notes of eachCue
			try -- This try will throw a detectable error if the next line doesn't work (hard to think of a reason why it wouldn't though!)
				set notes of eachCue to currentNotes & theSuffix
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSuffixNotes
 
on adjustSetTime(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceTimesParameter = {"Pre Wait", "Duration", "Post Wait"}
 
	tell application id "com.figure53.QLab.4"
 
		if theParameter is item 3 of subChoiceTimesParameter then -- Special option to set Post Waits to the same time as the Duration
			set specialCase to "Set to Duration"
		else
			set specialCase to {}
		end if
 
		set theTime to my enterATimeWithCustomButton("Enter the time you wish to set as the " & theParameter & ¬
			" for the " & whichCuesString & " (seconds or minutes:seconds):", "", specialCase)
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- This try will throw a detectable error if the appropriate line doesn't work
				if theParameter is item 1 of subChoiceTimesParameter then
					set pre wait of eachCue to theTime
				else if theParameter is item 2 of subChoiceTimesParameter then
					set duration of eachCue to theTime
				else if theParameter is item 3 of subChoiceTimesParameter then
					if theTime is specialCase then
						set theDuration to duration of eachCue
						set post wait of eachCue to theDuration
					else
						set post wait of eachCue to theTime
					end if
				end if
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSetTime
 
on adjustScaleTime(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceTimesParameter = {"Pre Wait", "Duration", "Post Wait"}
 
	tell application id "com.figure53.QLab.4"
 
		set theMultiplicand to my enterARatio("Enter the ratio with which you wish to scale the " & theParameter & ¬
			" for the " & whichCuesString & " (eg: 1.1 will make them 10% longer):", "")
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- This try will throw a detectable error if the appropriate line doesn't work
				if theParameter is item 1 of subChoiceTimesParameter then
					set currentTime to pre wait of eachCue
					set pre wait of eachCue to currentTime * theMultiplicand
				else if theParameter is item 2 of subChoiceTimesParameter then
					set currentTime to duration of eachCue
					set duration of eachCue to currentTime * theMultiplicand
				else if theParameter is item 3 of subChoiceTimesParameter then
					set currentTime to post wait of eachCue
					set post wait of eachCue to currentTime * theMultiplicand
				end if
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustScaleTime
 
on adjustAddSubractTime(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceTimesParameter = {"Pre Wait", "Duration", "Post Wait"}
 
	tell application id "com.figure53.QLab.4"
 
		set theAddend to my enterANumber("Enter the number of seconds you wish to add to " & theParameter & " for the " & whichCuesString & ":", "")
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- This try will throw a detectable error if the appropriate line doesn't work
				if theParameter is item 1 of subChoiceTimesParameter then
					set currentTime to pre wait of eachCue
					set pre wait of eachCue to currentTime + theAddend
				else if theParameter is item 2 of subChoiceTimesParameter then
					set currentTime to duration of eachCue
					set duration of eachCue to currentTime + theAddend
				else if theParameter is item 3 of subChoiceTimesParameter then
					set currentTime to post wait of eachCue
					set post wait of eachCue to currentTime + theAddend
				end if
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustAddSubractTime
 
on adjustSetMIDICommand(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set theParameter to my pickFromList(subChoiceMIDICommand, "Set the MIDI Command of the selected cues to:")
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- Skip over cues that don't take MIDI!
				if message type of eachCue is not voice then -- This will throw an error if eachCue isn't a MIDI Cue
					error -- This rejects MSC & SysEx cues
				end if
				try -- This try will throw a detectable error if the appropriate line doesn't work
					if theParameter is item 1 of subChoiceMIDICommand then
						set command of eachCue to note_on
					else if theParameter is item 2 of subChoiceMIDICommand then
						set command of eachCue to note_off
					else if theParameter is item 3 of subChoiceMIDICommand then
						set command of eachCue to program_change
					else if theParameter is item 4 of subChoiceMIDICommand then
						set command of eachCue to control_change
					else if theParameter is item 5 of subChoiceMIDICommand then
						set command of eachCue to key_pressure
					else if theParameter is item 6 of subChoiceMIDICommand then
						set command of eachCue to channel_pressure
					else if theParameter is item 7 of subChoiceMIDICommand then
						set command of eachCue to pitch_bend
					end if
				on error
					set ohDear to true
				end try
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSetMIDICommand
 
on adjustSetMIDI(cuesToProcess, theParameter, whichCuesString) -- This is the only one that handles "Channel"
 
	-- subChoiceMIDIParameter = {"Command", "Channel", "Byte One", "Byte Two", "Byte Combo", "End Value"}
 
	tell application id "com.figure53.QLab.4"
 
		if theParameter is item 2 of subChoiceMIDIParameter then
			set theMin to 1
			set theMax to 16
		else if theParameter is item 5 of subChoiceMIDIParameter then
			set theMin to -8192
			set theMax to 8191
		else
			set theMin to 0
			set theMax to 127
		end if
 
		set theInteger to my enterANumberWithRangeWithCustomButton("Enter the value to which you wish to set the " & theParameter & ¬
			" for the " & whichCuesString & ":", "", theMin, true, theMax, true, true, {}, "OK")
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- Skip over cues that don't take MIDI!
				if message type of eachCue is not voice then -- This will throw an error if eachCue isn't a MIDI Cue
					error -- This rejects MSC & SysEx cues
				end if
				try -- This try will throw a detectable error if the appropriate line doesn't work
					if theParameter is item 2 of subChoiceMIDIParameter then
						set channel of eachCue to theInteger
					else if theParameter is item 3 of subChoiceMIDIParameter then
						if command of eachCue is not pitch_bend then
							set byte one of eachCue to theInteger
						end if
					else if theParameter is item 4 of subChoiceMIDIParameter then
						if command of eachCue is not pitch_bend then
							set byte two of eachCue to theInteger
						end if
					else if theParameter is item 5 of subChoiceMIDIParameter then
						if command of eachCue is pitch_bend then
							set byte combo of eachCue to theInteger + 8192 -- Pitch bend of 0 in the Inspector is reported to AppleScript as 8192
						end if
					else if theParameter is item 6 of subChoiceMIDIParameter then
						if command of eachCue is not pitch_bend then
							set end value of eachCue to theInteger
						else
							set end value of eachCue to theInteger + 8192
						end if
					end if
				on error
					set ohDear to true
				end try
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSetMIDI
 
on adjustScaleMIDI(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceMIDIParameter = {"Command", "Channel", "Byte One", "Byte Two", "Byte Combo", "End Value"}
 
	tell application id "com.figure53.QLab.4"
 
		set theMultiplicand to my enterARatio("Enter the ratio with which you wish to scale the " & theParameter & ¬
			" for the " & whichCuesString & " (eg: 1.1 will make them 10% larger):", "")
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- Skip over cues that don't take MIDI!
				if message type of eachCue is not voice then -- This will throw an error if eachCue isn't a MIDI Cue
					error -- This rejects MSC & SysEx cues
				end if
				try -- This try will throw a detectable error if the appropriate line doesn't work
					if theParameter is item 3 of subChoiceMIDIParameter then
						if command of eachCue is not pitch_bend then
							set currentValue to byte one of eachCue
							set byte one of eachCue to currentValue * theMultiplicand
						end if
					else if theParameter is item 4 of subChoiceMIDIParameter then
						if command of eachCue is not pitch_bend then
							set currentValue to byte two of eachCue
							set byte two of eachCue to currentValue * theMultiplicand
						end if
					else if theParameter is item 5 of subChoiceMIDIParameter then
						if command of eachCue is pitch_bend then
							set currentValue to (byte combo of eachCue) - 8192
							set byte combo of eachCue to currentValue * theMultiplicand + 8192
						end if
					else if theParameter is item 6 of subChoiceMIDIParameter then
						if command of eachCue is not pitch_bend then
							set currentValue to end value of eachCue
							set end value of eachCue to currentValue * theMultiplicand
						else
							set currentValue to ((end value of eachCue) - 8192)
							set end value of eachCue to (currentValue * theMultiplicand) + 8192
						end if
					end if
				on error
					set ohDear to true
				end try
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustScaleMIDI
 
on adjustAddSubractMIDI(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceMIDIParameter = {"Command", "Channel", "Byte One", "Byte Two", "Byte Combo", "End Value"}
 
	tell application id "com.figure53.QLab.4"
 
		set theAddend to my enterAnInteger("Enter the integer you wish to add to " & theParameter & " for the " & whichCuesString & ":", "")
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- Skip over cues that don't take MIDI!
				if message type of eachCue is not voice then -- This will throw an error if eachCue isn't a MIDI Cue
					error -- This rejects MSC & SysEx cues
				end if
				try -- This try will throw a detectable error if the appropriate line doesn't work
					if theParameter is item 3 of subChoiceMIDIParameter then
						set currentValue to byte one of eachCue
						set byte one of eachCue to currentValue + theAddend
					else if theParameter is item 4 of subChoiceMIDIParameter then
						set currentValue to byte two of eachCue
						set byte two of eachCue to currentValue + theAddend
					else if theParameter is item 5 of subChoiceMIDIParameter then
						set currentValue to byte combo of eachCue
						set byte combo of eachCue to currentValue + theAddend
					else if theParameter is item 6 of subChoiceMIDIParameter then
						set currentValue to end value of eachCue
						set end value of eachCue to currentValue + theAddend
					end if
				on error
					set ohDear to true
				end try
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustAddSubractMIDI
 
on adjustSeriesMIDI(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceMIDIParameter = {"Command", "Channel", "Byte One", "Byte Two", "Byte Combo", "End Value"}
 
	tell application id "com.figure53.QLab.4"
 
		if theParameter is item 5 of subChoiceMIDIParameter then
			set theMin to -8192
			set theMax to 8191
		else
			set theMin to 0
			set theMax to 127
		end if
 
		set startNumber to my enterANumberWithRangeWithCustomButton("Enter the value to which you wish to set the " & theParameter & ¬
			" of the first of the " & whichCuesString & ":", "", theMin, true, theMax, true, true, {}, "OK")
		set theIncrement to my enterAnInteger("Enter the increment for each step:", "")
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			set theCounter to startNumber + (i - 1) * theIncrement -- We're relying on QLab to hit a ceiling/floor for this
			try -- Skip over cues that don't take MIDI!
				if message type of eachCue is not voice then -- This will throw an error if eachCue isn't a MIDI Cue
					error -- This rejects MSC & SysEx cues
				end if
				try -- This try will throw a detectable error if the appropriate line doesn't work
					if theParameter is item 2 of subChoiceMIDIParameter then
						set channel of eachCue to theCounter
					else if theParameter is item 3 of subChoiceMIDIParameter then
						if command of eachCue is not pitch_bend then
							set byte one of eachCue to theCounter
						end if
					else if theParameter is item 4 of subChoiceMIDIParameter then
						if command of eachCue is not pitch_bend then
							set byte two of eachCue to theCounter
						end if
					else if theParameter is item 5 of subChoiceMIDIParameter then
						if command of eachCue is pitch_bend then
							set byte combo of eachCue to theCounter + 8192 -- Pitch bend of 0 in the Inspector is reported to AppleScript as 8192
						end if
					else if theParameter is item 6 of subChoiceMIDIParameter then
						if command of eachCue is not pitch_bend then
							set end value of eachCue to theCounter
						else
							set end value of eachCue to theCounter + 8192
						end if
					end if
				on error
					set ohDear to true
				end try
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSeriesMIDI
 
on adjustDeviceID(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set theParameter to my enterANumberWithRangeWithCustomButton("Enter the MSC Device ID you wish to set for the " & whichCuesString & ¬
			" (an integer from 0 to 127):", "", 0, true, 127, true, true, {}, "OK")
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			if message type of eachCue is msc then
				try -- This try will throw a detectable error if the appropriate line doesn't work (hard to think of a reason why it wouldn't though!)
					set deviceID of eachCue to theParameter
				on error
					set ohDear to true
				end try
			end if
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustDeviceID
 
on adjustAutoload(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceAutoload = {"On", "Off"}
 
	tell application id "com.figure53.QLab.4"
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- This try will throw a detectable error if the appropriate line doesn't work (hard to think of a reason why it wouldn't though!)
				if theParameter is item 1 of subChoiceAutoload then
					set autoload of eachCue to true
				else if theParameter is item 2 of subChoiceAutoload then
					set autoload of eachCue to false
				end if
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustAutoload
 
on adjustArmed(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceArmed = {"Armed", "Disarmed"}
 
	tell application id "com.figure53.QLab.4"
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- This try will throw a detectable error if the appropriate line doesn't work (hard to think of a reason why it wouldn't though!)
				if theParameter is item 1 of subChoiceArmed then
					set armed of eachCue to true
				else if theParameter is item 2 of subChoiceArmed then
					set armed of eachCue to false
				end if
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustArmed
 
on adjustContinueMode(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceContinueMode = {"Do not continue", "Auto-continue", "Auto-follow"}
 
	tell application id "com.figure53.QLab.4"
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- This try will throw a detectable error if the appropriate line doesn't work (hard to think of a reason why it wouldn't though!)
				if theParameter is item 1 of subChoiceContinueMode then
					set continue mode of eachCue to do_not_continue
				else if theParameter is item 2 of subChoiceContinueMode then
					set continue mode of eachCue to auto_continue
				else if theParameter is item 3 of subChoiceContinueMode then
					set continue mode of eachCue to auto_follow
				end if
			on error
				set ohDear to true
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustContinueMode
 
on adjustMode(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceMode = {"Timeline - Start all children simultaneously","Start first child and enter into group", "Start first child and go to next cue" ,
	(* "Start random child and go to next cue"} *)
 
	tell application id "com.figure53.QLab.4"
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			try -- Skip over cues that aren't Groups
				if mode of eachCue is not cue_list then -- Don't adjust cue lists!
					try -- This try will throw a detectable error if the appropriate line doesn't work
						if theParameter is item 1 of subChoiceMode then
							set mode of eachCue to timeline
						else if theParameter is item 2 of subChoiceMode then
							set mode of eachCue to fire_first_enter_group
						else if theParameter is item 3 of subChoiceMode then
							set mode of eachCue to fire_first_go_to_next_cue
						else if theParameter is item 4 of subChoiceMode then
							set mode of eachCue to fire_random
						end if
					on error
						set ohDear to true
					end try
				end if
			end try
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustMode
 
on adjustPatch(cuesToProcess, whichCuesString)
 
	tell application id "com.figure53.QLab.4"
 
		set theParameter to my enterANumberWithRangeWithCustomButton("Enter the patch you wish to set for the " & whichCuesString & ¬
			" (1-8, 1-16 for Network Cues):", "", 1, true, false, false, true, {}, "OK")
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			if q type of eachCue is in {"Audio", "Mic", "Video", "Network", "MIDI", "MIDI File", "Timecode"} then
				try -- This try will throw a detectable error if the appropriate line doesn't work (hard to think of a reason why it wouldn't though!)
					set patch of eachCue to theParameter
				on error
					set ohDear to true
				end try
			end if
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustPatch
 
on adjustCameraPatch(cuesToProcess, theParameter, whichCuesString)
 
	-- subChoiceCameraPatch = {1, 2, 3, 4, 5, 6, 7, 8}
 
	tell application id "com.figure53.QLab.4"
 
		my startTheClock()
 
		set countCues to count cuesToProcess
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			if q type of eachCue is "Camera" then
				try -- This try will throw a detectable error if the appropriate line doesn't work (hard to think of a reason why it wouldn't though!)
					set camera patch of eachCue to theParameter
				on error
					set ohDear to true
				end try
			end if
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustCameraPatch
 
on adjustSearchReplaceCommandText(cuesToProcess, whichCuesString, instrumentNamesONLY)
 
	tell application id "com.figure53.QLab.4"
 
		if instrumentNamesONLY is true then
			set searchFor to my enterSomeText("Enter the search string you wish to replace in the Instrument Names in the Command Text of the " & ¬
				whichCuesString & " (not case-sensitive):", "", false)
			set replaceWith to my enterSomeText("Enter the string with which wish to replace all occurrences of \"" & ¬
				searchFor & "\" in Instrument Names in the the Command Text of the " & whichCuesString & ":", "", true)
			set searchFor to searchFor & " = "
			set replaceWith to replaceWith & " = "
		else
			set searchFor to my enterSomeText("Enter the search string you wish to replace in the Command Text of the " & whichCuesString & ¬
				" (not case-sensitive):", "", false)
			set replaceWith to my enterSomeText("Enter the string with which wish to replace all occurrences of \"" & ¬
				searchFor & "\" in the Command Text of the " & whichCuesString & ":", "", true)
		end if
 
		my startTheClock()
 
		set countCues to count cuesToProcess
		set currentTIDs to AppleScript's text item delimiters
 
		repeat with i from 1 to countCues
			set eachCue to item i of cuesToProcess
			if q type of eachCue is "Light" then
				try -- This try will throw a detectable error if something doesn't work
					set currentCommandText to command text of eachCue
					if currentCommandText is not missing value then -- Skip cues that are empty
						set AppleScript's text item delimiters to searchFor
						set searchedCommandText to text items of currentCommandText
						set AppleScript's text item delimiters to replaceWith
						set theCommandText to searchedCommandText as text
						set AppleScript's text item delimiters to currentTIDs
						set command text of eachCue to theCommandText
					end if
				on error
					set ohDear to true
				end try
			end if
			if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countCues, whichCuesString)
			end if
		end repeat
 
		my finishedDialogBespoke()
 
	end tell
 
end adjustSearchReplaceCommandText
 
(* === TIME === *)
 
on makeMSS(howLong) -- [Shared subroutine]
	set howManyMinutes to howLong div 60
	set howManySeconds to howLong mod 60 div 1
	return (howManyMinutes as text) & ":" & my padNumber(howManySeconds, 2)
end makeMSS
 
on makeNiceT(howLong) -- [Shared subroutine]
	if howLong < 1 then
		return "less than a second"
	end if
	set howManyHours to howLong div 3600
	if howManyHours is 0 then
		set hourString to ""
	else if howManyHours is 1 then
		set hourString to "1 hour"
	else
		set hourString to (howManyHours as text) & " hours"
	end if
	set howManyMinutes to howLong mod 3600 div 60
	if howManyMinutes is 0 then
		set minuteString to ""
	else if howManyMinutes is 1 then
		set minuteString to "1 minute"
	else
		set minuteString to (howManyMinutes as text) & " minutes"
	end if
	set howManySeconds to howLong mod 60 div 1
	if howManySeconds is 0 then
		set secondString to ""
	else if howManySeconds is 1 then
		set secondString to "1 second"
	else
		set secondString to (howManySeconds as text) & " seconds"
	end if
	set theAmpersand to ""
	if hourString is not "" then
		if minuteString is not "" and secondString is not "" then
			set theAmpersand to ", "
		else if minuteString is not "" or secondString is not "" then
			set theAmpersand to " and "
		end if
	end if
	set theOtherAmpersand to ""
	if minuteString is not "" and secondString is not "" then
		set theOtherAmpersand to " and "
	end if
	return hourString & theAmpersand & minuteString & theOtherAmpersand & secondString
end makeNiceT
 
on makeSecondsFromM_S(howLong) -- [Shared subroutine]
	try
		set currentTIDs to AppleScript's text item delimiters
		set AppleScript's text item delimiters to ":"
		set theMinutes to first text item of howLong
		set theSeconds to rest of text items of howLong as text
		set AppleScript's text item delimiters to currentTIDs
		return theMinutes * 60 + theSeconds
	on error
		return false
	end try
end makeSecondsFromM_S
 
(* === TEXT WRANGLING === *)
 
on padNumber(theNumber, minimumDigits) -- [Shared subroutine]
	set paddedNumber to theNumber as text
	repeat while (count paddedNumber) < minimumDigits
		set paddedNumber to "0" & paddedNumber
	end repeat
	return paddedNumber
end padNumber

General

Group selected cue(s)

Put selected cue(s) in a new Group Cue:

WARNING!!!! This script will move selected cues out of any Group Cues they are in and into the new Group Cue. If you want a Group Cue to remain intact when it is moved, DO NOT include its children in the selection! Also, remember that new cues are inserted into the cue list after the last cue that was selected (which is not always the same as the cue at the bottom of the selection); this will dictate where the new Group Cue is made, and hence what is moved into it – as the script can't move an existing Group Cue inside the new Group Cue if the new Group Cue is made within the existing Group Cue… It is subtly different to the built-in behaviour when making a new Group Cue with a selection, not least because it acts on single selected cues! The new Group Cue's settings will follow the Workspace Preferences, by the way. (Doesn't fire in a cart!)

IT IS VERY EASY TO CONSTRUCT ELABORATE NESTS OF GROUPS THAT WILL CAUSE THIS SCRIPT TO HANG QLAB!

tell front workspace
 
	if q type of current cue list is "Cart" then return -- This will stop the script if we're in a cart, as it doesn't make sense to continue!
 
	set selectedCues to (selected as list)
 
	if (count selectedCues) is not 0 then
 
		set selected to last item of selectedCues -- Protect against default behaviour
		make type "Group"
		set groupCue to last item of (selected as list)
		set groupCueIsIn to parent of groupCue
 
		repeat with eachCue in selectedCues
			if contents of eachCue is not groupCueIsIn then -- Skip a Group Cue that contains the new Group Cue
				set eachCueID to uniqueID of eachCue
				try
					move cue id eachCueID of parent of eachCue to end of groupCue
				end try
			end if
		end repeat
 
	end if
 
end tell

Group selected cue(s) with number & notes

Put selected cue(s) in a new Group Cue, retaining Cue Number & Notes from first cue:

WARNING!!!! This script will move selected cues out of any Group Cues they are in and into the new Group Cue. If you want a Group Cue to remain intact when it is moved, DO NOT include its children in the selection! Also, remember that new cues are inserted into the cue list after the last cue that was selected (which is not always the same as the cue at the bottom of the selection); this will dictate where the new Group Cue is made, and hence what is moved into it – as the script can't move an existing Group Cue inside the new Group Cue if the new Group Cue is made within the existing Group Cue… It is subtly different to the built-in behaviour when making a new Group Cue with a selection, not least because it acts on single selected cues! The new Group Cue's settings will follow the Workspace Preferences, by the way. (Doesn't fire in a cart!)

IT IS VERY EASY TO CONSTRUCT ELABORATE NESTS OF GROUPS THAT WILL CAUSE THIS SCRIPT TO HANG QLAB!

tell front workspace
 
	if q type of current cue list is "Cart" then return -- This will stop the script if we're in a cart, as it doesn't make sense to continue!
 
	set selectedCues to (selected as list)
 
	if (count selectedCues) is not 0 then
 
		set selected to last item of selectedCues -- Protect against default behaviour
		make type "Group"
		set groupCue to last item of (selected as list)
		set groupCueIsIn to parent of groupCue
 
		set cueNumber to q number of first item of selectedCues
		set q number of first item of selectedCues to ""
		set q number of groupCue to cueNumber
 
		set cueNames to {}
		repeat with i from 1 to count selectedCues
			set eachName to q list name of item i of selectedCues
			if eachName is not "" then
				set end of cueNames to eachName
			end if
		end repeat
		set currentTIDs to AppleScript's text item delimiters
		set AppleScript's text item delimiters to " & "
		set q name of groupCue to cueNames as text
		set AppleScript's text item delimiters to currentTIDs
 
		set cueNotes to notes of first item of selectedCues
		set notes of first item of selectedCues to ""
		set notes of groupCue to cueNotes
 
		repeat with eachCue in selectedCues
			if contents of eachCue is not groupCueIsIn then -- Skip a Group Cue that contains the new Group Cue
				set eachCueID to uniqueID of eachCue
				try
					move cue id eachCueID of parent of eachCue to end of groupCue
				end try
			end if
		end repeat
 
	end if
 
end tell

Convert Group Cue

Convert Group Cues between “Start first child and go to next cue” sequences and “Timeline” with independent Pre Waits; IT IS NOT GUARANTEED TO COVER EVERY POSSIBLE SCENARIO SO USE IT AT YOUR OWN RISK! In particular, it will not work if the children of a “Timeline” Group Cue are not in the order in which their Pre Waits will cause them to fire (ie: with each child's Pre Wait greater than or equal to that of the previous child)…

tell front workspace
	try
		set selectedCue to last item of (selected as list)
		if q type of selectedCue is "Group" then
			if mode of selectedCue is fire_first_go_to_next_cue then
				set theCues to cues of selectedCue
				set deltaTime to 0
				repeat with eachCue in theCues
					set eachPre to pre wait of eachCue
					set deltaTime to deltaTime + eachPre
					set pre wait of eachCue to deltaTime
					if continue mode of eachCue is auto_follow then
						set eachPost to duration of eachCue
					else
						set eachPost to post wait of eachCue
					end if
					set deltaTime to deltaTime + eachPost
					set post wait of eachCue to 0
					set continue mode of eachCue to do_not_continue
				end repeat
				set mode of selectedCue to timeline -- Can't set continue modes while mode is timeline, so have to do this here…
				if post wait of selectedCue is duration of selectedCue then -- Clear Post Wait if it looks like it's been set by "effective duration" script
					set post wait of selectedCue to 0
				end if
			else if mode of selectedCue is timeline then
				set theCues to cues of selectedCue
				set previousPre to 0
				set mode of selectedCue to fire_first_go_to_next_cue
				repeat with eachCue in theCues
					set eachPre to pre wait of eachCue
					set deltaTime to eachPre - previousPre
					set previousPre to eachPre
					set pre wait of eachCue to deltaTime
					if contents of eachCue is not last item of theCues then
						set continue mode of eachCue to auto_continue
					end if
				end repeat
			end if
		end if
	end try
end tell

Set target

Set target of selected cue to cue above (for a selected Fade Cue, the script will find the closest preceding Group, Audio, Mic, Video, Camera or Text Cue (rather than just the cue above); likewise, Devamp Cues will look for the closest Audio or Video Cue):

-- Declarations
 
set simpleCases to {"Start", "Stop", "Pause", "Load", "Reset", "GoTo", "Target", "Arm", "Disarm"}
set specialCases to {"Fade", "Devamp"}
set acceptableTargets to {{"Group", "Audio", "Mic", "Video", "Camera", "Text"}, {"Audio", "Video"}}
 
-- Main routine
 
tell front workspace
	try -- This protects against no selection (can't get last item of (selected as list))
		set originalCue to last item of (selected as list)
		set originalType to q type of originalCue
		if originalType is in simpleCases then
			set targetCue to cue before originalCue
			set cue target of originalCue to targetCue
			set targetName to q list name of targetCue
			if targetName is "" then
				set targetName to q display name of targetCue
			end if
			set q name of originalCue to originalType & ": " & targetName
		else if originalType is in specialCases then
			repeat with i from 1 to count specialCases
				if originalType is item i of specialCases then
					set acceptableTypes to item i of acceptableTargets
					exit repeat
				end if
			end repeat
			set foundType to ""
			set checkedCue to originalCue
			repeat while foundType is not in acceptableTypes
				set targetCue to cue before checkedCue
				set foundType to q type of targetCue
				if targetCue is first item of cues of current cue list then -- Protect against infinite loop if no acceptable target found
					exit repeat
				end if
				set checkedCue to targetCue
			end repeat
			if foundType is in acceptableTypes then -- Don't change the target if we've gone all the way up the cue list without finding one
				set cue target of originalCue to targetCue
				set targetName to q list name of targetCue
				if targetName is "" then
					set targetName to q display name of targetCue
				end if
				set q name of originalCue to originalType & ": " & targetName
			end if
		end if
	end try
end tell

Reveal target file

Reveal selected cue's target in Finder (although this functionality is built-in, the button takes some locating…):

tell front workspace
	try -- This protects against no selection (can't get last item of (selected as list))
		set selectedCue to last item of (selected as list)
		set fileTarget to file target of selectedCue
		tell application "Finder"
			reveal fileTarget
			activate
		end tell
	end try
end tell

Import files

Choose multiple audio, video and MIDI files and add them to the workspace as new cues; the original files will be copied to the appropriate subfolder next to the workspace, unless they already exist there (in which case the existing files will be used); the script can be adjusted to enforce copying only for non-local files, eg: those on network servers:

set userCopyOnImport to true -- Set this to false if you do not want the files to be copied to the workspace folder
set userForceCopyFromNetwork to true -- Set this to false if you do not want files that aren't on local drives to be copied automatically
set userReassuranceThreshold to 5 -- When the number of files imported is greater than this variable, a dialog will let you know that the process is complete
 
-- ###FIXME### Should audioFileTypes include "public.ulaw-audio"?
-- ###FIXME### Is videoFileTypes a sufficiently exhaustive list? Are any of the file types not supported by QLab?
 
-- Declarations
 
global dialogTitle, sharedPath
set dialogTitle to "Import files"
 
set audioFileTypes to {"com.apple.coreaudio-format", "com.apple.m4a-audio", "com.microsoft.waveform-audio", "public.aifc-audio", "public.aiff-audio", ¬
	"public.audio", "public.mp3", "public.mpeg-4-audio"} -- This list deliberately excludes "com.apple.protected-mpeg-4-audio" to protect against
(* old DRM-restricted iTunes files *)
set videoFileTypes to {"com.adobe.photoshop-image", "com.apple.icns", "com.apple.macpaint-image", "com.apple.pict", "com.apple.quicktime-image", ¬
	"com.apple.quicktime-movie", "public.3gpp", "public.3gpp2", "public.avi", "public.camera-raw-image", "public.image", "public.jpeg", "public.jpeg-2000", ¬
	"public.movie", "public.mpeg", "public.mpeg-4", "public.png", "public.tiff", "public.video", "public.xbitmap-image"}
set midiFileTypes to {"public.midi-audio"}
 
(* cf: https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html *)
 
set theFileTypes to {audioFileTypes, videoFileTypes, midiFileTypes}
set foldersExist to {null, null, null}
set theSubfolders to {"audio", "video", "midi file"}
set theCueTypes to {"Audio", "Video", "MIDI File"}
 
-- Main routine
 
tell front workspace
 
	-- Establish the path to the current workspace
 
	set workspacePath to path
	if workspacePath is missing value then
		display dialog "The current workspace has not yet been saved anywhere." with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK" giving up after 5
		return
	end if
 
	-- Get the path that should prefix all media file paths
 
	tell application "System Events"
		set sharedPath to path of container of file workspacePath
	end tell
 
	-- Choose the files to import
 
	set newTargets to choose file of type {"public.image", "public.audiovisual-content"} ¬
		with prompt "Please select one or more audio, video or MIDI files:" with multiple selections allowed
 
	-- Import them
 
	repeat with eachFile in newTargets
 
		tell application "System Events"
			set eachType to type identifier of eachFile
			set eachName to name of eachFile
			if userForceCopyFromNetwork is true then -- Only check file's locality if it will be relevant
				set fileIsLocal to local volume of disk (volume of eachFile)
			else
				set fileIsLocal to true
			end if
		end tell
 
		set eachTarget to eachFile -- This variable will be updated if the file is copied
 
		-- Work through the three types of cues that will be processed
 
		repeat with i from 1 to 3
 
			if eachType is in contents of item i of theFileTypes then
 
				if (userCopyOnImport is true) or (userForceCopyFromNetwork is true and fileIsLocal is false) then
 
					-- If copying is specified by the user definitions then…
 
					-- Check for appropriate subfolder next to workspace and make it if it doesn't exist
 
					if item i of foldersExist is null then
						set item i of foldersExist to my checkForFolder(item i of theSubfolders)
						if item i of foldersExist is false then
							my makeFolder(item i of theSubfolders)
						end if
					end if
 
					-- If the file is not already in place, copy it to the appropriate subfolder
 
					if my checkForFile(item i of theSubfolders, eachName) is false then
						my copyFileViaFinder(item i of theSubfolders, eachFile)
					end if
 
					set eachTarget to sharedPath & item i of theSubfolders & ":" & eachName
 
				end if
 
				-- Make an appropriate cue
 
				make type item i of theCueTypes
				set newCue to last item of (selected as list)
				set file target of newCue to eachTarget
 
				exit repeat
 
			end if
 
		end repeat
 
	end repeat
 
end tell
 
if (count newTargets) > userReassuranceThreshold then
	display dialog "Done." with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 5
end if
 
-- Subroutines
 
(* === FILE WRANGLING === *)
 
on checkForFile(theSuffix, theName) -- [Shared subroutine]
	tell application "System Events"
		return exists file (sharedPath & theSuffix & ":" & theName)
	end tell
end checkForFile
 
on checkForFolder(theSuffix) -- [Shared subroutine]
	tell application "System Events"
		return exists folder (sharedPath & theSuffix)
	end tell
end checkForFolder
 
on makeFolder(theFolder) -- [Shared subroutine]
	tell application "Finder"
		make new folder at sharedPath with properties {name:theFolder}
	end tell
end makeFolder
 
on copyFileViaFinder(theSuffix, theFile)
	(* NB: by using the Finder the usual file-copy progress window is invoked, which may be more reassuring than the faceless
	'do shell script "cp -p " & quoted form of POSIX path of theFile & " " & quoted form of POSIX path of (sharedPath & theSuffix & ":" & theName)'
	- which may look like a freeze (the -p flag copies every property of a file; "theName" would need to be passed to the subroutine to implement this) *)
	tell application "Finder"
		duplicate theFile to folder (sharedPath & theSuffix)
	end tell
end copyFileViaFinder

Localise media

Copy the audio, video and MIDI files referenced by the selected cue(s) to the appropriate subfolder next to the workspace (unless they already exist there, in which case the existing files will be used), keeping start/end times:

-- QLab retains slice points within the duration of a new File Target but resets the start & end times (this script maintains start & end times)
 
-- Declarations
 
global dialogTitle, sharedPath
set dialogTitle to "Localise media"
 
set foldersExist to {null, null, null}
set theSubfolders to {"audio", "video", "midi file"}
set theCueTypes to {"Audio", "Video", "MIDI File"}
 
-- Main routine
 
tell front workspace
 
	-- Establish the path to the current workspace
 
	set workspacePath to path
	if workspacePath is missing value then
		display dialog "The current workspace has not yet been saved anywhere." with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK" giving up after 5
		return
	end if
 
	-- Get the path that should prefix all media file paths
 
	tell application "System Events"
		set sharedPath to path of container of file workspacePath
	end tell
 
	-- Work through the selected cues
 
	display dialog "One moment caller…" with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 1
 
	repeat with eachCue in (selected as list)
 
		set eachType to q type of eachCue
 
		if eachType is in theCueTypes then
 
			-- Identify which item of the declared lists to use
 
			repeat with i from 1 to 3
				if eachType is item i of theCueTypes then
					set eachIndice to i
					set eachSubfolder to item eachIndice of theSubfolders
					exit repeat
				end if
			end repeat
 
			-- Get the existing target (the try protects against missing File Targets)
 
			try
 
				set eachFile to file target of eachCue as alias
				tell application "System Events"
					set eachName to name of eachFile
				end tell
 
				-- Check for appropriate subfolder next to workspace and make it if it doesn't exist
 
				if item eachIndice of foldersExist is null then
					set item eachIndice of foldersExist to my checkForFolder(eachSubfolder)
					if item eachIndice of foldersExist is false then
						my makeFolder(eachSubfolder)
					end if
				end if
 
				-- If the file is not already in place, copy it to the appropriate subfolder
 
				if my checkForFile(eachSubfolder, eachName) is false then
					my copyFile(eachSubfolder, eachFile, eachName)
				end if
 
				-- Record the new file location
 
				set eachNewTarget to sharedPath & eachSubfolder & ":" & eachName
 
				-- Replace the targets (keeping the times)
 
				if eachType is not "MIDI File" then
					set currentStart to start time of eachCue
					set currentEnd to end time of eachCue
				end if
 
				set file target of eachCue to eachNewTarget
 
				if eachType is not "MIDI File" then
					set start time of eachCue to currentStart
					set end time of eachCue to currentEnd
				end if
 
			end try
 
		end if
 
	end repeat
 
	display dialog "Done." with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 5
 
end tell
 
-- Subroutines
 
(* === FILE WRANGLING === *)
 
on checkForFile(theSuffix, theName) -- [Shared subroutine]
	tell application "System Events"
		return exists file (sharedPath & theSuffix & ":" & theName)
	end tell
end checkForFile
 
on checkForFolder(theSuffix) -- [Shared subroutine]
	tell application "System Events"
		return exists folder (sharedPath & theSuffix)
	end tell
end checkForFolder
 
on makeFolder(theFolder) -- [Shared subroutine]
	tell application "Finder"
		make new folder at sharedPath with properties {name:theFolder}
	end tell
end makeFolder
 
on copyFile(theSuffix, theFile, theName) -- [Shared subroutine]
	do shell script "cp -p " & quoted form of POSIX path of theFile & " " & quoted form of POSIX path of (sharedPath & theSuffix & ":" & theName)
end copyFile

Scan for new files

This script will attempt to recreate the folder structure of a user-specified folder as Group Cues in a user-specified cue list, adding any audio/video files as Audio/Video Cues – unless they already exist. It will fail if the names of subfolders are not unique, or if cues with those names already exist in the wrong place.

set userWatchedFolderIsNextToWorkspace to true -- Change this to false if your watched folder isn't next to the workspace
set userWatchedFolder to "watched" -- Set the name of the watched folder (or change this to a full POSIX path if you change the above to false)
set userWatchedCuelist to "Watched" -- Set the name of the cue list for automatically-generated cues
set userFlagNewCues to true -- Flag any automatically-generated cues?
 
-- ###FIXME### Should audioFileTypes include "public.ulaw-audio"?
-- ###FIXME### Is videoFileTypes a sufficiently exhaustive list? Are any of the file types not supported by QLab?
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Scan for files"
 
set audioFileTypes to {"com.apple.coreaudio-format", "com.apple.m4a-audio", "com.microsoft.waveform-audio", "public.aifc-audio", "public.aiff-audio", ¬
	"public.audio", "public.mp3", "public.mpeg-4-audio"} -- This list deliberately excludes "com.apple.protected-mpeg-4-audio" to protect against
(* old DRM-restricted iTunes files *)
set videoFileTypes to {"com.adobe.photoshop-image", "com.apple.icns", "com.apple.macpaint-image", "com.apple.pict", "com.apple.quicktime-image", ¬
	"com.apple.quicktime-movie", "public.3gpp", "public.3gpp2", "public.avi", "public.camera-raw-image", "public.image", "public.jpeg", "public.jpeg-2000", ¬
	"public.movie", "public.mpeg", "public.mpeg-4", "public.png", "public.tiff", "public.video", "public.xbitmap-image"}
set midiFileTypes to {"public.midi-audio"}
 
(* cf: https://developer.apple.com/library/content/documentation/Miscellaneous/Reference/UTIRef/Articles/System-DeclaredUniformTypeIdentifiers.html *)
 
set theFileTypes to audioFileTypes & videoFileTypes
set cuesAdded to 0
 
-- Main routine
 
tell application id "com.figure53.QLab.4" to tell front workspace
 
	-- Record current selection & cue list to return to if no cues added
 
	set startingSelection to selected
	set startingCueList to current cue list
 
	-- Locate the watched folder
 
	if userWatchedFolderIsNextToWorkspace then
 
		-- Establish the path to the current workspace
 
		set workspacePath to path
		if workspacePath is missing value then
			display dialog "The current workspace has not yet been saved anywhere." with title dialogTitle with icon 0 ¬
				buttons {"OK"} default button "OK" giving up after 5
			return
		end if
 
		-- Get the path to the watched folder
 
		tell application "System Events"
			set watchedFolder to path of container of file workspacePath & userWatchedFolder
		end tell
 
	else
 
		set watchedFolder to userWatchedFolder
 
	end if
 
	-- Check watched folder exists
 
	tell application "System Events" to set folderExists to exists folder watchedFolder
 
	if folderExists is false then
		display dialog "The watched folder \"" & POSIX path of watchedFolder & "\" does not exist." with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK"
		return
	end if
 
	-- Check watched cue list exists
 
	try
		set watchedCuelist to first cue list whose q name is userWatchedCuelist
	on error
		display dialog "The destination cue list \"" & userWatchedCuelist & "\" does not exist." with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK"
		return
	end try
 
	-- Check watched cue list isn't a cart
 
	if q type of watchedCuelist is "Cart" then
		display dialog "The destination cue list \"" & userWatchedCuelist & "\" is a cart, so no Group Cues can be made…" with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK"
		return
	end if
 
	-- Replicate file structure
 
	set theFolders to {POSIX path of watchedFolder}
	set countFolders to count theFolders
	set parentFolders to {}
	set i to 0
 
	set currentTIDs to AppleScript's text item delimiters
	set AppleScript's text item delimiters to "/"
 
	repeat until i = countFolders
 
		set eachFolder to item (i + 1) of theFolders
 
		-- Record the parent of each folder processed
 
		if i is 0 then
			set end of parentFolders to last text item of eachFolder
		else
			set end of parentFolders to text item -2 of eachFolder
		end if
 
		-- Make a list of all Group Cues in the watched cue list
 
		set existingGroups to cues of watchedCuelist whose q type is "Group"
		set countExistingGroups to count existingGroups
		set j to 0
 
		repeat until j = countExistingGroups
			try
				set existingGroups to existingGroups & (cues of item (j + 1) of existingGroups whose q type is "Group")
			end try
			set j to j + 1
			set countExistingGroups to count existingGroups
		end repeat
 
		-- Find first Group Cue whose name matches
 
		set makeNextCueIn to watchedCuelist -- If there is no match, new cues will be added directly to the cue list
		repeat with eachGroup in existingGroups
			if q name of eachGroup is item (i + 1) of parentFolders then
				set makeNextCueIn to eachGroup
				exit repeat
			end if
		end repeat
 
		-- Move the selection to set where the next cue will be made
 
		try
			set selected to last item of (cues of makeNextCueIn)
		on error
			set current cue list to watchedCuelist
		end try
 
		-- Make Group Cues if needed
 
		if i is 0 then
			set currentGroup to watchedCuelist
		else
			set groupName to last text item of eachFolder
			set currentGroup to false
			repeat with eachGroup in existingGroups
				if q name of eachGroup is groupName then
					set currentGroup to eachGroup
					exit repeat
				end if
			end repeat
			if currentGroup is false then
				make type "Group"
				set cuesAdded to cuesAdded + 1
				set newCue to last item of (selected as list)
				if userFlagNewCues then set flagged of newCue to true
				set q name of newCue to groupName
				set currentGroup to newCue
			end if
		end if
 
		-- Make a list of files already used in the current Group Cue
 
		set existingTargets to file target of cues of currentGroup whose broken is false and q type is "Audio" or q type is "Video"
		set usedFiles to {}
		repeat with eachTarget in existingTargets
			set end of usedFiles to POSIX path of eachTarget
		end repeat
 
		-- Get files of folder to be processed
 
		tell application "System Events" to set theFiles to POSIX path of disk items of folder eachFolder whose visible is true
 
		-- Process them: if folder, add to list for processing; if file, make a cue if needed
 
		repeat with eachFile in theFiles
 
			try -- This detects folders
				tell application "System Events"
					set eachType to type identifier of file eachFile
				end tell
				if eachType is in theFileTypes and eachFile is not in usedFiles then
					if eachType is in audioFileTypes then
						make type "Audio"
					else
						make type "Video"
					end if
					set cuesAdded to cuesAdded + 1
					set newCue to last item of (selected as list)
					if userFlagNewCues then set flagged of newCue to true
					set file target of newCue to eachFile
					if parent of newCue is not currentGroup then -- Will need to move first cue made if Group Cue was empty
						set newCueID to uniqueID of newCue
						move cue id newCueID of parent of newCue to end of currentGroup
						set selected to newCue
					end if
				end if
			on error
				set theFolders to theFolders & eachFile
			end try
 
		end repeat
 
		set i to i + 1
		set countFolders to count theFolders
 
	end repeat
 
	set AppleScript's text item delimiters to currentTIDs
 
	-- Report how many cues added
 
	if cuesAdded is 0 then
		set theMessage to "No cues were added."
		delay 1
		set selected to startingSelection
		set current cue list to startingCueList
	else if cuesAdded is 1 then
		set theMessage to "1 cue was added."
	else
		set theMessage to (cuesAdded & " cues were added.") as text
	end if
 
	display dialog theMessage with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 5
 
end tell

Transport

Nudge

Nudge selected cue(s) forward 5s (without changing their running state):

-- Only works properly when run as a separate process!
-- NB: the OSC command will NOT WORK if the workspace has a Passcode
 
set userNudge to 5
 
tell application id "com.figure53.QLab.4" to tell front workspace
	repeat with eachCue in (selected as list)
		if q type of eachCue is not "Script" then -- Protect the script from running on itself
			try
 
				if running of eachCue is true then
					pause eachCue
					set startFlag to true
				else
					set startFlag to false
				end if
 
				(* -- AppleScript method, which inadvertently resets Audio Cues to their programmed levels
				set currentTime to action elapsed of eachCue
				load eachCue time currentTime + userNudge
				*)
 
				set currentTime to ((action elapsed of eachCue) - (pre wait of eachCue)) -- loadActionAt method adds pre wait back to time argument!
				set eachID to uniqueID of eachCue
				tell me to do shell script "echo '/cue_id/" & eachID & "/loadActionAt " & currentTime + userNudge & "' | nc -u -w 0 localhost 53535"
 
				if startFlag is true then
					start eachCue
				end if
 
			end try
		end if
	end repeat
end tell

Toggle pause of all except selected cue(s)

Toggle pause of all except selected cue(s) (the script will include children of selected cues, but not their children: grandchildren will be paused):

tell front workspace
 
	-- Make a list of selected cues, including one layer of children
 
	set selectedIDs to {}
	repeat with eachCue in (selected as list)
		if q type of eachCue is not "Group" then
			set end of selectedIDs to uniqueID of eachCue
		else
			set selectedIDs to selectedIDs & uniqueID of cues of eachCue
		end if
	end repeat
 
	-- Make a list of active cues that aren't Group Cues and aren't selected
 
	set alreadyPaused to false
	set activeIDs to {}
	set activeCues to (active cues as list)
	repeat with eachCue in activeCues
		if q type of eachCue is not "Group" then
			set eachID to uniqueID of eachCue
			if eachID is not in selectedIDs then
				set end of activeIDs to eachID
			end if
		end if
	end repeat
 
	-- If any of the "active cues" are paused, start them and set a flag not to pause any
 
	repeat with eachID in activeIDs
		if paused of cue id eachID is true then
			start cue id eachID
			set alreadyPaused to true
		end if
	end repeat
 
	-- If none of the "active cues" were paused, pause them all
 
	if alreadyPaused is false then
		repeat with eachID in activeIDs
			pause cue id eachID
		end repeat
	end if
 
end tell

Loading

Load nest of cues to waveform cursor

###EXPERIMENTAL###: this script was developed for a specific show, so may not be generally useful. If you click in the waveform of an Audio Cue and run the script it will attempt to load the entire sequence of nested cues you are in to that point in time: useful for choosing a cue point visually. It assumes the sequence hierarchy is entirely “Timeline” Group Cues, although it can cope with the parent of the selected cue being a “Start first child and go to next cue” Group Cue. The load time calculated is copied to the Clipboard for you to paste in next time.

tell front workspace
 
	if q type of current cue list is "Cart" then return -- This will stop the script if we're in a cart, as it doesn't make sense to continue!
 
	try -- This protects against no selection (can't get last item of (selected as list))
 
		set selectedCue to last item of (selected as list)
 
		-- Get cursor position
 
		set loadTime to (pre wait of selectedCue) + (percent action elapsed of selectedCue) * (duration of selectedCue)
		-- ###FIXME### As of 4.4.1, "action elapsed" reports differently between clicking in waveform and loading to time when rate ≠ 1
 
		set parentCue to parent of selectedCue
 
		-- If the cue is in a "Start first child and go to next cue" Group Cue, all the cues before it will need to be loaded too;
		(* this won't detect if the cues before selectedCue won't in fact follow on into it! *)
 
		if mode of parentCue is fire_first_go_to_next_cue then
			repeat with eachChild in cues of parentCue
				if contents of eachChild is selectedCue then exit repeat
				set loadTime to loadTime + (pre wait of eachChild)
				set eachContinueMode to continue mode of eachChild
				if eachContinueMode is auto_continue then
					set loadTime to loadTime + (post wait of eachChild)
				else if eachContinueMode is auto_follow then
					set loadTime to loadTime + (duration of eachChild)
				end if
			end repeat
		end if
 
		-- Go up the hierarchy until you find a Group Cue directly in a cue list
 
		repeat until q type of parent of parentCue is "Cue List" -- This will throw an error if the selected cue is directly in a cue list
			set loadTime to loadTime + (pre wait of parentCue)
			set parentCue to parent of parentCue
		end repeat
 
		set loadTime to loadTime + (pre wait of parentCue) -- Also include the top level cue's Pre Wait
 
		-- Load the cue
 
		stop selectedCue
		set selected to parentCue
		load parentCue time loadTime
 
		-- Copy the load time to the Clipboard
 
		set the clipboard to loadTime as text
 
	end try
 
end tell

Load to time

Load selected cue(s) to a time you enter (this enhances the built-in functionality as it can act on more than one cue at a time; NB: running cues can't be loaded to time):

-- Declarations
 
global dialogTitle
set dialogTitle to "Load to time"
 
-- Check the Clipboard for a previous time
 
try
	set clipboardContents to the clipboard as text -- The time requested previously will have been copied to the Clipboard, and may still be on there
on error
	set clipboardContents to ""
end try
 
if (count paragraphs of clipboardContents) > 1 or (count words of clipboardContents) > 2 or ¬
	((count words of clipboardContents) > 1 and clipboardContents does not contain ":") then -- Slight protection against spurious Clipboard contents
	set clipboardContents to ""
end if
 
-- Prompt to get the time
 
set promptText to "Load selected cues to this time (seconds or minutes:seconds):" & return & return ¬
	& "(You can enter a negative value to specify a time remaining.)"
set {theTime, theOption, inputText} to enterATimeWithIconWithExtraOptionButton(promptText, clipboardContents, "Start the cues too", true, true)
 
-- Copy the input text to the Clipboard
 
set the clipboard to inputText as text
 
-- Load (and start) the cues
 
tell front workspace
	if theTime < 0 then
		repeat with eachCue in (selected as list)
			load eachCue time ((pre wait of eachCue) + (duration of eachCue) + theTime)
		end repeat
	else
		load selected time theTime
	end if
	if theOption is "Start the cues too" then
		start selected
	end if
end tell
 
-- Subroutines
 
(* === INPUT === *)
 
on enterATimeWithIconWithExtraOptionButton(thePrompt, defaultAnswer, extraOptionButton, clearDefaultAnswerAfterFirst, negativeAllowed) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set {theQuestion, theButton} to {text returned, button returned} of (display dialog thePrompt with title dialogTitle with icon 1 ¬
				default answer defaultAnswer buttons (extraOptionButton as list) & {"Cancel", "OK"} default button "OK" cancel button "Cancel")
			if clearDefaultAnswerAfterFirst is true then
				set defaultAnswer to ""
			end if
			try
				set theAnswer to theQuestion as number
				if negativeAllowed is false then
					if theAnswer < 0 then
						set theQuestion to ""
					end if
				end if
			on error
				if theQuestion contains ":" then
					if theQuestion begins with "-" then
						if negativeAllowed is false then
							set theAnswer to false
						else
							set theAnswer to -(my makeSecondsFromM_S(text 2 thru end of theQuestion))
						end if
					else
						set theAnswer to my makeSecondsFromM_S(theQuestion)
					end if
					if theAnswer is false then
						set theQuestion to ""
					end if
				else
					set theQuestion to ""
				end if
			end try
		end repeat
		return {theAnswer, theButton, theQuestion}
	end tell
end enterATimeWithIconWithExtraOptionButton
 
(* === TIME === *)
 
on makeSecondsFromM_S(howLong) -- [Shared subroutine]
	try
		set currentTIDs to AppleScript's text item delimiters
		set AppleScript's text item delimiters to ":"
		set theMinutes to first text item of howLong
		set theSeconds to rest of text items of howLong as text
		set AppleScript's text item delimiters to currentTIDs
		return theMinutes * 60 + theSeconds
	on error
		return false
	end try
end makeSecondsFromM_S

Jump into a string of cues

Jump into a string of cues; the script rather assumes that you are working in such a way that every cue that is triggered by a manual GO is a Group Cue (either “Timeline” or “Start first child and go to next cue”). In addition to Group Cues, it will also process any selected Memo Cues – but it will ignore all other cue types. For best results, select the first Group Cue that should still be playing and all intervening cues up to and including the last Group Cue you wish to jump into, but not any of its children. (Doesn't fire in a cart!)

-- Declarations
 
global dialogTitle
set dialogTitle to "Jump into a string of cues"
 
-- Main routine
 
tell front workspace
 
	-- Check we're not in a cart
 
	if q type of current cue list is "Cart" then return
 
	-- Check more than one cue selected
 
	try
		set selectedCues to items 1 thru -2 of (selected as list)
	on error
		display dialog "You need to select more than one cue!" with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after 5
		return
	end try
 
	-- Check the Clipboard for a previous time
 
	try
		set clipboardContents to the clipboard as text -- The time requested previously will have been copied to the Clipboard, and may still be on there
	on error
		set clipboardContents to ""
	end try
 
	if (count paragraphs of clipboardContents) > 1 or (count words of clipboardContents) > 2 or ¬
		((count words of clipboardContents) > 1 and clipboardContents does not contain ":") then -- Slight protection against spurious Clipboard contents
		set clipboardContents to ""
	end if
 
	-- Prompt to get the time
 
	set promptText to "This script will load the last selected cue to the time you enter below and attempt to load any other Group Cues selected " & ¬
		"so that any fades will effectively have completed when you start the selected cues." & return & return & ¬
		"THIS IS NOT GUARANTEED TO WORK!" & return & return & "Enter the load time (seconds or minutes:seconds):"
	set {theTime, unusedVariable, inputText} to my enterATimeWithIconWithExtraOptionButton(promptText, clipboardContents, {}, true, false)
 
	-- Copy the input text to the Clipboard
 
	set the clipboard to inputText as text
 
	-- Clean out cues that won't be processed, and prepare a list for checking to see if remaining selection contains its own children…
 
	set selectedCuesClean to {}
	set childrenIDs to {}
	repeat with eachCue in selectedCues
		if q type of eachCue is in {"Group", "Memo"} then
			set end of selectedCuesClean to eachCue
			try -- This will fail silently for Memo Cues
				set childrenIDs to childrenIDs & uniqueID of cues of eachCue
			end try
		end if
	end repeat
 
	-- Exit if no cues left to process
 
	if (count selectedCuesClean) is 0 then
		display dialog "Not enough Group or Memo Cues selected to proceed." with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK" giving up after 5
		return
	end if
 
	-- Work out the total time to which to load, and temporarily set each Group/Memo Cue to auto-continue
 
	set longestGroupTime to 0
	set summedWaits to 0
	set processedCues to {}
	set continueModes to {}
 
	repeat with eachCue in selectedCuesClean
 
		try
 
			set groupTime to 0
 
			try
				set eachMode to mode of eachCue
			on error -- Memo Cue
				set eachMode to ""
			end try
 
			-- Skip cues that are children of selected Group Cues
 
			if uniqueID of eachCue is in childrenIDs then
				error -- Skip any selected children of selected Group Cues so as not to process them twice
			end if
 
			(* The total time of a "Timeline" Group Cue – the time to which to load it to "complete" -
			is the sum of the Pre Wait and Duration of the Fade Cue that will take longest to complete *)
 
			if eachMode is timeline then
				set fadeCues to (cues of eachCue whose q type is "Fade")
				set longestChildFadeTime to 0
				repeat with eachChild in fadeCues
					set eachChildFadeTime to (pre wait of eachChild) + (duration of eachChild)
					if eachChildFadeTime > longestChildFadeTime then
						set longestChildFadeTime to eachChildFadeTime
					end if
				end repeat
				set groupTime to longestChildFadeTime
			end if
 
			(* The total time of a "Start first child and go to next cue" Group Cue – the time to which to load it to "complete" -
			is the sum of all the Pre Waits and Post Waits of cues that continue, plus the Duration of the longest Fade Cue *)
 
			if eachMode is fire_first_go_to_next_cue then
				set longestChildFadeTime to 0
				repeat with eachChild in cues of eachCue
					set groupTime to groupTime + (pre wait of eachChild)
					set eachContinueMode to continue mode of eachChild
					if eachContinueMode is auto_continue then
						set groupTime to groupTime + (post wait of eachChild)
						if q type of eachChild is "Fade" then
							set eachChildFadeTime to duration of eachChild
							if eachChildFadeTime > longestChildFadeTime then
								set longestChildFadeTime to eachChildFadeTime
							end if
						end if
					else if eachContinueMode is auto_follow then
						set groupTime to groupTime + (duration of eachChild)
					else
						exit repeat -- No point looking at children after this as they aren't part of the sequence; this child's Pre Wait has been counted
					end if
				end repeat
				set groupTime to groupTime + longestChildFadeTime
			end if
 
			-- Since the Group Cues are being set to auto-continue, loading to the longest of their total times will load them all to "completion"
 
			if groupTime > longestGroupTime then
				set longestGroupTime to groupTime
			end if
 
			(* If any of the Group/Memo Cues have non-zero Pre or Post Waits then these will effectively extend the time to which we have to load,
			so these are summed too *)
 
			set end of processedCues to eachCue
			set end of continueModes to continue mode of eachCue
			set continue mode of eachCue to auto_continue
			set summedWaits to summedWaits + (pre wait of eachCue)
			if contents of eachCue is not last item of selectedCuesClean then
				(* Don't include the penultimate cue's Post wait in the sum as it will be set temporarily to 0 *)
				set summedWaits to summedWaits + (post wait of eachCue)
			end if
 
		end try
 
	end repeat
 
	-- Temporarily change Post Wait of penultimate Group/Memo Cue in selection so that when the string is loaded all other cues will "complete"
 
	set lastPost to post wait of last item of selectedCuesClean
	set post wait of last item of selectedCuesClean to longestGroupTime + summedWaits
 
	-- Load the cue string and prompt to start it
 
	load first item of selectedCuesClean time longestGroupTime + summedWaits + theTime -- This includes the load to time specified by the user in the dialog
 
	try -- This try means that the rest of the script will complete even if the user cancels
		display dialog "Ready to go?" with title dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel"
		start first item of selectedCuesClean
	end try
 
	-- Reset the cues
 
	repeat with i from 1 to count processedCues
		set continue mode of item i of processedCues to item i of continueModes
	end repeat
	set post wait of last item of selectedCuesClean to lastPost
 
end tell
 
-- Subroutines
 
(* === INPUT === *)
 
on enterATimeWithIconWithExtraOptionButton(thePrompt, defaultAnswer, extraOptionButton, clearDefaultAnswerAfterFirst, negativeAllowed) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set {theQuestion, theButton} to {text returned, button returned} of (display dialog thePrompt with title dialogTitle with icon 1 ¬
				default answer defaultAnswer buttons (extraOptionButton as list) & {"Cancel", "OK"} default button "OK" cancel button "Cancel")
			if clearDefaultAnswerAfterFirst is true then
				set defaultAnswer to ""
			end if
			try
				set theAnswer to theQuestion as number
				if negativeAllowed is false then
					if theAnswer < 0 then
						set theQuestion to ""
					end if
				end if
			on error
				if theQuestion contains ":" then
					if theQuestion begins with "-" then
						if negativeAllowed is false then
							set theAnswer to false
						else
							set theAnswer to -(my makeSecondsFromM_S(text 2 thru end of theQuestion))
						end if
					else
						set theAnswer to my makeSecondsFromM_S(theQuestion)
					end if
					if theAnswer is false then
						set theQuestion to ""
					end if
				else
					set theQuestion to ""
				end if
			end try
		end repeat
		return {theAnswer, theButton, theQuestion}
	end tell
end enterATimeWithIconWithExtraOptionButton
 
(* === TIME === *)
 
on makeSecondsFromM_S(howLong) -- [Shared subroutine]
	try
		set currentTIDs to AppleScript's text item delimiters
		set AppleScript's text item delimiters to ":"
		set theMinutes to first text item of howLong
		set theSeconds to rest of text items of howLong as text
		set AppleScript's text item delimiters to currentTIDs
		return theMinutes * 60 + theSeconds
	on error
		return false
	end try
end makeSecondsFromM_S

Mark/Jump

Mark selected cue, or jump to previously marked cue (the script uses a property to store information about the marked cue: if there is no selection, the script will jump to the marked cue; if no cue has been marked, the script will mark the selected cue; if there is a selection and a cue has previously been marked, the script will ask what to do next):

-- This script can not be run as a separate process as this creates a new instance each time, resetting the property used to store the marked cue
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Mark/Jump"
property storedCue : false
 
-- Main routine
 
tell front workspace
	set selectedCues to (selected as list)
	if (count selectedCues) is 0 then -- There is no selected cue: we are jumping
		if storedCue is not false then
			my jumpToCue()
		end if
	else
		if storedCue is false then -- There is no stored cue: we are marking
			my markCue(last item of selectedCues)
		else -- There is a stored cue, but we'll check what is required
			set theButton to button returned of (display dialog "Jump to stored cue?" with title dialogTitle with icon 1 ¬
				buttons {"Mark", "OK"} default button "OK")
			if theButton is "OK" then -- We are jumping
				my jumpToCue()
			else -- We are marking
				my markCue(last item of selectedCues)
			end if
		end if
	end if
end tell
 
-- Subroutines
 
(* === PROCESSING === *)
 
on markCue(cueToMark)
	tell front workspace
		set storedCue to cueToMark
	end tell
end markCue
 
on jumpToCue()
	tell front workspace
		try
			set selected to storedCue -- This will throw an error if the cue has been deleted
		on error
			set storedCue to false -- Clear the stored cue if it wasn't found
		end try
	end tell
end jumpToCue

Next broken cue

Go to next broken cue (can be adjusted to search for other properties – although for the case “loaded is true”, the script inevitably finds itself, and for the case “running is true”, the script finds itself and its parent!):

tell front workspace
 
	set foundCues to uniqueID of cues whose q type is not "Cue List" and q type is not "Cart" and ¬
		broken is true -- Change this line to search for different properties, eg: q type is "Audio"
 
	set foundCuesRef to a reference to foundCues
	set countFoundCues to count foundCuesRef
 
	if countFoundCues is 0 then -- No cues match the search
		return
	end if
 
	-- Find where we are in the list of all cues
 
	try
 
		set currentCue to uniqueID of last item of (selected as list) -- This will throw an error if there is no selection
 
		if currentCue is in foundCuesRef then
 
			set currentIndex to null -- Bypass iterative searches if selected cue is in "found" cues
 
		else
 
			set allCues to uniqueID of cues
			set allCuesRef to a reference to allCues
			set countCues to count allCuesRef
 
			repeat with i from 1 to countCues
				if item i of allCuesRef is currentCue then
					set currentIndex to i -- Position of selected cue
					exit repeat
				end if
			end repeat
 
		end if
 
	on error
 
		set currentIndex to 0 -- No selection; start search at beginning
 
		set allCues to uniqueID of cues
		set allCuesRef to a reference to allCues
		set countCues to count allCuesRef
 
	end try
 
	-- Find the next "found" cue in the list of all cues
 
	if currentIndex is null then
 
		repeat with i from 1 to countFoundCues
			if item i of foundCuesRef is currentCue then
				set foundID to item (i mod countFoundCues + 1) of foundCuesRef -- The mod makes the search wrap around
				exit repeat
			end if
		end repeat
 
	else
 
		set foundID to null
 
		repeat with i from (currentIndex mod countCues + 1) to countCues -- The mod protects against selected cue being the last in the workspace
			if item i of allCuesRef is in foundCuesRef then
				set foundID to item i of allCuesRef
				exit repeat
			end if
		end repeat
 
		if foundID is null then -- Need to wrap around as no cue found
			repeat with i from 1 to currentIndex
				if item i of allCuesRef is in foundCuesRef then
					set foundID to item i of allCuesRef
					exit repeat
				end if
			end repeat
		end if
 
	end if
 
	-- Move the selection
 
	set selected to cue id foundID
 
end tell

Next cue which shares Cue Target

Go to next cue with the same Cue Target as the selected cue – or which targets the selected cue itself, if it doesn't have a Cue Target:

tell front workspace
 
	try -- This protects against no selection (can't get last item of (selected as list))
 
		set selectedCue to last item of (selected as list)
 
		set targetCue to cue target of selectedCue
		if targetCue is missing value then
			set targetCue to selectedCue
		end if
 
		set foundCues to (uniqueID of cues whose cue target is targetCue) & uniqueID of targetCue -- Include the shared Cue Target itself
 
		set foundCuesRef to a reference to foundCues
		set countFoundCues to count foundCuesRef
 
		-- Find where we are in the list of all cues
 
		set currentCue to uniqueID of selectedCue
 
		-- Find the next "found" cue in the list of all cues
 
		repeat with i from 1 to countFoundCues
			if item i of foundCuesRef is currentCue then
				set foundID to item (i mod countFoundCues + 1) of foundCuesRef -- The mod makes the search wrap around
				exit repeat
			end if
		end repeat
 
		-- Move the selection
 
		set selected to cue id foundID
 
	end try
 
end tell

Present a popup list of cues with the same Cue Target as the selected cue – or which target the selected cue itself, if it doesn't have a Cue Target:

set userPreselectNextCue to true -- Set this to false to preselect the selected cue in the popup list
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Navigate cues with shared Cue Target"
 
-- Main routine
 
tell front workspace
 
	try -- This protects against no selection (can't get last item of (selected as list))
 
		set selectedCue to last item of (selected as list)
 
		set targetCue to cue target of selectedCue
		if targetCue is missing value then
			set targetCue to selectedCue
		end if
 
		set threadCues to {targetCue} & (cues whose cue target is targetCue) -- Only form of this query that doesn't throw an error (brackets essential)!
		set threadCount to count threadCues
		if threadCount is 1 then -- Short thread!
			return
		end if
 
		set threadNames to {q list name of targetCue} & q list name of (cues whose cue target is targetCue)
		set threadTypes to {q type of targetCue} & q type of (cues whose cue target is targetCue)
		set threadNumbers to {q number of targetCue} & q number of (cues whose cue target is targetCue)
 
		-- Make the popup list
 
		set threadList to {}
		set cueListDetector to cue id "[root group of cue lists]" -- Cue lists have this as their parent
		repeat with i from 1 to threadCount
			set eachCue to item i of threadCues
			set eachName to item i of threadNames
			set eachType to item i of threadTypes
			set eachNumber to item i of threadNumbers
			if eachNumber is "" then
				set eachParent to parent of eachCue
				if parent of eachParent is not cueListDetector then -- eachCue's parent is not a cue list
					repeat until parent of eachParent's parent is cueListDetector -- Go up the hierarchy until you find a Group Cue directly in a cue list
						set eachParent to parent of eachParent
					end repeat
					set eachNumber to q number of eachParent
					if eachNumber is not "" then
						set eachNumber to "{" & eachNumber & "}" -- "Inherited" Cue Number from enclosing group(s)
					end if
				end if
			end if
			set end of threadList to (eachNumber & tab & eachType & tab & eachName) -- ###FIXME### This can be ugly…
		end repeat
 
		-- Display the list, including pre-selecting the cue after the selected cue (or the selected cue, depending on value of userPreselectNextCue)
 
		repeat with i from 1 to threadCount
			if selectedCue is item i of threadCues then
				if userPreselectNextCue is true then
					set preSelect to (i mod threadCount) + 1
				else
					set preSelect to i
				end if
				exit repeat
			end if
		end repeat
 
		set chosenCue to my pickFromListCustomDefault(threadList, "Please choose the cue to jump to:", preSelect)
 
		-- Convert the answer to a cue
 
		repeat with i from 1 to threadCount
			if chosenCue is item i of threadList then
				set jumpToCue to item i of threadCues
				exit repeat
			end if
		end repeat
 
		-- Move the selection
 
		set selected to jumpToCue
 
	end try
 
end tell
 
-- Subroutines
 
(* === INPUT === *)
 
on pickFromListCustomDefault(theChoice, thePrompt, theDefault) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		choose from list theChoice with prompt thePrompt with title dialogTitle default items item theDefault of theChoice
		if result is not false then
			return item 1 of result
		else
			error number -128
		end if
	end tell
end pickFromListCustomDefault

View

Switch cue lists

Switch to the cue list called “Main Cue List”:

set userCueList to "Main Cue List" -- Use this to specify the name of the cue list
 
tell front workspace
	set current cue list to first cue list whose q name is userCueList
end tell

Switch cue lists without losing playhead

Switch to the cue list called “Sub Cue List”; the script will attempt to navigate to the cue that would be fired by the next GO targeting this list (useful for secondary cue lists that are fired by an independent GO button or hotkey):

set userCueList to "Sub Cue List" -- Use this to specify the name of the cue list
 
tell front workspace
	set current cue list to first cue list whose q name is userCueList
	try
		set selected to playback position of current cue list
	end try
end tell

Not hotkeys

Macros for one-off processes (eg: making a soundcheck, reporting, etc).

Batch adjusting

Batch modify OSC cues for localisation issues

Localise imported OSC cues if you use “,” rather than “.” for the decimal point:

set userSearchFor to "." -- Number separator to be replaced
set userReplaceWith to "," -- Replacement value
 
tell application id "com.figure53.QLab.4" to tell front workspace
	set vulnerableCues to cues whose q type is "Network" and custom message contains userSearchFor
	set currentTIDs to AppleScript's text item delimiters
	repeat with eachCue in vulnerableCues
		set AppleScript's text item delimiters to userSearchFor
		set eachMessage to custom message of eachCue
		set eachList to text items of eachMessage
		set AppleScript's text item delimiters to userReplaceWith
		set custom message of eachCue to eachList as text
	end repeat
	set AppleScript's text item delimiters to currentTIDs
end tell

Set every cue to auto-load

Set every cue in the workspace to auto-load:

/cue/*/autoLoad 1

Making

Make a soundcheck sequence

Create a Group Cue containing a sequence of soundcheck cues, made from an Audio Cue you choose:

-- Best run as a separate process so it can be happening in the background
 
set userSoundcheckList to "Soundcheck" -- Use this to set the name of the cue list in which to search for the root Audio Cue
set userMinVolume to -120 -- Set what level you mean by "faded out" (you can adjust this to match the workspace "Min Volume Limit" if necessary)
 
set userVerboseMode to true -- Set this to false to use the next 4 user settings below without any dialogs
set userNumberOfOutputsToCheck to 32 -- Set your preferred option for how many outputs to check – confirmed by dialog
set userCrossfadeDuration to 1 -- Set your preferred option for how long the crossfades should be – confirmed by dialog
set userStartSequenceWithFadeIn to true -- Set your preferred option for fading in at the start – confirmed by dialog
set userAutomaticFollowOnTime to "" -- Set the time spent at each output (or "" for no follow-ons) – confirmed by dialog
 
set userDefaultCueNumberForSoundcheckCue to "999" -- Set the Cue Number for the soundcheck cue (or "" for workspace default)
 
set userStartCueName to "Start soundcheck" -- Set the name for the first cue
set userFadeInCueName to "Fade in output " -- Set the name for the fade-in cue, if used
set userMoveCuesBaseName to "Move to output " -- Set the base name for the move cues
set userMoveCuesBaseNotes to "You are listening to output " -- Set the base Notes for the move cues
set userFollowOnCueNames to {"     Automatic follow-on…", "     …Automatic follow-on"} -- Set the names for the cues used to create the follow-ons, if used
set userStopCueName to "Stop soundcheck" -- Set the name for the final cue
 
-- Explanations
 
set theExplanation to "This script will create a Group Cue containing a sequence of soundcheck cues made from an Audio Cue you choose from the \"" & ¬
	userSoundcheckList & "\" cue list (the Audio Cue needs to be directly in the cue list, not inside a Group Cue). " & ¬
	"The soundcheck will step through one output at a time via a sequence of default crossfades.
 
Before running the script you should configure the Audio Cue to play at the desired levels out of all the outputs you wish to check, " & ¬
	"as these levels will be used in the fades. The script will not adjust crosspoints.
 
Several additional user settings can be adjusted by editing the script itself."
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Make a soundcheck sequence"
 
set qLabMaxAudioChannels to 64
 
-- Preamble
 
tell application id "com.figure53.QLab.4" to tell front workspace
 
	-- Display introductory dialog, if in verbose mode
 
	if userVerboseMode is true then
		display dialog theExplanation with title dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel"
	end if
 
	-- Exit if userSoundcheckList doesn't exist
 
	try
		set soundcheckList to first cue list whose q name is userSoundcheckList
	on error
		display dialog "The cue list \"" & userSoundcheckList & "\" can not be found…" with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK" giving up after 5
		return
	end try
 
	-- Exit if userSoundcheckList is a cart
 
	try
		set notCaseSensitiveMatch to q name of soundcheckList
		if q type of soundcheckList is "Cart" then
			display dialog "The destination cue list \"" & notCaseSensitiveMatch & "\" is a cart, so no Group Cues can be made…" with title dialogTitle ¬
				with icon 0 buttons {"OK"} default button "OK"
			return
		end if
	end try
 
	-- Exit if there aren't any Audio Cues in userSoundcheckList
 
	set possibleCues to cues of soundcheckList whose q type is "Audio" and broken is false
	set countPossibleCues to count possibleCues
 
	if countPossibleCues is 0 then
		display dialog "No Audio Cues found directly in the \"" & userSoundcheckList & "\" cue list." with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK" giving up after 5
		return
	end if
 
	-- Check to see if any possible Audio Cues have Cue Numbers, and prepare the choose from list dialog accordingly
 
	set possibleCueNames to q list name of cues of soundcheckList whose q type is "Audio" and broken is false
	set possibleCueNumbers to q number of cues of soundcheckList whose q type is "Audio" and broken is false
 
	set currentTIDs to AppleScript's text item delimiters
	set AppleScript's text item delimiters to "" -- Should not assume this
	if possibleCueNumbers as text is not "" then
		repeat with i from 1 to countPossibleCues
			set item i of possibleCueNames to (item i of possibleCueNumbers & tab & item i of possibleCueNames) as text
		end repeat
	end if
	set AppleScript's text item delimiters to currentTIDs
 
	-- Choose which Audio Cue
 
	set theAudioCueName to my pickFromList(possibleCueNames, "Please choose the Audio Cue:")
 
	repeat with i from 1 to countPossibleCues
		if theAudioCueName is item i of possibleCueNames then
			set theAudioCue to item i of possibleCues
			exit repeat
		end if
	end repeat
 
	-- Get all output levels from the Audio Cue (except Master)
 
	set allOutCheck to true
	set originalCueLevels to {}
	repeat with i from 1 to qLabMaxAudioChannels
		set thisOutputLevel to theAudioCue getLevel row 0 column i
		if thisOutputLevel is not userMinVolume then
			set allOutCheck to false
		end if
		set end of originalCueLevels to thisOutputLevel
	end repeat
 
	-- Exit if there are no levels set in the Audio Cue
 
	if allOutCheck is true then
		display dialog "The last selected Audio Cue has all its individual output levels set to \"-INF\".
 
It makes no sense to proceed…" with title dialogTitle with icon 0 buttons {"OK"} default button "OK"
		return
	end if
 
	-- Skip the dialogs if userVerboseMode is false
 
	if userVerboseMode is false then
 
		set howManyOutputs to userNumberOfOutputsToCheck
		set howLong to userCrossfadeDuration
		set fadeIn to userStartSequenceWithFadeIn
		set followOn to userAutomaticFollowOnTime
 
	else
 
		-- Prompt for how many outputs to test
 
		set howManyOutputs to my enterANumberWithRangeWithCustomButton("How many outputs do you wish to check?", userNumberOfOutputsToCheck, ¬
			2, true, qLabMaxAudioChannels, true, true, {}, 2)
 
		-- Prompt for length of crossfades
 
		set howLong to my enterANumberWithRangeWithCustomButton("Enter a duration for the crossfades (in seconds):", userCrossfadeDuration, ¬
			0, true, false, false, false, {}, 2)
 
		-- Prompt for whether there should be a fade in at the start
 
		if userStartSequenceWithFadeIn is true then
			set fadeButtons to {"Cancel", "No", "Yes"}
		else
			set fadeButtons to {"Cancel", "Yes", "No"}
		end if
		set fadeQuestion to button returned of (display dialog "Would you like the sequence to start by fading in?" with title dialogTitle with icon 1 ¬
			buttons fadeButtons default button 3 cancel button 1)
		if fadeQuestion is "Yes" then
			set fadeIn to true
		else
			set fadeIn to false
		end if
 
		-- Prompt for whether the sequence should automatically follow-on
 
		set followOnMessage to "Set the time spent at each output (in seconds):"
		if userAutomaticFollowOnTime is "" then
			set followOnDefault to 1
			set followOnMessage to followOnMessage & return & return & "(You'll need to click \"OK\" to enter a time, not press return.)"
			-- ###FIXME### This could be more elegant: use text returned to test for an entry
		else
			set followOnDefault to 3
		end if
 
		set followOn to my enterANumberWithRangeWithCustomButton(followOnMessage, userAutomaticFollowOnTime, ¬
			0, true, false, false, false, "No follow-ons", followOnDefault)
 
		if followOn is "No follow-ons" then
			set followOn to ""
		end if
 
	end if
 
	-- The bit of the script that actually does the work starts here…
 
	display dialog "One moment caller…" with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 1
 
	-- Switch to userSoundcheckList
 
	set current cue list to soundcheckList
 
	-- Make a new Group Cue for the sequence: after the Audio Cue
 
	set selected to theAudioCue -- This also protects against the default behaviour of grouping selection if more than 2 selected
	make type "Group"
	set theGroupCue to last item of (selected as list)
	set mode of theGroupCue to fire_first_enter_group
	set q name of theGroupCue to userStartCueName
	if userDefaultCueNumberForSoundcheckCue is not "" then
		set q number of theGroupCue to userDefaultCueNumberForSoundcheckCue
	end if
 
	-- Move the Audio Cue inside the Group Cue
 
	set theAudioCueID to uniqueID of theAudioCue
	set theAudioCueIsIn to parent of theAudioCue
	set theGroupCueID to uniqueID of theGroupCue
	move cue id theAudioCueID of theAudioCueIsIn to end of cue id theGroupCueID
	set selected to theAudioCue -- The Group Cue was the last selection, so we need to select a cue inside the group before making the fades
 
	-- Set outputs 2 & above to -INF (do all the outputs regardless of userNumberOfOutputsToCheck so there's no unexpected audio from higher output numbers)
 
	set outputOneGang to getGang theAudioCue row 0 column 1
	if outputOneGang is not missing value then
		setGang theAudioCue row 0 column 1 gang "" -- Temporarily override gang for output 1 (it affects setLevel on the Audio Cue, but not on any fades)
	end if
 
	repeat with i from 2 to qLabMaxAudioChannels
		theAudioCue setLevel row 0 column i db userMinVolume
	end repeat
 
	if outputOneGang is not missing value then
		setGang theAudioCue row 0 column 1 gang outputOneGang
	end if
 
	-- Create fade in, if necessary
 
	if fadeIn is true then
		theAudioCue setLevel row 0 column 1 db userMinVolume
		set continue mode of theAudioCue to auto_continue
		make type "Fade"
		set newCue to last item of (selected as list)
		set cue target of newCue to theAudioCue
		set duration of newCue to howLong
		set newCueLevels to item 1 of originalCueLevels
		newCue setLevel row 0 column 1 db newCueLevels
		if followOn is "" then
			set q name of newCue to userFadeInCueName & "1"
		else
			my makeCrashableWait(newCue, followOn, true, ¬
				userFadeInCueName & "1", item 2 of userFollowOnCueNames, userMoveCuesBaseName & "2", userMoveCuesBaseNotes & "1")
		end if
	else
		if followOn is "" then
			set continue mode of theAudioCue to do_not_continue
		else
			my makeCrashableWait(theAudioCue, followOn, false, ¬
				false, item 2 of userFollowOnCueNames, userMoveCuesBaseName & "2", userMoveCuesBaseNotes & "1")
		end if
	end if
 
	-- Make fades
 
	repeat with i from 2 to howManyOutputs
		make type "Fade"
		set newCue to last item of (selected as list)
		set cue target of newCue to theAudioCue
		set duration of newCue to howLong
		set newCueLevels to item i of originalCueLevels
		newCue setLevel row 0 column i db newCueLevels
		newCue setLevel row 0 column (i - 1) db userMinVolume
		if followOn is "" then
			set q name of newCue to userMoveCuesBaseName & i
			set notes of newCue to userMoveCuesBaseNotes & (i - 1)
		else if i < howManyOutputs then
			my makeCrashableWait(newCue, followOn, true, ¬
				item 1 of userFollowOnCueNames, item 2 of userFollowOnCueNames, userMoveCuesBaseName & (i + 1), userMoveCuesBaseNotes & i)
		else
			my makeCrashableWait(newCue, followOn, true, ¬
				item 1 of userFollowOnCueNames, item 2 of userFollowOnCueNames, userStopCueName, userMoveCuesBaseNotes & i)
		end if
	end repeat
 
	-- Make final fade out
 
	make type "Fade"
	set newCue to last item of (selected as list)
	set cue target of newCue to theAudioCue
	set duration of newCue to howLong
	newCue setLevel row 0 column howManyOutputs db userMinVolume
	set stop target when done of newCue to true
	if followOn is "" then
		set q name of newCue to userStopCueName
		set notes of newCue to userMoveCuesBaseNotes & howManyOutputs
	else
		set q name of newCue to item 2 of userFollowOnCueNames
	end if
 
	activate
	display dialog "Done." with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 60
 
end tell
 
-- Subroutines
 
(* === INPUT === *)
 
on enterANumberWithRangeWithCustomButton(thePrompt, defaultAnswer, ¬
	lowRange, acceptEqualsLowRange, highRange, acceptEqualsHighRange, integerOnly, customButton, defaultButton) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		set theQuestion to ""
		repeat until theQuestion is not ""
			set {theQuestion, theButton} to {text returned, button returned} of (display dialog thePrompt with title dialogTitle ¬
				default answer defaultAnswer buttons (customButton as list) & {"Cancel", "OK"} default button defaultButton cancel button "Cancel")
			if theButton is customButton then
				set theAnswer to theButton
				exit repeat
			end if
			try
				if integerOnly is true then
					set theAnswer to theQuestion as integer -- Detects non-numeric strings
					if theAnswer as text is not theQuestion then -- Detects non-integer input
						set theQuestion to ""
					end if
				else
					set theAnswer to theQuestion as number -- Detects non-numeric strings
				end if
				if lowRange is not false then
					if acceptEqualsLowRange is true then
						if theAnswer < lowRange then
							set theQuestion to ""
						end if
					else
						if theAnswer ≤ lowRange then
							set theQuestion to ""
						end if
					end if
				end if
				if highRange is not false then
					if acceptEqualsHighRange is true then
						if theAnswer > highRange then
							set theQuestion to ""
						end if
					else
						if theAnswer ≥ highRange then
							set theQuestion to ""
						end if
					end if
				end if
			on error
				set theQuestion to ""
			end try
		end repeat
		return theAnswer
	end tell
end enterANumberWithRangeWithCustomButton
 
on pickFromList(theChoice, thePrompt) -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		choose from list theChoice with prompt thePrompt with title dialogTitle default items item 1 of theChoice
		if result is not false then
			return item 1 of result
		else
			error number -128
		end if
	end tell
end pickFromList
 
(* === PROCESSING === *)
 
on makeCrashableWait(originalCue, waitTime, autoFollow, originalCueName, startCueName, stopCueName, stopCueNotes)
	tell application id "com.figure53.QLab.4" to tell front workspace
		if autoFollow is false then
			set continue mode of originalCue to auto_continue
		else
			set continue mode of originalCue to auto_follow
		end if
		if originalCueName is not false then
			set q name of originalCue to originalCueName
		end if
		make type "Start"
		set newStartCue to last item of (selected as list)
		set cue target of newStartCue to current cue list
		set pre wait of newStartCue to waitTime
		set q name of newStartCue to startCueName
		make type "Stop"
		set newStopCue to last item of (selected as list)
		set cue target of newStopCue to newStartCue
		set q name of newStopCue to stopCueName
		set notes of newStopCue to stopCueNotes -- The "crashable wait" pauses on the Stop Cue
		set continue mode of newStopCue to auto_continue
	end tell
end makeCrashableWait

Reporting

Log event to file

###EXPERIMENTAL###

Fire this script with a Start Cue with a 1s Post Wait – the name of the Start Cue will be logged to a text file along with the time of day:

-- Best run as a separate process
 
set userLogsFolder to false -- Default behaviour is to create log files on the Desktop; to override, enter a path to a different folder here (it needs to exist)
(* The easiest way to get the path is to select the folder in the Finder, press alt-cmd-C and paste the resulting path as a "string" above, replacing 'false' *)
 
set userCommonLogFile to false -- Default behaviour is to write a new log file each day and log workspaces in separate files;
(* if you want to have a single ongoing file that logs all workspaces, change this variable to the name of the file, eg: "QLab Log" *)
 
set userFileText to " | Logged Events | " -- Set the text to be inserted into the filename between workspace name and today's date, if in that default mode
 
-- Get cue that started this Script Cue and get the time
 
tell application id "com.figure53.QLab.4" to tell front workspace
	try
		set startCueName to q list name of item -3 of (active cues as list) -- This bit only works if this script is inside a Group Cue!
	on error
		set startCueName to false
	end try
end tell
 
set logTime to do shell script "date '+%d-%m-%y %T'"
 
-- Declarations
 
global dialogTitle
set dialogTitle to "Log event to file"
 
-- Check userLogsFolder exists
 
if userLogsFolder is false then -- Default to the Desktop
	set logsFolder to path to desktop
else
	try
		tell application "System Events"
			set logsFolder to path of folder userLogsFolder -- This will throw an error if the folder doesn't exist
		end tell
	on error
		display dialog "The folder \"" & userLogsFolder & "\" can not be accessed; please revisit userLogsFolder variable." with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK"
		return
	end try
end if
 
-- Get info for the log
 
tell application id "com.figure53.QLab.4" to tell front workspace
	set workspaceName to q number
	if startCueName is false then
		set theMessage to "**** Start Cue name not captured ****"
	else
		set theMessage to startCueName
	end if
	if userCommonLogFile is not false then
		set theMessage to workspaceName & tab & theMessage
	end if
end tell
 
-- Get variables for file naming
 
set fileDate to do shell script "date '+%y-%m-%d'"
 
if userCommonLogFile is false then
	set theFilePath to POSIX path of logsFolder & workspaceName & userFileText & fileDate & ".txt"
else
	set theFilePath to POSIX path of logsFolder & userCommonLogFile & ".txt"
end if
 
-- Write to the log	
 
do shell script "echo " & logTime & tab & quoted form of theMessage & " >> " & quoted form of theFilePath

Make a list of media files

Make a text file on the Desktop with a list of all the media files used as targets in the current workspace:

-- Best run as a separate process so it can be happening in the background
 
-- Explanations
 
set theExplanation to "This script creates a text file on the Desktop with a list of all the media files used as targets in the current workspace, " & ¬
	"removing duplicates and sorting alphabetically. It then opens the file in TextEdit."
 
-- Declarations
 
global dialogTitle, startTime
set dialogTitle to "Make a list of media files"
 
-- Preamble
 
tell application id "com.figure53.QLab.4"
 
	display dialog theExplanation & return & return & "It may take a little while to run; are you sure you wish to proceed?" with title dialogTitle with icon 1 ¬
		buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel"
 
	-- Prepare variables for filename
 
	set fileTime to my grabTimeForFilename()
	set workspaceName to q number of front workspace -- This actually gets the name of the workspace
 
	my startTheClock()
 
	-- Extract array of File Targets from QLab, skipping duplicates
 
	set mediaFiles to {}
	set mediaFilesRef to a reference to mediaFiles
 
	tell front workspace
 
		-- First, the easy ones
 
		set validTargets to file target of cues whose broken is false and (q type is "Audio" or q type is "Video" or q type is "MIDI File")
		set countValidTargets to count validTargets
 
		repeat with i from 1 to countValidTargets
 
			set eachTarget to item i of validTargets
			set targetFile to POSIX path of alias eachTarget -- Convert to POSIX
			if targetFile is not in mediaFilesRef then
				set end of mediaFilesRef to targetFile
			end if
 
			if i mod 100 is 0 and (countValidTargets - i) > 50 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countValidTargets, "valid Audio, Video or MIDI File Cues")
			end if
 
		end repeat
 
		-- Now, broken cues
 
		set brokenCues to cues whose broken is true and (q type is "Audio" or q type is "Video" or q type is "MIDI File")
		set countBrokenCues to count brokenCues
 
		repeat with i from 1 to countBrokenCues
 
			set eachCue to item i of brokenCues
			set eachTarget to file target of eachCue
			if eachTarget is missing value then -- This will be returned by cues whose targets have become invalid
				set eachName to q display name of eachCue
				set targetFile to "**** Missing file target for cue named \"" & eachName & "\" ****"
			else
				set targetFile to POSIX path of alias eachTarget & " [BROKEN]"
			end if
			if targetFile is not in mediaFilesRef then
				set end of mediaFilesRef to targetFile
			end if
 
			if i mod 100 is 0 and (countBrokenCues - i) > 50 then -- Countdown timer (and opportunity to escape)
				my countdownTimer(i, countBrokenCues, "broken Audio, Video or MIDI File Cues")
			end if
 
		end repeat
 
	end tell
 
	-- Check there are some files
 
	if (count mediaFilesRef) is 0 then
		activate
		display dialog "No media files were found!" with title dialogTitle with icon 0 ¬
			buttons {"OK"} default button "OK" giving up after 60
		return
	end if
 
end tell
 
-- Convert the list to text and sort it
 
set currentTIDs to AppleScript's text item delimiters
set AppleScript's text item delimiters to linefeed
set theText to mediaFilesRef as text
set AppleScript's text item delimiters to currentTIDs
 
set sortedText to sortTextIgnoringCase(theText)
 
-- Create a string of the full path of the text file to be created
 
set newFile to "" & (path to desktop) & "QLab | " & workspaceName & " | Media files in use | " & fileTime & ".txt"
 
-- Make the file
 
makeFileFromText(newFile, sortedText)
 
-- Open it in TextEdit
 
tell application "TextEdit"
	activate
	open file newFile
	set zoomed of front window to true
end tell
 
finishedDialogBespoke()
 
-- Subroutines
 
(* === OUTPUT === *)
 
on startTheClock() -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		display dialog "One moment caller…" with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 1
	end tell
	set startTime to current date
end startTheClock
 
on countdownTimer(thisStep, totalSteps, whichCuesString) -- [Shared subroutine]
	set timeTaken to round (current date) - startTime rounding as taught in school
	set timeString to my makeMSS(timeTaken)
	tell application id "com.figure53.QLab.4"
		if frontmost then
			display dialog "Time elapsed: " & timeString & " – " & thisStep & " of " & totalSteps & " " & whichCuesString & ¬
				" done…" with title dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 1
		end if
	end tell
end countdownTimer
 
on finishedDialogBespoke()
	set timeTaken to round (current date) - startTime rounding as taught in school
	set timeString to my makeNiceT(timeTaken)
	tell application "TextEdit"
		activate
		display dialog "Done." & return & return & "(That took " & timeString & ".)" with title dialogTitle with icon 1 ¬
			buttons {"OK"} default button "OK" giving up after 60
	end tell
end finishedDialogBespoke
 
(* === TIME === *)
 
on makeMSS(howLong) -- [Shared subroutine]
	set howManyMinutes to howLong div 60
	set howManySeconds to howLong mod 60 div 1
	return (howManyMinutes as text) & ":" & my padNumber(howManySeconds, 2)
end makeMSS
 
on makeNiceT(howLong) -- [Shared subroutine]
	if howLong < 1 then
		return "less than a second"
	end if
	set howManyHours to howLong div 3600
	if howManyHours is 0 then
		set hourString to ""
	else if howManyHours is 1 then
		set hourString to "1 hour"
	else
		set hourString to (howManyHours as text) & " hours"
	end if
	set howManyMinutes to howLong mod 3600 div 60
	if howManyMinutes is 0 then
		set minuteString to ""
	else if howManyMinutes is 1 then
		set minuteString to "1 minute"
	else
		set minuteString to (howManyMinutes as text) & " minutes"
	end if
	set howManySeconds to howLong mod 60 div 1
	if howManySeconds is 0 then
		set secondString to ""
	else if howManySeconds is 1 then
		set secondString to "1 second"
	else
		set secondString to (howManySeconds as text) & " seconds"
	end if
	set theAmpersand to ""
	if hourString is not "" then
		if minuteString is not "" and secondString is not "" then
			set theAmpersand to ", "
		else if minuteString is not "" or secondString is not "" then
			set theAmpersand to " and "
		end if
	end if
	set theOtherAmpersand to ""
	if minuteString is not "" and secondString is not "" then
		set theOtherAmpersand to " and "
	end if
	return hourString & theAmpersand & minuteString & theOtherAmpersand & secondString
end makeNiceT
 
(* === TEXT WRANGLING === *)
 
on padNumber(theNumber, minimumDigits) -- [Shared subroutine]
	set paddedNumber to theNumber as text
	repeat while (count paddedNumber) < minimumDigits
		set paddedNumber to "0" & paddedNumber
	end repeat
	return paddedNumber
end padNumber
 
on sortTextIgnoringCase(theText) -- [Shared subroutine]
	return do shell script "echo " & quoted form of theText & " | sort -f "
end sortTextIgnoringCase
 
(* === FILES === *)
 
on grabTimeForFilename() -- [Shared subroutine]
	return do shell script "date '+%y-%m-%d %H%M%S'"
end grabTimeForFilename
 
on makeFileFromText(newFilePath, fileContents) -- [Shared subroutine]
	copy (open for access newFilePath with write permission) to theOpenFile
	set eof theOpenFile to 0 -- Clear it out first (just in case it already existed)
	write fileContents to theOpenFile
	close access theOpenFile
end makeFileFromText

Helper files

Scripts housed externally as they are best run outside of QLab.

Making

Make Group Cues from a text file [droplet]

(* Make Group Cues from a text file: see Description tab or theExplanation variable
 
www.allthatyouhear.com | Please attribute this work if you share it, and please report any bugs or issues you encounter
 
v1.0:	06/02/18		Rich Walsh (with thanks to Gareth Fry for the original idea, years and years ago)
		16/02/18		Tested with QLab 4.4.1: no changes (although note that "front workspace" may not be what you expect if you've hidden QLab)
 
<<< Last tested with QLab 4.4.1, MacOS 10.12.6 >>> *)
 
-- User-defined variables
 
property userBypassExplanation : false -- Set this to true if you don't want to see the explanation dialog each time you run the application;
(* it's never displayed when dropping files *)
 
-- Explanations
 
property theExplanation : "This application will make Group Cues in QLab's front workspace, based on the file you open/drop. If it has 1 column, " & ¬
	"that will be used for the names; if it has 2 they will be Cue Number and name; 3 columns will also set Notes. Tab-delimited layouts are:
 
	q name
	q number     q name
	q number     q name     notes"
 
-- Declarations
 
property dialogTitle : "Make Group Cues from a text file"
 
global currentTIDs
 
on run
 
	set currentTIDs to AppleScript's text item delimiters
 
	if userBypassExplanation is false then
		display dialog theExplanation with title dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel"
	end if
 
	-- Check QLab is running
 
	tell application "System Events"
		set qLabIsRunning to exists (processes whose bundle identifier is "com.figure53.QLab.4")
	end tell
	if qLabIsRunning is false then
		my exitStrategy("QLab is not running.", 5)
		return
	end if
 
	-- Test for a workspace
 
	tell application id "com.figure53.QLab.4"
		try
			set workspaceName to q number of front workspace
		on error
			set workspaceName to false
		end try
	end tell
	if workspaceName is false then
		my exitStrategy("There is no workspace open in QLab.", 5)
		return
	end if
 
	set openedFile to choose file of type "public.plain-text" with prompt "Please select a tab-delimited text file:" default location (path to desktop)
 
	makeCues(openedFile)
 
end run
 
on open droppedFiles
 
	set currentTIDs to AppleScript's text item delimiters
 
	if class of droppedFiles is list then
		set droppedFiles to first item of droppedFiles -- Only handle the first one if multiple files dropped
	end if
 
	-- Check dropped file is plain text
 
	tell application "System Events"
		set fileType to type identifier of droppedFiles
	end tell
	if fileType is "public.plain-text" then
		makeCues(droppedFiles)
	else
		my exitStrategy("That wasn't a text file!", false)
	end if
 
end open
 
-- Subroutines
 
(* === OUTPUT === *)
 
on exitStrategy(theMessage, givingUp) -- [Shared subroutine]
	if theMessage is not false then
		if givingUp is not false then
			display dialog theMessage with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after givingUp
		else
			display dialog theMessage with title dialogTitle with icon 0 buttons {"OK"} default button "OK"
		end if
	end if
	set AppleScript's text item delimiters to currentTIDs
end exitStrategy
 
(* === PROCESSING === *)
 
on makeCues(theFile)
 
	try
		set theText to read theFile
	on error
		my exitStrategy("I'm afraid that file tasted funny so I've had to spit it out. Please check the file and try again. Sorry.", false)
		return
	end try
 
	set AppleScript's text item delimiters to tab
 
	try
		set columnCount to count (text items of paragraph 1 of theText)
	on error
		my exitStrategy("I'm afraid that file tasted funny so I've had to spit it out. Please check the file and try again. Sorry.", false)
		return
	end try
 
	if columnCount > 3 then
		my exitStrategy("Too many columns…", false)
		return
	end if
 
	tell application id "com.figure53.QLab.4" to tell front workspace
 
		repeat with eachPara in paragraphs of theText
 
			make type "Group"
 
			set newCue to last item of (selected as list)
 
			if columnCount is 1 then
 
				set q name of newCue to text item 1 of eachPara
 
			else if columnCount is 2 then
 
				set q number of newCue to text item 1 of eachPara
				set q name of newCue to text item 2 of eachPara
 
			else if columnCount is 3 then
 
				set q number of newCue to text item 1 of eachPara
				set q name of newCue to text item 2 of eachPara
				set notes of newCue to text item 3 of eachPara
 
			end if
 
		end repeat
 
		activate
 
	end tell
 
end makeCues
 
(* END: Make Group Cues from a text file *)

Reporting

Make a text file from cues [script]

(* Make a text file from cues: ingest/make a tab-delimited text file and populate (a copy of) it with a report of the cues in the current workspace in QLab;
	see "Explanations" for further explanation. Don't forget you can do this for the properties that have columns in the cue list view – PLUS notes &
	hotkey triggers – simply by copying a selection of cues and pasting into TextEdit or Excel (this is also – currently – the _only_ way of getting a list
	of hotkey triggers).
 
www.allthatyouhear.com | Please attribute this work if you share it, and please report any bugs or issues you encounter
 
This script is not designed to be run from within QLab!
 
NB: if you don't have Microsoft Excel you'll need to edit the script a bit: search for "### NO EXCEL ###"
 
v0.9:	12/10/09		Rich Walsh (with thanks to Jeremy Lee for some of the basic concepts for the sister script "Make cues from a text file")
v0.9.1:	16/10/09		Now "tested" in Snow Leopard; expanded makeNiceT for hours; fixed nasty mess with missing cue/file target strings;
		added Excel cleanup if renaming levels columns; made progress updates more frequent; general tidying – including first attempts at improving efficiency;
		updates for QLab 2.2.5
v0.9.2:	27/10/09		Added MSC translation; numerous typos
v1.0:	11/01/10		Fixed text-cleaning routines; "start value" is read-only, so fixed that; byte combo (and related) origin offset; corrected minor typos;
		added tell front workspace for elegance; wrapped text for better wiki experience; implemented dialogTitle for cross-script pillaging
v1.1:	31/01/10		Fixed a bug (!) with carriage returns in notes
v2.0ß:	12/11/17		Major overhaul & rewrite for QLab 4… New pick'n'mix option!
v2.0:	06/02/18		Made it possible to set userFileTimestampFormat to false for option of no timestamps in filenames
v2.1:	07/02/18		Trapped a small bug for Excel; fixed a big bug with not removing tabs from text properties!
 
Note:	"Make cues from a text file" has not been updated for QLab 4 yet…
 
<<< Last tested with QLab 4.1.6, MacOS 10.12.6, Microsoft Excel 16.10; NB: **** NOT THOROUGHLY TESTED! **** >>>
 
-- ###FIXME### As of 4.1.6, "start time offset" for Timecode Cues can only be reported as an unformatted number of seconds as attempting to get the
	smpte format for the non-integer rates throws an AppleScript error (they haven't been defined as constants in the sdef yet)
-- ###FIXME### Microsoft Excel 2016 refuses to open a file as a text file unless you open it first and then close it! It's some kind of sandbox / permissions issue…
-- ###FIXME### Reporting solution for text format records is not very satisfactory (suggestions welcome!)
 
-- ###ADD### If the "Min" level from the Audio preferences page becomes scriptable then item 1 of userMinusInfinity could be pulled from the workspace
 
*** CUSTOMISATION ***
 
This script has been designed to be highly customisable: you have control over what properties are reported, what the columns are called,
how QLab's constants are displayed and some other little tweaks too. However, you should record what you've done so you can repeat it
if the script ever gets updated, and please DON'T release your private version into the wild!
 
To change what properties are reported, you can make your own text file or add/modify "presets" below. If, say, you add one with
"set preset8 to {"file target"}" then make sure you add something to the end of customPresets so you can choose it, and "preset8"
to the end of presetMapper so the script can find it. You'll need to add an entry to currentListOnlyNoChildrenPresetMapper too.
 
To change what the columns are called, edit the relevant entry in the "set acceptableColumnHeaders" line, eg: change "continue mode" to "f/on"
if you prefer (just don't use commas or carets – they identify level- & gang-setting columns). You'll need to do this in any presets that refer to that
property too, mind.
 
To change how QLab's constants are returned, edit the contents of the relevant "set constantsXXX" line, eg: change "set constants11_continue_mode"
entries from "do_not_continue", "auto_continue" & "auto_follow" to "no", "a/c" & "a/f" if you prefer.
 
Further tweaks are possible under "User-defined variables" & "Customisable reporting translations"; hopefully they're self-explanatory.
 
*** OUTPUT FILE FORMAT ***
 
The final output is to a text file with the system encoding, regardless of what type of file is ingested. Experiments suggested that this was
the safest route as problems were encountered with characters like "¶": writing out as Unicode text leads to wrong characters in TextEdit,
whilst «class utf8» leads to wrong characters in Excel; Excel can still be tricky with these characters if opening the file via the Finder as
it's not detecting the file origin correctly, and needs to be told that the file is from a Macintosh. *)
 
-- User-defined variables
 
set userEscapeHatchInterval to 20 -- Set the number of cues to process between each progress report / opportunity to cancel
 
set userFileTimestampFormat to "+%y-%m-%d %H%M%S" -- Set the format for timestamps appended to filenames (set to false for none);
(* the structure of this string follows the conversion specifiers of strftime: run "man strftime" in Terminal to see more *)
 
set userFileNameAppendix to "Cues report" -- String to append to files made by copying and populating existing files
 
set userSortPickNMix to true -- Set this to false if you don't want the list of properties in pick'n'mix mode (mostly) sorted alphabetically –
(* they'll be in the same order as acceptableColumnHeaders, which is based on QLab's AppleScript dictionary *)
 
set userDefaultInputs to 2 -- The default number of audio input channels to offer to report in level-reporting dialogs
set userDefaultOutputs to 32 -- The default number of outputs to offer to report in level-reporting dialogs
set userOtherRows to 1 -- The default option to offer for rows if "Other" is chosen in level-reporting dialogs
set userOtherColumns to 17 -- The default option to offer for columns if "Other" is chosen in level-reporting dialogs
 
set userReportCueListTimesToDisplayedPrecision to true -- Set this to false if you want times that appear in the cue list columns to be reported to
(* the 3 decimal places of the Inspector, rather than the 2 decimal places shown in the columns *)
 
set userTabCharacterSubstitute to "     " -- Symbol to replace tab characters stripped from text properties, as they can't be copied into tab-delimited output files
(* (not every text field will accept tab, but some surprising ones do – eg: q number) *)
 
set userMinusInfinity to {-120, "-INF"} -- If a level is returned below the first item, the second item in this list is substituted;
(* NB: Excel gets confused by "-INF" in the cells unless you let the script force Excel to open every column as "text"… *)
 
set userCarriageReturnsInLongText to "¶" -- Symbol to use for carriage returns in multi-line text properties –
(* notes, text of Text Cues, command text, script source; carriage returns have to be removed as they would corrupt the tab-delimited structure *)
 
set userSliceRecordColumnDelimiter to " | " -- String to use between slice time and play count in slice records
set userSliceRecordRowDelimiter to " ¶ " -- String to use between slice records when making text block
-- Sadly, within tab-delimited structure of report there is no simple way to break slice records out into multiple rows & columns
 
set userTextFormatDelimiter to " ; " -- String to use between coerced text formats when there is more than one
-- ###FIXME### Coercion format is not yet customisable; it's a bit like css at the moment {style:ugly; happy:not}
 
set userMissingCueTarget to "**** No target cue ****" -- String to return when a cue's cue target is missing
set userMissingFileTarget to "**** No target file ****" -- String to return when a cue's file target is missing
set userMissingQuartzFile to "None" -- String to return when a cue doesn't have a custom Quartz Composer file (no way to detect if this matters)
set userNoMTCSyncSource to "None" -- String to return when a cue's mtc sync source name is missing (ie: hasn't been changed from "None")
 
set userLayerThousandIsTop to "top" -- String to return if a cue's layer is 1000 (and hence displayed as "top" by QLab)
set userLayerZeroIsBottom to "bottom" -- String to return if a cue's layer is 0 (and hence displayed as "bottom" by QLab)
set userConvertOpacityToPercent to true -- Report opacity as percentage (as per Inspector) rather than 0-1 as per AppleScript property?
 
set userCueListsAreOrphans to "–" -- String to return when asked for the parent of a cue list or cue cart
set userParentIsCueListBrackets to {"[", "]"} -- Symbols to surround cue's parents if they are cue lists or carts; change to "" for none
 
set userIrrelevantCrosspoints to "N/A" -- String to return when asked to examine crosspoints in rows beyond the number of audio input channels in a cue
 
-- Explanations
 
set theExplanation1 to "This script will ingest a tab-delimited plain text file, copy it, and then populate the copy with a report of the cues " & ¬
	"in the front workspace in QLab – returning the properties you specify in the text file.
 
Alternatively, it also comes with a set of exciting presets, to which you can add levels & gangs reporting to suit your needs each time you run the script.
 
When it's done you can choose to open the file in Excel, forcing Excel to not try to be clever and, therefore, unhelpfully disrupt the formatting. " & ¬
	"The file you get out at the end is a plain text file with no particular application association, regardless of what you put in " & ¬
	"(this just turned out to be the easiest way).
 
If you choose to make your own text file then the first row must contain the \"column headers\", ie: it should define which properties " & ¬
	"you are hoping to get from your cues. Unless you customise the script, these column headers must match exactly the strings used in " & ¬
	"QLab's AppleScript dictionary. For a list you can look at the dictionary, see the \"set acceptableColumnHeaders\" line inside this script, " & ¬
	"or push the button below to copy it to the Clipboard (as a tab-delimited row). Note that this list is slightly different from the list in the sister script " & ¬
	"\"Make cues from a text file\", as it includes properties that are read-only (like \"broken\") – and \"put in group\" becomes \"parent\". " & ¬
	"It's also worth noting that you probably won't be able to take the output of this script, run it through that script and end up with the workspace " & ¬
	"again (QLab has its own \"Save\" routine for that!).
 
The next page has some details about levels and customisation…"
 
set theExplanation2 to "Since there are currently 1,625 possible scriptable levels for those cues that take them, it's up to you which levels to request " & ¬
	"in your file. For any crosspoint you wish to get, add a column header of the form \"row,column\" and those levels will be returned in that column. " & ¬
	"For example: column header \"0,0\" specifies row 0 column 0 (ie: the Master level), \"2,42\" would be row 2 column 42 (the crosspoint between " & ¬
	"channel 2 of your audio file and output 42). You'll get the choice to turn some of these abstract numbers into text when the script runs " & ¬
	"(try it and find out!). It's the same syntax for gangs, but with \"^\" instead of \",\". Be prepared to wait if you set a high number of crosspoints to report on!
 
Some other substitution takes place when values are returned, eg: carriage returns in a cue's \"notes\" are replaced with \"\", so as not to break " & ¬
	"the tab-delimiting. Most of the substitutions are customisable, though.
 
In fact, this script is highly customisable – just look inside it to find out more.
 
With a few exceptions, properties that are stored as \"real\" will be reported to the precision returned, not to the precision displayed in the Inspector."
 
-- Declarations
 
global dialogTitle, qLabMaxAudioInputs, qLabMaxAudioChannels
set dialogTitle to "Make a text file from cues"
 
set qLabMaxAudioInputs to 24
set qLabMaxAudioChannels to 64
 
global userEscapeHatchInterval, userFileTimestampFormat, userTabCharacterSubstitute, userCarriageReturnsInLongText, startTime
 
global currentTIDs
set currentTIDs to AppleScript's text item delimiters
set AppleScript's text item delimiters to "" -- Should not assume this
 
set acceptableColumnHeaders to {"q type", "q number", "q name", "q color", "notes", ¬
	"cue target", "file target", "pre wait", "duration", "post wait", "continue mode", "flagged", "autoload", "armed", "hotkey trigger", ¬
	"midi trigger", "midi command", "midi byte one", "midi byte two", "midi byte one string", "midi byte two string", ¬
	"timecode trigger", "timecode show as timecode", "timecode hours", "timecode minutes", "timecode seconds", "timecode frames", "timecode bits", ¬
	"wall clock trigger", "wall clock hours", "wall clock minutes", "wall clock seconds", "mode", ¬
	"sync to timecode", "sync mode", "smpte format", "mtc sync source name", "ltc sync channel", ¬
	"patch", "start time", "end time", "play count", "infinite loop", "rate", "integrated fade", "lock fade to cue", ¬
	"pitch shift", "slice markers", "last slice play count", "last slice infinite loop", ¬
	"layer", "full surface", "preserve aspect ratio", "opacity", "translation x", "translation y", "scale x", "scale y", "do video effect", "custom quartz file", ¬
	"hold at end", "text", "text format", "text alignment", "camera patch", "audio fade mode", "video fade mode", "stop target when done", ¬
	"rotation type", "rotation", "do opacity", "do translation", "do rotation", "do scale", ¬
	"command text", "always collate", "message type", "command", "channel", "byte one", "byte two", "byte combo", "end value", ¬
	"fade", "deviceID", "command format", "command number", "q_number", "q_list", "q_path", "macro", "control number", "control value", ¬
	"hours", "minutes", "seconds", "frames", "subframes", "send time with set", "sysex message", "osc message type", ¬
	"q_num", "q_command", "q_params", "custom message", "udp message", "start time offset", ¬
	"fire next cue when slice ends", "stop target when slice ends", "load time", "assigned number", ¬
	"script source"} -- All possible cue properties (except status-related properties, such as "running"); order is based on entries in QLab's AppleScript dictionary
 
set customColumnHeaders to {"parent"} -- Additional columns this script will understand
repeat with eachItem in customColumnHeaders
	set end of acceptableColumnHeaders to eachItem as text
end repeat
 
set reportingOnlyColumns to {"unique ID", "q list name", "q display name", "q default name", "broken", "audio input channels", "start value"}
-- These are r/o properties that can't be set/made
repeat with eachItem in reportingOnlyColumns
	set end of acceptableColumnHeaders to eachItem as text
end repeat
 
set levelColumns to {} -- If a column header contains "," it will be added to this list
set gangColumns to {} -- If a column header contains "^" it will be added to this list
 
-- This is a list of properties that can be read for each cue type (do not edit these variables); ^ indicates text properties that testing has shown can include tabs
 
-- set index1_q_type: every cue has a q type (text)
-- set index2_q_number: every cue has q number (text^)
-- set index3_q_name: every cue has q name (text^)
-- set index4_q_color: every cue has q color (text)
-- set index5_notes: every cue has notes (text^)
set index6_cue_target to {"Fade", "Start", "Stop", "Pause", "Load", "Reset", "Devamp", "GoTo", "Target", "Arm", "Disarm"} -- (cue / "missing value");
(* although every cue returns a value for cue target, these are the only types that actually take one *)
set index7_file_target to {"Audio", "Video", "MIDI File"} -- (file / "missing value");
(* although every cue returns a value for file target, these are the only types that actually take one *)
-- set index8_pre_wait: every cue has pre wait (real)
set index9_duration to {"Group", "Audio", "Video", "Text", "Light", "Fade", "Network", "MIDI", "MIDI File", "Wait"} -- (real);
(* although every cue returns a value for duration, for the other types it makes no sense to record "0" *)
-- set index10_post_wait: every cue has post wait (real)
-- set index11_continue_mode: every cue has continue mode (constants)
-- set index12_flagged: every cue has flagged (boolean)
-- set index13_autoload: every cue has autoload (boolean)
-- set index14_armed: every cue has armed (boolean)
-- set index15_hotkey_trigger: every cue has hotkey trigger (constants)
-- set index16_midi_trigger: every cue has midi trigger (constants)
-- set index17_midi_command: every cue has midi command (constants)
-- set index18_midi_byte_one: every cue has midi byte one (integer)
-- set index19_midi_byte_two: every cue has midi byte two (integer)
-- set index20_midi_byte_one_string: every cue has midi byte one string (text)
-- set index21_midi_byte_two_string: every cue has midi byte two string (text)
-- set index22_timecode_trigger: every cue has timecode trigger (constants)
-- set index23_timecode_show_as_timecode: every cue has timecode show as timecode (boolean)
-- set index24_timecode_hours: every cue has timecode hours (integer)
-- set index25_timecode_minutes: every cue has timecode minutes (integer)
-- set index26_timecode_seconds: every cue has timecode seconds (integer)
-- set index27_timecode_frames: every cue has timecode frames (integer)
-- set index28_timecode_bits: every cue has timecode bits (integer)
-- set index29_wall_clock_trigger: every cue has wall clock trigger (constants)
-- set index30_wall_clock_hours: every cue has wall clock hours (integer); NB: value is reported as 24-hour regardless of display choice in Inspector
-- set index31_wall_clock_minutes: every cue has wall clock minutes (integer)
-- set index32_wall_clock_seconds: every cue has wall clock seconds (integer)
set index33_mode to {"Cue List", "Group"} -- (constants)
set index34_sync_to_timecode to {"Cue List"} -- (constants)
set index35_sync_mode to {"Cue List"} -- (constants)
set index36_smpte_format to {"Cue List", "MIDI"} -- (constants) ###FIXME### "Timecode" should be in index36_smpte_format,
(* but as of 4.1.6, QLab throws error -10000 if you try to get smpte format from a Timecode Cue… *)
set index37_mtc_sync_source_name to {"Cue List"} -- (text^)
set index38_ltc_sync_channel to {"Cue List"} -- (integer)
set index39_patch to {"Audio", "Mic", "Video", "Network", "MIDI", "MIDI File", "Timecode"} -- (integer) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index40_start_time to {"Audio", "Video"} -- (real) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index41_end_time to {"Audio", "Video"} -- (real) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index42_play_count to {"Audio", "Video"} -- (integer) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index43_infinite_loop to {"Audio", "Video"} -- (boolean) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index44_rate to {"Audio", "Video", "MIDI File"} -- (real) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index45_integrated_fade to {"Audio", "Video"} -- (constants) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index46_lock_fade_to_cue to {"Audio", "Video"} -- (constants) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index47_pitch_shift to {"Audio", "Video"} -- (constants) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index48_slice_markers to {"Audio", "Video"} -- (records) ###FIXME### As of 4.1.6, "Text" has entry in sdef
###TODO### Having slice markers in a single column may not scale well to "Make cues from a text file" when (if) it is updated…
set index49_last_slice_play_count to {"Audio", "Video"} -- (integer) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index50_last_slice_infinite_loop to {"Audio", "Video"} -- (boolean) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index51_layer to {"Video", "Camera", "Text"} -- (integer)
set index52_full_surface to {"Video", "Camera", "Text"} -- (boolean)
set index53_preserve_aspect_ratio to {"Video", "Camera", "Text", "Fade"} -- (boolean)
set index54_opacity to {"Video", "Camera", "Text", "Fade"} -- (real); NB: reports as 0-1 but displays as 0-100%
set index55_translation_x to {"Video", "Camera", "Text", "Fade"} -- (real)
set index56_translation_y to {"Video", "Camera", "Text", "Fade"} -- (real)
set index57_scale_x to {"Video", "Camera", "Text", "Fade"} -- (real)
set index58_scale_y to {"Video", "Camera", "Text", "Fade"} -- (real)
set index59_do_video_effect to {"Video", "Camera", "Text"} -- (boolean)
set index60_custom_quartz_file to {"Video", "Camera", "Text"} -- (file)
set index61_hold_at_end to {"Video", "Text"} -- (boolean)
set index62_text to {"Text"} -- (text^)
set index63_text_format to {"Text"} -- (records); wordIndex (integer) can be used as a record label when setting, but not getting
###TODO### Handling text format records will take some considerable thought in "Make cues from a text file" when (if) it is updated!
set index64_text_alignment to {"Text"} -- (text)
set index65_camera_patch to {"Camera"} -- (integer)
set index66_audio_fade_mode to {"Fade"} -- (constants)
set index67_video_fade_mode to {"Fade"} -- (constants)
set index68_stop_target_when_done to {"Fade"} -- (boolean)
set index69_rotation_type to {"Fade"} -- (integer)
set index70_rotation to {"Fade"} -- (real)
set index71_do_opacity to {"Fade"} -- (boolean)
set index72_do_translation to {"Fade"} -- (boolean)
set index73_do_rotation to {"Fade"} -- (boolean)
set index74_do_scale to {"Fade"} -- (boolean)
set index75_command_text to {"Light"} -- (text^)
set index76_always_collate to {"Light"} -- (boolean)
set index77_message_type to {"MIDI"} -- (constants)
set index78_command to {"MIDI"} -- (constants)
set index79_channel to {"MIDI"} -- (integer)
set index80_byte_one to {"MIDI"} -- (integer)
set index81_byte_two to {"MIDI"} -- (integer)
set index82_byte_combo to {"MIDI"} -- (integer)
set index83_end_value to {"MIDI"} -- (integer)
set index84_fade to {"MIDI"} -- (constants)
set index85_deviceID to {"MIDI"} -- (integer)
set index86_command_format to {"MIDI"} -- (integer)
set index87_command_number to {"MIDI"} -- (integer)
set index88_q__number to {"MIDI"} -- (text)
set index89_q__list to {"MIDI"} -- (text)
set index90_q__path to {"MIDI"} -- (text)
set index91_macro to {"MIDI"} -- (integer)
set index92_control_number to {"MIDI"} -- (integer)
set index93_control_value to {"MIDI"} -- (integer)
set index94_hours to {"MIDI"} -- (integer)
set index95_minutes to {"MIDI"} -- (integer)
set index96_seconds to {"MIDI"} -- (integer)
set index97_frames to {"MIDI"} -- (integer)
set index98_subframes to {"MIDI"} -- (integer)
set index99_send_time_with_set to {"MIDI"} -- (boolean)
set index100_sysex_message to {"MIDI"} -- (text^)
set index101_osc_message_type to {"Network"} -- (constants)
set index102_q_num to {"Network"} -- (text^)
set index103_q_command to {"Network"} -- (number)
set index104_q_params to {"Network"} -- (text^)
set index105_custom_message to {"Network"} -- (text^)
set index106_udp_message to {"Network"} -- (text^)
set index107_start_time_offset to {"Timecode"} -- (real)
set index108_fire_next_cue_when_slice_ends to {"Devamp"} -- (boolean)
set index109_stop_target_when_slice_ends to {"Devamp"} -- (boolean)
set index110_load_time to {"Load"} -- (real)
set index111_assigned_number to {"Target"} -- (text^)
set index112_script_source to {"Script"} -- (text^)
-- set index113_parent: every cue has parent (cue)
-- set index114_unique_ID: every cue has unique ID (text)
-- set index115_q_list_name: every cue has q list name (text^)
-- set index116_q_display_name: every cue has q display name (text^)
-- set index117_q_default_name: every cue has q default name (text^)
-- set index118_broken: every cue has broken (boolean)
set index119_audio_input_channels to {"Audio", "Mic", "Video"} -- (integer) ###FIXME### As of 4.1.6, "Text" has entry in sdef
set index120_start_value to {"MIDI"} -- (integer)
 
set index_takesLevel to {"Audio", "Video", "Fade"} -- Special private index for custom column headers
set index_takesGang to {"Audio", "Video", "Fade"} -- Special private index for custom column headers
 
-- This is a list of values for any constants (which can be used to customise the entries returned in the text file)
 
set constants11_continue_mode to {"do_not_continue", "auto_continue", "auto_follow"}
set constants12_flagged to {"true", "false"}
set constants13_autoload to {"true", "false"}
set constants14_armed to {"true", "false"}
set constants15_hotkey_trigger to {"enabled", "disabled"}
set constants16_midi_trigger to {"enabled", "disabled"}
set constants17_midi_command to {"note_on", "note_off", "program_change", "control_change", "key_pressure", "channel_pressure"}
set constants22_timecode_trigger to {"enabled", "disabled"}
set constants23_timecode_show_as_timecode to {"true", "false"}
set constants29_wall_clock_trigger to {"enabled", "disabled"}
set constants33_mode to {"cue_list", "fire_first_enter_group", "fire_first_go_to_next_cue", "fire_all", "fire_random"}
set constants34_sync_to_timecode to {"enabled", "disabled"}
set constants35_sync_mode to {"mtc", "ltc"}
set constants36_smpte_format to {"fps_24", "fps_25", "fps_30_drop", "fps_30_non_drop"}
set constants43_infinite_loop to {"true", "false"}
set constants45_integrated_fade to {"enabled", "disabled"}
set constants46_lock_fade_to_cue to {"enabled", "disabled"}
set constants47_pitch_shift to {"enabled", "disabled"}
set constants50_last_slice_infinite_loop to {"true", "false"}
set constants52_full_surface to {"true", "false"}
set constants53_preserve_aspect_ratio to {"true", "false"}
set constants59_do_video_effect to {"true", "false"}
set constants61_hold_at_end to {"true", "false"}
set constants66_audio_fade_mode to {"absolute", "relative"}
set constants67_video_fade_mode to {"absolute", "relative"}
set constants68_stop_target_when_done to {"true", "false"}
set constants71_do_opacity to {"true", "false"}
set constants72_do_translation to {"true", "false"}
set constants73_do_rotation to {"true", "false"}
set constants74_do_scale to {"true", "false"}
set constants76_always_collate to {"true", "false"}
set constants77_message_type to {"voice", "msc", "sysex"}
set constants78_command to {"note_on", "note_off", "program_change", "control_change", "key_pressure", "channel_pressure", "pitch_bend"}
set constants84_fade to {"enabled", "disabled"}
set constants99_send_time_with_set to {"true", "false"}
set constants101_osc_message_type to {"qlab", "custom", "udp"}
set constants108_fire_next_cue_when_slice_ends to {"true", "false"}
set constants109_stop_target_when_slice_ends to {"true", "false"}
set constants118_broken to {"true", "false"}
 
-- This variable is used to translate rotation type integers into English
 
set translation69_rotation_type to {0, "3D orientation", 1, "X rotation", 2, "Y rotation", 3, "Z rotation"}
 
-- These variables are used to translate MSC integers into English
 
set translation86_command_format to {1, "Lighting (General)", 2, "Moving Lights", 3, "Color Changers", 4, "Strobes", 5, "Lasers", 6, "Chasers", ¬
	16, "Sound (General)", 17, "Music", 18, "CD Players", 19, "EPROM Playback", 20, "Audio Tape Machines", 21, "Intercoms", 22, "Amplifiers", ¬
	23, "Audio Effects Devices", 24, "Equalizers", 32, "Machinery (General)", 33, "Rigging", 34, "Flys", 35, "Lifts", 36, "Turntables", 37, "Trusses", ¬
	38, "Robots", 39, "Animation", 40, "Floats", 41, "Breakaways", 42, "Barges", 48, "Video (General)", 49, "Video Tape Machines", ¬
	50, "Video Cassette Machines", 51, "Video Disc Players", 52, "Video Switchers", 53, "Video Effects", 54, "Video Character Generators", ¬
	55, "Video Still Stores", 56, "Video Monitors", 64, "Projection (General)", 65, "Film Projectors", 66, "Slide Projectors", 67, "Video Projectors", ¬
	68, "Dissolvers", 69, "Shutter Controls", 80, "Process Control (General)", 81, "Hydraulic Oil", 82, "H2O", 83, "CO2", 84, "Compressed Air", ¬
	85, "Natural Gas", 86, "Fog", 87, "Smoke", 88, "Cracked Haze", 96, "Pyrotechnics (General)", 97, "Fireworks", 98, "Explosions", 99, "Flame", ¬
	100, "Smoke Pots", 127, "All Types"}
set translation87_command_number to {1, "GO", 2, "STOP", 3, "RESUME", 4, "TIMED_GO", 5, "LOAD", 6, "SET", 7, "FIRE", 8, "ALL_OFF", ¬
	9, "RESTORE", 10, "RESET", 11, "GO_OFF", 16, "GO/JAM_CLOCK", 17, "STANDBY_+", 18, "STANDBY_-", 19, "SEQUENCE_+", ¬
	20, "SEQUENCE_-", 21, "START_CLOCK", 22, "STOP_CLOCK", 23, "ZERO_CLOCK", 24, "SET_CLOCK", 25, "MTC_CHASE_ON", ¬
	26, "MTC_CHASE_OFF", 27, "OPEN_CUE_LIST", 28, "CLOSE_CUE_LIST", 29, "OPEN_CUE_PATH", 30, "CLOSE_CUE_PATH"}
 
-- This variable is used to translate QLab type Network message command integers into English
 
set translation103_q_command to {1, "start", 2, "stop", 3, "hardStop", 4, "pause", 5, "resume", 6, "togglePause", 7, "load", 8, "preview", 9, "reset", ¬
	10, "panic", 11, "number", 12, "name", 13, "notes", 14, "cueTargetNumber", 15, "preWait", 16, "duration", 17, "postWait", 18, "continueMode", ¬
	19, "flagged", 20, "armed", 21, "colorName"}
 
-- Customisable reporting translations
 
set levelCrosspointsInEnglish to {"0,0", "Master", "0,", "Output "} -- Replacement text for levels columns
repeat with i from 1 to qLabMaxAudioInputs
	set end of levelCrosspointsInEnglish to (i & ",0") as text
	set end of levelCrosspointsInEnglish to ("Input " & i) as text
end repeat
 
set gangCrosspointsInEnglish to {"0^0", "Gang: Master", "0^", "Gang: Output "} -- Replacement text for gangs columns
repeat with i from 1 to qLabMaxAudioInputs
	set end of gangCrosspointsInEnglish to (i & "^0") as text
	set end of gangCrosspointsInEnglish to ("Gang: Input " & i) as text
end repeat
 
-- Reporting presets (customisable)
 
set customPresets to {"The columns that you see", "Properties for audio & MIDI", "Properties for video", "All the properties", "Just the basic facts", ¬
	"Key properties for a levels report", "Top level cue details of current cue list only"}
 
set preset1 to {"unique ID", "q type", "q number", "q list name", "cue target", "file target", "pre wait", "duration", "post wait", "continue mode", "broken", ¬
	"notes", "mode", "parent"} -- The columns that you see
set preset2 to {"unique ID", "q type", "q number", "q list name", "cue target", "file target", "pre wait", "duration", "post wait", "continue mode", "broken", ¬
	"notes", "mode", "parent", "patch", "audio input channels", "rate", "pitch shift", "start time", "end time", "play count", "infinite loop", ¬
	"integrated fade", "lock fade to cue", "slice markers", "last slice play count", "last slice infinite loop", ¬
	"audio fade mode", "stop target when done", "message type", "command", "channel", "byte one", "byte two", "byte combo", "start value", "end value", ¬
	"fade", "SMPTE format", "device ID", "command format", "command number", "q_number", "q_list", "q_path", "macro", "control number", "control value", ¬
	"hours", "minutes", "seconds", "frames", "subframes", "send time with set", "sysex message"} -- Properties for audio & MIDI
set preset3 to {"unique ID", "q type", "q number", "q list name", "cue target", "file target", "pre wait", "duration", "post wait", "continue mode", "broken", ¬
	"notes", "mode", "parent", "patch", "rate", "start time", "end time", "play count", "infinite loop", "layer", "full surface", "preserve aspect ratio", "opacity", ¬
	"translation x", "translation y", "scale x", "scale y", "do video effect", "custom quartz file", "hold at end", "video fade mode", "stop target when done", ¬
	"rotation type", "rotation", "do opacity", "do translation", "do rotation", "do scale"} -- Properties for video
set preset4 to acceptableColumnHeaders -- All the properties
set preset5 to {"q number", "q list name", "continue mode", "notes", "mode", "parent"} -- Can you show me where it hurts
set preset6 to {"unique ID", "q type", "q number", "q list name", "cue target", "file target", "broken", "notes", ¬
	"audio input channels", "patch"} -- Key properties for a levels report
set preset7 to {"q number", "q list name", "continue mode", "notes"} -- Top level cue details of current cue list only
 
set presetMapper to {preset1, preset2, preset3, preset4, preset5, preset6, preset7}
set currentListOnlyNoChildrenPresetMapper to {preset1:false, preset2:false, preset3:false, preset4:false, preset5:false, preset6:false, preset7:true} -- If true,
(* the preset will only report cues directly in the current cue list, not their children *)
 
-- Add fixed presets
 
set availablePresets to {"I've rolled my own"} & customPresets & {"Pick'n'mix"}
 
-- General variables
 
set currentListOnlyNoChildren to false
set propertiesToColumns to {}
set propertiesToColumnsRef to a reference to propertiesToColumns
set headerRow to {}
set headerRowRef to a reference to headerRow
 
try -- This overall try makes sure TIDs are reset and open files are closed if any "Cancel" button is pushed
 
	-- Preamble
 
	set theNavigator to "Review instructions"
	repeat until theNavigator is "Get on with it"
		set theNavigator to button returned of (display dialog "Would you like to review the instructions for this script?" with title dialogTitle ¬
			with icon 1 buttons {"Review instructions", "Cancel", "Get on with it"} default button "Get on with it" cancel button "Cancel")
		if theNavigator is "Review instructions" then
			set finishedReading to false
			repeat until finishedReading is true
				set instructionButton1 to "Copy headers to Clipboard"
				repeat until instructionButton1 is not "Copy headers to Clipboard"
					set instructionButton1 to button returned of (display dialog theExplanation1 with title dialogTitle with icon 1 ¬
						buttons {"Back to start", "Copy headers to Clipboard", "To page 2 >>>"} default button "To page 2 >>>")
					if instructionButton1 is "Copy headers to Clipboard" then
						set the clipboard to listToDelimitedText(acceptableColumnHeaders, tab)
					else if instructionButton1 is "Back to start" then
						set finishedReading to true
					end if
				end repeat
				if instructionButton1 is "To page 2 >>>" then
					set instructionButton2 to "Copy headers to Clipboard"
					repeat until instructionButton2 is not "Copy headers to Clipboard"
						set instructionButton2 to button returned of (display dialog theExplanation2 with title dialogTitle with icon 1 ¬
							buttons {"<<< To page 1", "Copy headers to Clipboard", "Back to start"} default button "Back to start")
						if instructionButton2 is "Copy headers to Clipboard" then
							set the clipboard to listToDelimitedText(acceptableColumnHeaders, tab)
						else if instructionButton2 is "Back to start" then
							set finishedReading to true
						end if
					end repeat
				end if
			end repeat
		end if
 
	end repeat
 
	-- Check QLab is running
 
	tell application "System Events"
		set qLabIsRunning to exists (processes whose bundle identifier is "com.figure53.QLab.4")
	end tell
	if qLabIsRunning is false then
		exitStrategy("QLab is not running.", 5)
		return
	end if
 
	-- Test for a workspace (get the name at the same time)
 
	tell application id "com.figure53.QLab.4"
		try
			set workspaceName to q number of front workspace
		on error
			set workspaceName to false
		end try
	end tell
	if workspaceName is false then
		exitStrategy("There is no workspace open in QLab.", 5)
		return
	end if
 
	-- Select a preset
 
	choose from list availablePresets with prompt "Please choose whether to ingest your own file, or use one of my preset reports:" with title dialogTitle ¬
		default items item 1 of availablePresets
	if result is not false then
		set presetMenuChoice to item 1 of result
	else
		exitStrategy(false, false)
		return
	end if
 
	repeat with i from 2 to (count availablePresets) - 1
		if presetMenuChoice is item i of availablePresets then
			set chosenPreset to item (i - 1) of presetMapper
			set currentListOnlyNoChildren to item (i - 1) of (currentListOnlyNoChildrenPresetMapper as list)
			exit repeat
		end if
	end repeat
 
	-- Deal with pick'n'mix choice…
 
	if presetMenuChoice is last item of availablePresets then
		if userSortPickNMix is true then
			set pinnedToTop to items 1 thru 3 of acceptableColumnHeaders
			set sortableHeaders to items 4 thru end of acceptableColumnHeaders
			set sortedHeaders to paragraphs of sortTextIgnoringCase(listToDelimitedText(sortableHeaders, linefeed))
			set pickNChoose to pinnedToTop & sortedHeaders
		else
			set pickNChoose to acceptableColumnHeaders
		end if
		choose from list pickNChoose with prompt "Please choose the properties to report on:" with title dialogTitle with multiple selections allowed
		if result is not false then
			set chosenPreset to result
			offerToSave("Would you like to save that selection to the Desktop as a text file?", ¬
				{"Yes", "No", "Cancel"}, "No", "Cancel", "Yes", listToDelimitedText(chosenPreset, tab), "Pick'n'mix selection")
		else
			exitStrategy(false, false)
			return
		end if
	end if
 
	-- Prepare the output file…
 
	if userFileTimestampFormat is false then
		set fileTimeString to ""
	else
		set fileTimeString to " | " & grabTimeForFilenameBespoke()
	end if
 
	-- …either get the file (and prepare to copy it)…
 
	if presetMenuChoice is first item of availablePresets then
 
		set originalFile to choose file of type "public.plain-text" with prompt "Please select a tab-delimited text file which contains a header row " & ¬
			"for the properties you wish to include in the report:" default location (path to desktop)
 
		try
			set theText to read originalFile
		on error
			exitStrategy("I'm afraid that file tasted funny so I've had to spit it out. Please check the file and try again. Sorry.", false)
			return
		end try
 
		tell application "System Events"
			set theContainer to path of container of originalFile as text
			set theExtension to name extension of originalFile
			if theExtension is "" then
				set theName to name of originalFile
			else
				set theFullName to name of originalFile
				set theName to text 1 through (-1 - ((length of theExtension) + 1)) of theFullName
				set theExtension to "." & theExtension
			end if
			set outputFilename to theName & " | QLab | " & workspaceName & " | " & userFileNameAppendix & fileTimeString & theExtension
			set outputFile to theContainer & outputFilename
		end tell
 
		checkForFile(outputFile)
 
		set AppleScript's text item delimiters to tab
		try
			set headerRowRef to text items of paragraph 1 of theText -- Pull headers from file
		on error
			exitStrategy("I'm afraid that file tasted funny so I've had to spit it out. Please check the file and try again. Sorry.", false)
			return
		end try
		set AppleScript's text item delimiters to ""
 
		-- …or prepare to make it on the Desktop, with current time appended to name
 
	else
 
		set outputFilename to "QLab | " & workspaceName & " | " & dialogTitle & fileTimeString & ".txt"
		set outputFile to ((path to desktop) as text) & outputFilename
 
		checkForFile(outputFile)
 
		set headerRowRef to chosenPreset
 
		-- Ask about levels
 
		if currentListOnlyNoChildren is false then -- Don't ask about levels for currentListOnlyNoChildren preset
 
			if class of userDefaultInputs is not integer or userDefaultInputs < 0 or userDefaultInputs > qLabMaxAudioInputs or ¬
				class of userDefaultOutputs is not integer or userDefaultOutputs < 0 or userDefaultOutputs > qLabMaxAudioChannels then
				exitStrategy("There is a problem with the user-defined level-reporting dialog variables.", false)
				return
			end if
 
			if userDefaultInputs is 1 then
				set defaultOffer to "Mono to "
			else if userDefaultInputs is 2 then
				set defaultOffer to "Stereo to "
			else
				set defaultOffer to userDefaultInputs & " inputs to "
			end if
			if userDefaultOutputs is 1 then
				set defaultOffer to defaultOffer & "1 output"
			else
				set defaultOffer to defaultOffer & userDefaultOutputs & " outputs"
			end if
 
			set levelsQuestion to button returned of (display dialog "Do you want to include any levels?" with title dialogTitle with icon 1 ¬
				buttons {"No", defaultOffer, "Other"} default button defaultOffer)
			if levelsQuestion is defaultOffer then
				set {totalRows, totalColumns} to {userDefaultInputs, userDefaultOutputs}
			else if levelsQuestion is "Other" then
				set totalRows to (enterANumberWithRangeWithCustomButton("Enter the number of rows you wish to report " & ¬
					"(ie: a number between 1 & " & qLabMaxAudioInputs + 1 & "):", userOtherRows, 1, true, ¬
					qLabMaxAudioInputs + 1, true, true, {}, "OK")) - 1
				set totalColumns to (enterANumberWithRangeWithCustomButton("Enter the number of columns you wish to report " & ¬
					"(ie: a number between 1 & " & qLabMaxAudioChannels + 1 & "):", userOtherColumns, 1, true, ¬
					qLabMaxAudioChannels + 1, true, true, {}, "OK")) - 1
			end if
			if levelsQuestion is not "No" then
				repeat with i from 0 to totalRows
					repeat with j from 0 to totalColumns
						set end of headerRowRef to (i & "," & j) as text
					end repeat
				end repeat
				if button returned of (display dialog "Do you want to report gangs for those crosspoints too?" with title dialogTitle with icon 1 ¬
					buttons {"No", "Yes"} default button "Yes") is "Yes" then
					repeat with i from 0 to totalRows
						repeat with j from 0 to totalColumns
							set end of headerRowRef to (i & "^" & j) as text
						end repeat
					end repeat
					set saveMessagePrefix to "Levels & gangs"
					set saveAppendix to "Custom preset with levels & gangs"
				else
					set saveMessagePrefix to "Levels"
					set saveAppendix to "Custom preset with levels"
				end if
				offerToSave(saveMessagePrefix & ¬
					" reporting columns added. Would you like to save the augmented preset to the Desktop as a text file for re-use?", ¬
					{"Yes", "No", "Cancel"}, "No", "Cancel", "Yes", listToDelimitedText(headerRowRef, tab), saveAppendix)
			end if
 
		end if
 
		set theText to listToDelimitedText(headerRowRef, tab)
 
	end if
 
	-- Set up translation matrices
 
	repeat with i from 1 to count acceptableColumnHeaders -- Find which properties are in text file, and which column they are in
		set end of propertiesToColumnsRef to 0
		repeat with j from 1 to count headerRowRef
			if item j of headerRowRef is item i of acceptableColumnHeaders then
				set item i of propertiesToColumnsRef to j
			end if
		end repeat
	end repeat
 
	repeat with i from 1 to count headerRowRef -- Make lists of all columns flagged as levels/gangs
		if item i of headerRowRef contains "," then
			set end of levelColumns to i
		end if
		if item i of headerRowRef contains "^" then
			set end of gangColumns to i
		end if
	end repeat
 
	-- Make levels/gangs columns headers easier to read
 
	set AppleScript's text item delimiters to tab
 
	if (count levelColumns) is not 0 then
		if button returned of (display dialog ¬
			"Now that I've got what I need from the headers for levels & gangs reporting, shall I translate (some of) them into text for you?" with title ¬
			dialogTitle with icon 1 buttons {"No thanks", "Please do"} default button "Please do") is "Please do" then
			set dirtyHeader to paragraph 1 of theText -- Only work on first row (in case there is more text in the file)
			set theRest to rest of paragraphs of theText
			set dirtyColumns to text items of dirtyHeader
			set cleanColumns to {}
			repeat with eachColumn in dirtyColumns
				if eachColumn contains "," then -- Only work on levels columns (gangs columns aren't escaped as there are no caret-separated files!)
					set AppleScript's text item delimiters to "\"" -- Strip out Excel formatting: 0,0 becomes "0,0" when exported from Excel
					set cleanStore to text items of eachColumn
					set AppleScript's text item delimiters to ""
					set end of cleanColumns to cleanStore as text
				else
					set end of cleanColumns to eachColumn as text
				end if
			end repeat
			set AppleScript's text item delimiters to tab
			set cleanHeader to cleanColumns as text
			repeat with i from 1 to count levelCrosspointsInEnglish by 2 -- Replace strings in header row
				set AppleScript's text item delimiters to item i of levelCrosspointsInEnglish
				set englishHeader to text items of cleanHeader
				set AppleScript's text item delimiters to item (i + 1) of levelCrosspointsInEnglish
				set cleanHeader to englishHeader as text
			end repeat
			repeat with i from 1 to count gangCrosspointsInEnglish by 2 -- Replace strings in header row
				set AppleScript's text item delimiters to item i of gangCrosspointsInEnglish
				set englishHeader to text items of cleanHeader
				set AppleScript's text item delimiters to item (i + 1) of gangCrosspointsInEnglish
				set cleanHeader to englishHeader as text
			end repeat
			if (count theRest) is 0 then
				set theText to cleanHeader
			else
				set AppleScript's text item delimiters to return
				set theText to cleanHeader & return & theRest as text -- Stitch the file back together
			end if
		end if
	end if
 
	set AppleScript's text item delimiters to ""
 
	-- Now, to business
 
	startTheClock()
 
	tell application id "com.figure53.QLab.4"
 
		activate
 
		tell front workspace
 
			-- Get the cues, subject to currentListOnlyNoChildren
 
			if currentListOnlyNoChildren is false then
				set allCues to cues
			else
				set allCues to cues of current cue list
			end if
			set allCuesRef to a reference to allCues
			set countCues to count allCuesRef
 
			-- Step through cues
 
			repeat with i from 1 to countCues
 
				set eachCue to item i of allCuesRef
 
				-- Prepare a list to hold the properties for this cue
 
				set theProperties to {}
 
				repeat (count headerRowRef) times
					set end of theProperties to ""
				end repeat
 
				set thePropertiesRef to (a reference to theProperties)
 
				-- Get the type
 
				set theType to q type of eachCue as text
				if theType is "Group" and mode of eachCue is cue_list then -- Cue carts have their own q type, but cue lists don't!
					set theType to "Cue List"
				end if
 
				-- These if…then clauses retrieve the relevant property on each cue based on its type and the contents of the text file
 
				if item 1 of propertiesToColumnsRef is not 0 then -- index1_q_type
					set item (item 1 of propertiesToColumnsRef) of thePropertiesRef to theType
				end if
 
				if item 2 of propertiesToColumnsRef is not 0 then -- index2_q_number
					set theItem to q number of eachCue as text
					set item (item 2 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 3 of propertiesToColumnsRef is not 0 then -- index3_q_name
					set theItem to q name of eachCue as text
					set item (item 3 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 4 of propertiesToColumnsRef is not 0 then -- index4_q_color
					set theItem to q color of eachCue as text
					set item (item 4 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 5 of propertiesToColumnsRef is not 0 then -- index5_notes
					set theItem to notes of eachCue as text
					set item (item 5 of propertiesToColumnsRef) of thePropertiesRef to my noReturns(my noTabs(theItem))
				end if
 
				if item 6 of propertiesToColumnsRef is not 0 and theType is in index6_cue_target then
					set theItem to cue target of eachCue
					if theItem is missing value then
						set targetTitle to userMissingCueTarget
					else
						set targetTitle to (q list name of theItem) as text
						if targetTitle is "" then
							set targetTitle to (q number of theItem) as text
							if targetTitle is "" then
								set targetTitle to "id: " & (uniqueID of theItem) as text
							end if
						end if
					end if
					set item (item 6 of propertiesToColumnsRef) of thePropertiesRef to targetTitle
				end if
 
				if item 7 of propertiesToColumnsRef is not 0 and theType is in index7_file_target then
					set theItem to file target of eachCue
					if theItem is missing value then
						set targetTitle to userMissingFileTarget
					else
						set targetTitle to POSIX path of theItem as text
					end if
					set item (item 7 of propertiesToColumnsRef) of thePropertiesRef to targetTitle
				end if
 
				if item 8 of propertiesToColumnsRef is not 0 then -- index8_pre_wait
					set theItem to pre wait of eachCue
					if userReportCueListTimesToDisplayedPrecision is true then
						set item (item 8 of propertiesToColumnsRef) of thePropertiesRef to my makeHHMMSSss(theItem)
					else
						set item (item 8 of propertiesToColumnsRef) of thePropertiesRef to my makeHHMMSSsss(theItem)
					end if
				end if
 
				if item 9 of propertiesToColumnsRef is not 0 and theType is in index9_duration then
					set theItem to duration of eachCue
					if theType is not "Group" or mode of eachCue is fire_all then -- QLab only calculates duration for fire_all groups
						if userReportCueListTimesToDisplayedPrecision is true then
							set item (item 9 of propertiesToColumnsRef) of thePropertiesRef to my makeHHMMSSss(theItem)
						else
							set item (item 9 of propertiesToColumnsRef) of thePropertiesRef to my makeHHMMSSsss(theItem)
						end if
					end if
				end if
 
				if item 10 of propertiesToColumnsRef is not 0 then -- index10_post_wait
					set theItem to post wait of eachCue
					if userReportCueListTimesToDisplayedPrecision is true then
						set item (item 10 of propertiesToColumnsRef) of thePropertiesRef to my makeHHMMSSss(theItem)
					else
						set item (item 10 of propertiesToColumnsRef) of thePropertiesRef to my makeHHMMSSsss(theItem)
					end if
				end if
 
				if item 11 of propertiesToColumnsRef is not 0 then -- index11_continue_mode
					set theItem to continue mode of eachCue
					if theItem is do_not_continue then
						set item (item 11 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants11_continue_mode
					else if theItem is auto_continue then
						set item (item 11 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants11_continue_mode
					else if theItem is auto_follow then
						set item (item 11 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants11_continue_mode
					end if
				end if
 
				if item 12 of propertiesToColumnsRef is not 0 then -- index12_flagged
					set theItem to flagged of eachCue
					if theItem is true then
						set item (item 12 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants12_flagged
					else if theItem is false then
						set item (item 12 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants12_flagged
					end if
				end if
 
				if item 13 of propertiesToColumnsRef is not 0 then -- index13_autoload
					set theItem to autoload of eachCue
					if theItem is true then
						set item (item 13 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants13_autoload
					else if theItem is false then
						set item (item 13 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants13_autoload
					end if
				end if
 
				if item 14 of propertiesToColumnsRef is not 0 then -- index14_armed
					set theItem to armed of eachCue
					if theItem is true then
						set item (item 14 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants14_armed
					else if theItem is false then
						set item (item 14 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants14_armed
					end if
				end if
 
				if item 15 of propertiesToColumnsRef is not 0 then -- index15_hotkey_trigger
					set theItem to hotkey trigger of eachCue
					if theItem is enabled then
						set item (item 15 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants15_hotkey_trigger
					else if theItem is disabled then
						set item (item 15 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants15_hotkey_trigger
					end if
				end if
 
				if item 16 of propertiesToColumnsRef is not 0 then -- index16_midi_trigger
					set theItem to midi trigger of eachCue
					if theItem is enabled then
						set item (item 16 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants16_midi_trigger
					else if theItem is disabled then
						set item (item 16 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants16_midi_trigger
					end if
				end if
 
				if item 17 of propertiesToColumnsRef is not 0 then -- index17_midi_command
					set theItem to midi command of eachCue
					if theItem is note_on then
						set item (item 17 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants17_midi_command
					else if theItem is note_off then
						set item (item 17 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants17_midi_command
					else if theItem is program_change then
						set item (item 17 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants17_midi_command
					else if theItem is control_change then
						set item (item 17 of propertiesToColumnsRef) of thePropertiesRef to item 4 of constants17_midi_command
					else if theItem is key_pressure then
						set item (item 17 of propertiesToColumnsRef) of thePropertiesRef to item 5 of constants17_midi_command
					else if theItem is channel_pressure then
						set item (item 17 of propertiesToColumnsRef) of thePropertiesRef to item 6 of constants17_midi_command
					end if
				end if
 
				if item 18 of propertiesToColumnsRef is not 0 then -- index18_midi_byte_one
					set theItem to midi byte one of eachCue as text
					set item (item 18 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 19 of propertiesToColumnsRef is not 0 then -- index19_midi_byte_two
					if midi command of eachCue is not program_change and midi command of eachCue is not channel_pressure then
						set theItem to midi byte two of eachCue as text
						set item (item 19 of propertiesToColumnsRef) of thePropertiesRef to theItem
					end if
				end if
 
				if item 20 of propertiesToColumnsRef is not 0 then -- index20_midi_byte_one_string
					set theItem to midi byte one string of eachCue as text
					set item (item 20 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 21 of propertiesToColumnsRef is not 0 then -- index21_midi_byte_two_string
					if midi command of eachCue is not program_change and midi command of eachCue is not channel_pressure then
						set theItem to midi byte two string of eachCue as text
						set item (item 21 of propertiesToColumnsRef) of thePropertiesRef to theItem
					end if
				end if
 
				if item 22 of propertiesToColumnsRef is not 0 then -- index22_timecode_trigger
					set theItem to timecode trigger of eachCue
					if theItem is enabled then
						set item (item 22 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants22_timecode_trigger
					else if theItem is disabled then
						set item (item 22 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants22_timecode_trigger
					end if
				end if
 
				if item 23 of propertiesToColumnsRef is not 0 then -- index23_timecode_show_as_timecode
					set theItem to timecode show as timecode of eachCue
					if theItem is true then
						set item (item 23 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants23_timecode_show_as_timecode
					else if theItem is false then
						set item (item 23 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants23_timecode_show_as_timecode
					end if
				end if
 
				if item 24 of propertiesToColumnsRef is not 0 then -- index24_timecode_hours
					set theItem to timecode hours of eachCue as text
					set item (item 24 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 25 of propertiesToColumnsRef is not 0 then -- index25_timecode_minutes
					set theItem to timecode minutes of eachCue as text
					set item (item 25 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 26 of propertiesToColumnsRef is not 0 then -- index26_timecode_seconds
					set theItem to timecode seconds of eachCue as text
					set item (item 26 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 27 of propertiesToColumnsRef is not 0 then -- index27_timecode_frames
					set theItem to timecode frames of eachCue as text
					set item (item 27 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 28 of propertiesToColumnsRef is not 0 then -- index28_timecode_bits
					set theItem to timecode bits of eachCue as text
					set item (item 28 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 29 of propertiesToColumnsRef is not 0 then -- index29_wall_clock_trigger
					set theItem to wall clock trigger of eachCue
					if theItem is enabled then
						set item (item 29 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants29_wall_clock_trigger
					else if theItem is disabled then
						set item (item 29 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants29_wall_clock_trigger
					end if
				end if
 
				if item 30 of propertiesToColumnsRef is not 0 then -- index30_wall_clock_hours
					set theItem to wall clock hours of eachCue as text
					set item (item 30 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 31 of propertiesToColumnsRef is not 0 then -- index31_wall_clock_minutes
					set theItem to wall clock minutes of eachCue as text
					set item (item 31 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 32 of propertiesToColumnsRef is not 0 then -- index32_wall_clock_seconds
					set theItem to wall clock seconds of eachCue as text
					set item (item 32 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 33 of propertiesToColumnsRef is not 0 and theType is in index33_mode then
					set theItem to mode of eachCue
					if theItem is cue_list then
						set item (item 33 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants33_mode
					else if theItem is fire_first_enter_group then
						set item (item 33 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants33_mode
					else if theItem is fire_first_go_to_next_cue then
						set item (item 33 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants33_mode
					else if theItem is fire_all then
						set item (item 33 of propertiesToColumnsRef) of thePropertiesRef to item 4 of constants33_mode
					else if theItem is fire_random then
						set item (item 33 of propertiesToColumnsRef) of thePropertiesRef to item 5 of constants33_mode
					end if
				end if
 
				if item 34 of propertiesToColumnsRef is not 0 and theType is in index34_sync_to_timecode then
					set theItem to sync to timecode of eachCue
					if theItem is enabled then
						set item (item 34 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants34_sync_to_timecode
					else if theItem is disabled then
						set item (item 34 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants34_sync_to_timecode
					end if
				end if
 
				if item 35 of propertiesToColumnsRef is not 0 and theType is in index35_sync_mode then
					set theItem to sync mode of eachCue
					if theItem is mtc then
						set item (item 35 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants35_sync_mode
					else if theItem is ltc then
						set item (item 35 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants35_sync_mode
					end if
				end if
 
				if item 36 of propertiesToColumnsRef is not 0 and theType is in index36_smpte_format then
					set theItem to smpte format of eachCue
					if theItem is fps_24 then
						set item (item 36 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants36_smpte_format
					else if theItem is fps_25 then
						set item (item 36 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants36_smpte_format
					else if theItem is fps_30_drop then
						set item (item 36 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants36_smpte_format
					else if theItem is fps_30_non_drop then
						set item (item 36 of propertiesToColumnsRef) of thePropertiesRef to item 4 of constants36_smpte_format
					end if
				end if
 
				if item 37 of propertiesToColumnsRef is not 0 and theType is in index37_mtc_sync_source_name then
					set theItem to mtc sync source name of eachCue
					if theItem is missing value then
						set item (item 37 of propertiesToColumnsRef) of thePropertiesRef to userNoMTCSyncSource
					else
						set item (item 37 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem as text)
					end if
				end if
 
				if item 38 of propertiesToColumnsRef is not 0 and theType is in index38_ltc_sync_channel then
					set theItem to ltc sync channel of eachCue as text
					set item (item 38 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 39 of propertiesToColumnsRef is not 0 and theType is in index39_patch then
					set theItem to patch of eachCue as text
					set item (item 39 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 40 of propertiesToColumnsRef is not 0 and theType is in index40_start_time then
					set theItem to start time of eachCue
					set item (item 40 of propertiesToColumnsRef) of thePropertiesRef to my makeHHMMSSsss(theItem)
				end if
 
				if item 41 of propertiesToColumnsRef is not 0 and theType is in index41_end_time then
					set theItem to end time of eachCue
					set item (item 41 of propertiesToColumnsRef) of thePropertiesRef to my makeHHMMSSsss(theItem)
				end if
 
				if item 42 of propertiesToColumnsRef is not 0 and theType is in index42_play_count then
					set theItem to play count of eachCue as text
					set item (item 42 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 43 of propertiesToColumnsRef is not 0 and theType is in index43_infinite_loop then
					set theItem to infinite loop of eachCue
					if theItem is true then
						set item (item 43 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants43_infinite_loop
					else if theItem is false then
						set item (item 43 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants43_infinite_loop
					end if
				end if
 
				if item 44 of propertiesToColumnsRef is not 0 and theType is in index44_rate then
					set theItem to rate of eachCue as text
					set item (item 44 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 45 of propertiesToColumnsRef is not 0 and theType is in index45_integrated_fade then
					set theItem to integrated fade of eachCue
					if theItem is enabled then
						set item (item 45 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants45_integrated_fade
					else if theItem is disabled then
						set item (item 45 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants45_integrated_fade
					end if
				end if
 
				if item 46 of propertiesToColumnsRef is not 0 and theType is in index46_lock_fade_to_cue then
					set theItem to lock fade to cue of eachCue
					if theItem is enabled then
						set item (item 46 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants46_lock_fade_to_cue
					else if theItem is disabled then
						set item (item 46 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants46_lock_fade_to_cue
					end if
				end if
 
				if item 47 of propertiesToColumnsRef is not 0 and theType is in index47_pitch_shift then
					set theItem to pitch shift of eachCue
					if theItem is enabled then
						set item (item 47 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants47_pitch_shift
					else if theItem is disabled then
						set item (item 47 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants47_pitch_shift
					end if
				end if
 
				if item 48 of propertiesToColumnsRef is not 0 and theType is in index48_slice_markers then
					set theItem to slice markers of eachCue
					set item (item 48 of propertiesToColumnsRef) of thePropertiesRef ¬
						to my recordToDelimitedText(theItem, userSliceRecordColumnDelimiter, userSliceRecordRowDelimiter)
				end if
 
				if item 49 of propertiesToColumnsRef is not 0 and theType is in index49_last_slice_play_count then
					set theItem to last slice play count of eachCue as text
					set item (item 49 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 50 of propertiesToColumnsRef is not 0 and theType is in index50_last_slice_infinite_loop then
					set theItem to last slice infinite loop of eachCue
					if theItem is true then
						set item (item 50 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants50_last_slice_infinite_loop
					else if theItem is false then
						set item (item 50 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants50_last_slice_infinite_loop
					end if
				end if
 
				if item 51 of propertiesToColumnsRef is not 0 and theType is in index51_layer then
					set theItem to layer of eachCue as text
					if theItem is "1000" then
						set theItem to userLayerThousandIsTop
					else if theItem is "0" then
						set theItem to userLayerZeroIsBottom
					end if
					set item (item 51 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 52 of propertiesToColumnsRef is not 0 and theType is in index52_full_surface then
					set theItem to full surface of eachCue
					if theItem is true then
						set item (item 52 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants52_full_surface
					else if theItem is false then
						set item (item 52 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants52_full_surface
					end if
				end if
 
				if item 53 of propertiesToColumnsRef is not 0 and theType is in index53_preserve_aspect_ratio then
					set theItem to preserve aspect ratio of eachCue
					if theItem is true then
						set item (item 53 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants53_preserve_aspect_ratio
					else if theItem is false then
						set item (item 53 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants53_preserve_aspect_ratio
					end if
				end if
 
				if item 54 of propertiesToColumnsRef is not 0 and theType is in index54_opacity then
					set theItem to opacity of eachCue
					if userConvertOpacityToPercent is true then
						set item (item 54 of propertiesToColumnsRef) of thePropertiesRef to ((round (100 * theItem) rounding toward zero) as text) & "%"
					else
						set item (item 54 of propertiesToColumnsRef) of thePropertiesRef to theItem as text
					end if
				end if
 
				if item 55 of propertiesToColumnsRef is not 0 and theType is in index55_translation_x then
					set theItem to translation x of eachCue as text
					set item (item 55 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 56 of propertiesToColumnsRef is not 0 and theType is in index56_translation_y then
					set theItem to translation y of eachCue as text
					set item (item 56 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 57 of propertiesToColumnsRef is not 0 and theType is in index57_scale_x then
					set theItem to scale x of eachCue as text
					set item (item 57 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 58 of propertiesToColumnsRef is not 0 and theType is in index58_scale_y then
					set theItem to scale y of eachCue as text
					set item (item 58 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 59 of propertiesToColumnsRef is not 0 and theType is in index59_do_video_effect then
					set theItem to do video effect of eachCue
					if theItem is true then
						set item (item 59 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants59_do_video_effect
					else if theItem is false then
						set item (item 59 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants59_do_video_effect
					end if
				end if
 
				if item 60 of propertiesToColumnsRef is not 0 and theType is in index60_custom_quartz_file then
					set theItem to custom quartz file of eachCue
					if theItem is missing value then
						set targetTitle to userMissingQuartzFile
					else
						set targetTitle to POSIX path of theItem as text
					end if
					set item (item 60 of propertiesToColumnsRef) of thePropertiesRef to targetTitle
				end if
 
				if item 61 of propertiesToColumnsRef is not 0 and theType is in index61_hold_at_end then
					set theItem to hold at end of eachCue
					if theItem is true then
						set item (item 61 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants61_hold_at_end
					else if theItem is false then
						set item (item 61 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants61_hold_at_end
					end if
				end if
 
				if item 62 of propertiesToColumnsRef is not 0 and theType is in index62_text then
					set theItem to text of eachCue as text
					set item (item 62 of propertiesToColumnsRef) of thePropertiesRef to my noReturns(my noTabs(theItem))
				end if
 
				if item 63 of propertiesToColumnsRef is not 0 and theType is in index63_text_format then
					set theItem to text format of eachCue as list
					set coercedRecords to {}
					repeat with eachRecord in theItem
						set end of coercedRecords to my coerceTextFormatRecord(eachRecord)
					end repeat
					set item (item 63 of propertiesToColumnsRef) of thePropertiesRef to my listToDelimitedText(coercedRecords, userTextFormatDelimiter)
				end if
 
				if item 64 of propertiesToColumnsRef is not 0 and theType is in index64_text_alignment then
					set theItem to text alignment of eachCue as text
					set item (item 64 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 65 of propertiesToColumnsRef is not 0 and theType is in index65_camera_patch then
					set theItem to camera patch of eachCue as text
					set item (item 65 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 66 of propertiesToColumnsRef is not 0 and theType is in index66_audio_fade_mode then
					set theItem to audio fade mode of eachCue
					if theItem is absolute then
						set item (item 66 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants66_audio_fade_mode
					else if theItem is relative then
						set item (item 66 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants66_audio_fade_mode
					end if
				end if
 
				if item 67 of propertiesToColumnsRef is not 0 and theType is in index67_video_fade_mode then
					set theItem to video fade mode of eachCue
					if theItem is absolute then
						set item (item 67 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants67_video_fade_mode
					else if theItem is relative then
						set item (item 67 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants67_video_fade_mode
					end if
				end if
 
				if item 68 of propertiesToColumnsRef is not 0 and theType is in index68_stop_target_when_done then
					set theItem to stop target when done of eachCue
					if theItem is true then
						set item (item 68 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants68_stop_target_when_done
					else if theItem is false then
						set item (item 68 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants68_stop_target_when_done
					end if
				end if
 
				if item 69 of propertiesToColumnsRef is not 0 and theType is in index69_rotation_type then
					set theItem to rotation type of eachCue
					set item (item 69 of propertiesToColumnsRef) of thePropertiesRef to my lookUpBespoke(theItem, translation69_rotation_type)
				end if
 
				if item 70 of propertiesToColumnsRef is not 0 and theType is in index70_rotation then
					set theItem to rotation of eachCue as text
					set item (item 70 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 71 of propertiesToColumnsRef is not 0 and theType is in index71_do_opacity then
					set theItem to do opacity of eachCue
					if theItem is true then
						set item (item 71 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants71_do_opacity
					else if theItem is false then
						set item (item 71 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants71_do_opacity
					end if
				end if
 
				if item 72 of propertiesToColumnsRef is not 0 and theType is in index72_do_translation then
					set theItem to do translation of eachCue
					if theItem is true then
						set item (item 72 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants72_do_translation
					else if theItem is false then
						set item (item 72 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants72_do_translation
					end if
				end if
 
				if item 73 of propertiesToColumnsRef is not 0 and theType is in index73_do_rotation then
					set theItem to do rotation of eachCue
					if theItem is true then
						set item (item 73 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants73_do_rotation
					else if theItem is false then
						set item (item 73 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants73_do_rotation
					end if
				end if
 
				if item 74 of propertiesToColumnsRef is not 0 and theType is in index74_do_scale then
					set theItem to do scale of eachCue
					if theItem is true then
						set item (item 74 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants74_do_scale
					else if theItem is false then
						set item (item 74 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants74_do_scale
					end if
				end if
 
				if item 75 of propertiesToColumnsRef is not 0 and theType is in index75_command_text then
					set theItem to command text of eachCue as text
					set item (item 75 of propertiesToColumnsRef) of thePropertiesRef to my noReturns(my noTabs(theItem))
				end if
 
				if item 76 of propertiesToColumnsRef is not 0 and theType is in index76_always_collate then
					set theItem to always collate of eachCue
					if theItem is true then
						set item (item 76 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants76_always_collate
					else if theItem is false then
						set item (item 76 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants76_always_collate
					end if
				end if
 
				if item 77 of propertiesToColumnsRef is not 0 and theType is in index77_message_type then
					set theItem to message type of eachCue
					if theItem is voice then
						set item (item 77 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants77_message_type
					else if theItem is msc then
						set item (item 77 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants77_message_type
					else if theItem is sysex then
						set item (item 77 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants77_message_type
					end if
				end if
 
				if item 78 of propertiesToColumnsRef is not 0 and theType is in index78_command then
					set theItem to command of eachCue
					if theItem is note_on then
						set item (item 78 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants78_command
					else if theItem is note_off then
						set item (item 78 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants78_command
					else if theItem is program_change then
						set item (item 78 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants78_command
					else if theItem is control_change then
						set item (item 78 of propertiesToColumnsRef) of thePropertiesRef to item 4 of constants78_command
					else if theItem is key_pressure then
						set item (item 78 of propertiesToColumnsRef) of thePropertiesRef to item 5 of constants78_command
					else if theItem is channel_pressure then
						set item (item 78 of propertiesToColumnsRef) of thePropertiesRef to item 6 of constants78_command
					else if theItem is pitch_bend then
						set item (item 78 of propertiesToColumnsRef) of thePropertiesRef to item 7 of constants78_command
					end if
				end if
 
				if item 79 of propertiesToColumnsRef is not 0 and theType is in index79_channel then
					set theItem to channel of eachCue as text
					set item (item 79 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 80 of propertiesToColumnsRef is not 0 and theType is in index80_byte_one then
					if command of eachCue is not pitch_bend then
						set theItem to byte one of eachCue as text
						set item (item 80 of propertiesToColumnsRef) of thePropertiesRef to theItem
					end if
				end if
 
				if item 81 of propertiesToColumnsRef is not 0 and theType is in index81_byte_two then
					if command of eachCue is not pitch_bend and command of eachCue is not program_change and ¬
						command of eachCue is not channel_pressure then
						set theItem to byte two of eachCue as text
						set item (item 81 of propertiesToColumnsRef) of thePropertiesRef to theItem
					end if
				end if
 
				if item 82 of propertiesToColumnsRef is not 0 and theType is in index82_byte_combo then
					if command of eachCue is pitch_bend then
						set theItem to ((byte combo of eachCue) - 8192) as text -- Pitch bend of 0 in the Inspector is reported to AppleScript as 8192
						set item (item 82 of propertiesToColumnsRef) of thePropertiesRef to theItem
					end if
				end if
 
				if item 83 of propertiesToColumnsRef is not 0 and theType is in index83_end_value then
					if command of eachCue is not pitch_bend then
						set theItem to end value of eachCue as text
					else
						set theItem to ((end value of eachCue) - 8192) as text -- Pitch bend of 0 in the Inspector is reported to AppleScript as 8192
					end if
					set item (item 83 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 84 of propertiesToColumnsRef is not 0 and theType is in index84_fade then
					set theItem to fade of eachCue
					if theItem is enabled then
						set item (item 84 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants84_fade
					else if theItem is disabled then
						set item (item 84 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants84_fade
					end if
				end if
 
				if item 85 of propertiesToColumnsRef is not 0 and theType is in index85_deviceID then
					set theItem to deviceID of eachCue as text
					set item (item 85 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 86 of propertiesToColumnsRef is not 0 and theType is in index86_command_format then
					set theItem to command format of eachCue
					set item (item 86 of propertiesToColumnsRef) of thePropertiesRef to my lookUpBespoke(theItem, translation86_command_format)
				end if
 
				if item 87 of propertiesToColumnsRef is not 0 and theType is in index87_command_number then
					set theItem to command number of eachCue
					set item (item 87 of propertiesToColumnsRef) of thePropertiesRef to my lookUpBespoke(theItem, translation87_command_number)
				end if
 
				if item 88 of propertiesToColumnsRef is not 0 and theType is in index88_q__number then
					set theItem to q_number of eachCue as text
					set item (item 88 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 89 of propertiesToColumnsRef is not 0 and theType is in index89_q__list then
					set theItem to q_list of eachCue as text
					set item (item 89 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 90 of propertiesToColumnsRef is not 0 and theType is in index90_q__path then
					set theItem to q_path of eachCue as text
					set item (item 90 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 91 of propertiesToColumnsRef is not 0 and theType is in index91_macro then
					set theItem to macro of eachCue as text
					set item (item 91 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 92 of propertiesToColumnsRef is not 0 and theType is in index92_control_number then
					set theItem to control number of eachCue as text
					set item (item 92 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 93 of propertiesToColumnsRef is not 0 and theType is in index93_control_value then
					set theItem to control value of eachCue as text
					set item (item 93 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 94 of propertiesToColumnsRef is not 0 and theType is in index94_hours then
					set theItem to hours of eachCue as text
					set item (item 94 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 95 of propertiesToColumnsRef is not 0 and theType is in index95_minutes then
					set theItem to minutes of eachCue as text
					set item (item 95 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 96 of propertiesToColumnsRef is not 0 and theType is in index96_seconds then
					set theItem to seconds of eachCue as text
					set item (item 96 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 97 of propertiesToColumnsRef is not 0 and theType is in index97_frames then
					set theItem to frames of eachCue as text
					set item (item 97 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 98 of propertiesToColumnsRef is not 0 and theType is in index98_subframes then
					set theItem to subframes of eachCue as text
					set item (item 98 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 99 of propertiesToColumnsRef is not 0 and theType is in index99_send_time_with_set then
					set theItem to send time with set of eachCue
					if theItem is true then
						set item (item 99 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants99_send_time_with_set
					else if theItem is false then
						set item (item 99 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants99_send_time_with_set
					end if
				end if
 
				if item 100 of propertiesToColumnsRef is not 0 and theType is in index100_sysex_message then
					set theItem to sysex message of eachCue as text
					set item (item 100 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 101 of propertiesToColumnsRef is not 0 and theType is in index101_osc_message_type then
					set theItem to osc message type of eachCue
					if theItem is qlab then
						set item (item 101 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants101_osc_message_type
					else if theItem is custom then
						set item (item 101 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants101_osc_message_type
					else if theItem is udp then
						set item (item 101 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants101_osc_message_type
					end if
				end if
 
				if item 102 of propertiesToColumnsRef is not 0 and theType is in index102_q_num then
					set theItem to q_num of eachCue as text
					set item (item 102 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 103 of propertiesToColumnsRef is not 0 and theType is in index103_q_command then
					set theItem to q_command of eachCue
					set item (item 103 of propertiesToColumnsRef) of thePropertiesRef to my lookUpBespoke(theItem, translation103_q_command)
				end if
 
				if item 104 of propertiesToColumnsRef is not 0 and theType is in index104_q_params then
					set theItem to q_params of eachCue as text
					set item (item 104 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 105 of propertiesToColumnsRef is not 0 and theType is in index105_custom_message then
					set theItem to custom message of eachCue as text
					set item (item 105 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 106 of propertiesToColumnsRef is not 0 and theType is in index106_udp_message then
					set theItem to udp message of eachCue as text
					set item (item 106 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 107 of propertiesToColumnsRef is not 0 and theType is in index107_start_time_offset then
					-- ###FIXME### It would be nice to format this as HH:MM:SS:FF or HH:MM:SS;FF, but smpte format property broken in 4.1.6
					set theItem to start time offset of eachCue as text
					set item (item 107 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 108 of propertiesToColumnsRef is not 0 and theType is in index108_fire_next_cue_when_slice_ends then
					set theItem to fire next cue when slice ends of eachCue
					if theItem is true then
						set item (item 108 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants108_fire_next_cue_when_slice_ends
					else if theItem is false then
						set item (item 108 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants108_fire_next_cue_when_slice_ends
					end if
				end if
 
				if item 109 of propertiesToColumnsRef is not 0 and theType is in index109_stop_target_when_slice_ends then
					set theItem to stop target when slice ends of eachCue
					if theItem is true then
						set item (item 109 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants109_stop_target_when_slice_ends
					else if theItem is false then
						set item (item 109 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants109_stop_target_when_slice_ends
					end if
				end if
 
				if item 110 of propertiesToColumnsRef is not 0 and theType is in index110_load_time then
					set theItem to load time of eachCue
					set item (item 110 of propertiesToColumnsRef) of thePropertiesRef to my makeHHMMSSsss(theItem)
				end if
 
				if item 111 of propertiesToColumnsRef is not 0 and theType is in index111_assigned_number then
					set theItem to assigned number of eachCue as text
					set item (item 111 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 112 of propertiesToColumnsRef is not 0 and theType is in index112_script_source then
					set theItem to script source of eachCue as text
					set item (item 112 of propertiesToColumnsRef) of thePropertiesRef to my noReturns(my noTabs(theItem))
				end if
 
				if item 113 of propertiesToColumnsRef is not 0 then -- index113_parent
					set cueListDetector to cue id "[root group of cue lists]" -- Cue lists & carts have this as their parent
					set theItem to parent of eachCue
					if theItem is cueListDetector then -- Cue is a cue list
						set targetTitle to userCueListsAreOrphans
					else
						set targetTitle to (q name of theItem) as text
						if targetTitle is "" then
							set targetTitle to (q number of theItem) as text
							if targetTitle is "" then
								set targetTitle to "id: " & (uniqueID of theItem) as text
							end if
						end if
					end if
					if parent of theItem is cueListDetector then -- Parent is a cue list
						set targetTitle to item 1 of userParentIsCueListBrackets & targetTitle & item 2 of userParentIsCueListBrackets
					end if
					set item (item 113 of propertiesToColumnsRef) of thePropertiesRef to targetTitle
				end if
 
				if item 114 of propertiesToColumnsRef is not 0 then -- index114_unique_ID
					set theItem to uniqueID of eachCue as text
					set item (item 114 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 115 of propertiesToColumnsRef is not 0 then -- index115_q_list_name
					set theItem to q list name of eachCue as text
					set item (item 115 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 116 of propertiesToColumnsRef is not 0 then -- index116_q_display_name
					set theItem to q display name of eachCue as text
					set item (item 116 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 117 of propertiesToColumnsRef is not 0 then -- index117_q_default_name
					set theItem to q default name of eachCue as text
					set item (item 117 of propertiesToColumnsRef) of thePropertiesRef to my noTabs(theItem)
				end if
 
				if item 118 of propertiesToColumnsRef is not 0 then -- index118_broken
					set theItem to broken of eachCue
					if theItem is true then
						set item (item 118 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants118_broken
					else if theItem is false then
						set item (item 118 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants118_broken
					end if
				end if
 
				if item 119 of propertiesToColumnsRef is not 0 and theType is in index119_audio_input_channels then
					set theItem to audio input channels of eachCue as text
					set item (item 119 of propertiesToColumnsRef) of thePropertiesRef to theItem
				end if
 
				if item 120 of propertiesToColumnsRef is not 0 and theType is in index120_start_value then
					set theItem to end value of eachCue
					if theItem is not missing value then
						if command of eachCue is not pitch_bend then
							set item (item 120 of propertiesToColumnsRef) of thePropertiesRef to theItem as text
						else
							set item (item 120 of propertiesToColumnsRef) of thePropertiesRef to (theItem - 8192) as text
							-- Pitch bend of 0 in the Inspector is reported to AppleScript as 8192
						end if
					end if
				end if
 
				if theType is in index_takesLevel then
					if theType is not "Fade" then -- This bit stops the script from reporting rows that aren't valid
						set audioInputs to audio input channels of eachCue
					else
						if broken of eachCue is false then
							try -- This protects against the cue target being a Group Cue
								set audioInputs to audio input channels of cue target of eachCue
							on error
								set audioInputs to qLabMaxAudioInputs
							end try
						else
							set audioInputs to qLabMaxAudioInputs
						end if
					end if
					set AppleScript's text item delimiters to ","
					repeat with eachLevelColumn in levelColumns
						set crossPoint to item eachLevelColumn of headerRowRef
						set theRow to text item 1 of crossPoint as integer
						if theRow > audioInputs then
							set theLevel to userIrrelevantCrosspoints
						else
							set theColumn to text item 2 of crossPoint as integer
							set theLevel to eachCue getLevel row theRow column theColumn as number
							if theLevel ≤ item 1 of userMinusInfinity then
								set theLevel to item 2 of userMinusInfinity
							end if
						end if
						set item eachLevelColumn of thePropertiesRef to theLevel
					end repeat
					set AppleScript's text item delimiters to tab
				end if
 
				if theType is in index_takesGang then
					if theType is not "Fade" then -- This bit stops the script from reporting rows that aren't valid
						set audioInputs to audio input channels of eachCue
					else
						if broken of eachCue is false then
							try -- This protects against the cue target being a Group Cue
								set audioInputs to audio input channels of cue target of eachCue
							on error
								set audioInputs to qLabMaxAudioInputs
							end try
						else
							set audioInputs to qLabMaxAudioInputs
						end if
					end if
					set AppleScript's text item delimiters to "^"
					repeat with eachGangColumn in gangColumns
						set crossPoint to item eachGangColumn of headerRowRef
						set theRow to text item 1 of crossPoint as integer
						if theRow > audioInputs then
							set theGang to userIrrelevantCrosspoints
						else
							set theColumn to text item 2 of crossPoint as integer
							set theGang to eachCue getGang row theRow column theColumn as text
						end if
						if theGang is not missing value then
							set item eachGangColumn of thePropertiesRef to theGang
						end if
					end repeat
					set AppleScript's text item delimiters to tab
				end if
 
				-- Add thePropertiesRef to the end of theText
 
				set AppleScript's text item delimiters to tab
				set theText to theText & return & thePropertiesRef as text
				set AppleScript's text item delimiters to ""
 
				-- Countdown timer (and opportunity to escape)
 
				if i mod userEscapeHatchInterval is 0 and (countCues - i) > userEscapeHatchInterval / 2 then -- Countdown timer (and opportunity to escape)
					tell me to countdownTimer(i, countCues, "cues") -- This form avoids privileges error getting current date within an application tell block
				end if
 
			end repeat
 
		end tell
 
	end tell
 
	-- Write the text back out to the file
 
	makeFileFromText(outputFile, theText)
 
	-- All done. Hoopla!
 
	set timeTaken to round (current date) - startTime rounding as taught in school
	set timeString to makeNiceT(timeTaken)
	tell application id "com.figure53.QLab.4"
		activate
		set whatNext to button returned of (display dialog "Done." & return & return & "(That took " & timeString & ".)" with title dialogTitle with icon 1 ¬
			buttons {"Open in Excel", "Open in TextEdit", "Reveal in Finder"} default button "Reveal in Finder" giving up after 60)
		(* ### NO EXCEL ### Replace the lines above with these lines if you don't have Excel: 
	set whatNext to button returned of (display dialog "Done." & return & return & "(That took " & timeString & ".)" with title dialogTitle with icon 1 ¬
		buttons {"Open in TextEdit", "Reveal in Finder"} default button "Reveal in Finder" giving up after 60)
		*)
	end tell
 
	set AppleScript's text item delimiters to currentTIDs
 
	if whatNext is "Open in Excel" then
		try -- ### NO EXCEL ### Delete this try block (^^^ to here ^^^) if you don't have Excel
			tell application "Microsoft Excel"
				set openAllColumnsAsText to {}
				repeat with i from 1 to count headerRowRef
					set end of openAllColumnsAsText to {i, text format}
				end repeat
				launch -- This protects against a very odd error message you see if Excel wasn't running:
				(* "Sorry, we couldn't find ~/Library/Containers/com.microsoft.Excel/Data/name. Is it possible it was moved, renamed or deleted?" *)
				open file outputFile -- ###FIXME### This seems to be the only way of getting Microsoft Excel to allow "open text file" to work
				close workbook outputFilename
				activate
				open text file filename outputFile field info openAllColumnsAsText origin Macintosh with tab
			end tell
		on error
			display dialog "That didn't work for some reason…" with title dialogTitle with icon 0 buttons {"OK"} default button "OK"
		end try -- ### NO EXCEL ### ^^^ to here ^^^
	else if whatNext is "Open in TextEdit" then
		try
			tell application "TextEdit"
				activate
				open file outputFile
				set zoomed of front window to true
			end tell
		on error
			display dialog "That didn't work for some reason…" with title dialogTitle with icon 0 buttons {"OK"} default button "OK"
		end try
	else
		tell application "Finder"
			activate
			reveal outputFile
		end tell
	end if
 
on error number -128
	set AppleScript's text item delimiters to currentTIDs
end try
 
-- Subroutines
 
(* === INPUT === *)
 
on enterANumberWithRangeWithCustomButton(thePrompt, defaultAnswer, ¬
	lowRange, acceptEqualsLowRange, highRange, acceptEqualsHighRange, integerOnly, customButton, defaultButton) -- [Shared subroutine; minor mod]
	-- tell application id "com.figure53.QLab.4" [Minor mod]
	set theQuestion to ""
	repeat until theQuestion is not ""
		set {theQuestion, theButton} to {text returned, button returned} of (display dialog thePrompt with title dialogTitle ¬
			default answer defaultAnswer buttons (customButton as list) & {"Cancel", "OK"} default button defaultButton cancel button "Cancel")
		if theButton is customButton then
			set theAnswer to theButton
			exit repeat
		end if
		try
			if integerOnly is true then
				set theAnswer to theQuestion as integer -- Detects non-numeric strings
				if theAnswer as text is not theQuestion then -- Detects non-integer input
					set theQuestion to ""
				end if
			else
				set theAnswer to theQuestion as number -- Detects non-numeric strings
			end if
			if lowRange is not false then
				if acceptEqualsLowRange is true then
					if theAnswer < lowRange then
						set theQuestion to ""
					end if
				else
					if theAnswer ≤ lowRange then
						set theQuestion to ""
					end if
				end if
			end if
			if highRange is not false then
				if acceptEqualsHighRange is true then
					if theAnswer > highRange then
						set theQuestion to ""
					end if
				else
					if theAnswer ≥ highRange then
						set theQuestion to ""
					end if
				end if
			end if
		on error
			set theQuestion to ""
		end try
	end repeat
	return theAnswer
	-- end tell
end enterANumberWithRangeWithCustomButton
 
(* === OUTPUT === *)
 
on exitStrategy(theMessage, givingUp) -- [Shared subroutine]
	if theMessage is not false then
		if givingUp is not false then
			display dialog theMessage with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after givingUp
		else
			display dialog theMessage with title dialogTitle with icon 0 buttons {"OK"} default button "OK"
		end if
	end if
	set AppleScript's text item delimiters to currentTIDs
end exitStrategy
 
on startTheClock() -- [Shared subroutine]
	tell application id "com.figure53.QLab.4"
		display dialog "One moment caller…" with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 1
	end tell
	set startTime to current date
end startTheClock
 
on countdownTimer(thisStep, totalSteps, whichCuesString) -- [Shared subroutine]
	set timeTaken to round (current date) - startTime rounding as taught in school
	set timeString to my makeMSS(timeTaken)
	tell application id "com.figure53.QLab.4"
		if frontmost then
			display dialog "Time elapsed: " & timeString & " – " & thisStep & " of " & totalSteps & " " & whichCuesString & ¬
				" done…" with title dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 1
		end if
	end tell
end countdownTimer
 
(* === TIME === *)
 
on makeHHMMSSss(howLong) -- [Shared subroutine]
	set howManyHours to howLong div 3600
	if howManyHours is 0 then
		set hourString to ""
	else
		set hourString to my padNumber(howManyHours, 2) & ":"
	end if
	set howManyMinutes to (howLong mod 3600) div 60
	set minuteString to my padNumber(howManyMinutes, 2)
	set howManySeconds to howLong mod 60 div 1
	set secondString to my padNumber(howManySeconds, 2)
	set howManyFractionalSeconds to howLong mod 1
	set howManyRoundedSeconds to round 100 * howManyFractionalSeconds rounding as taught in school
	set fractionString to my padNumber(howManyRoundedSeconds, 2)
	return hourString & minuteString & ":" & secondString & "." & fractionString
end makeHHMMSSss
 
on makeHHMMSSsss(howLong) -- [Shared subroutine]
	set howManyHours to howLong div 3600
	if howManyHours is 0 then
		set hourString to ""
	else
		set hourString to my padNumber(howManyHours, 2) & ":"
	end if
	set howManyMinutes to (howLong mod 3600) div 60
	set minuteString to my padNumber(howManyMinutes, 2)
	set howManySeconds to howLong mod 60 div 1
	set secondString to my padNumber(howManySeconds, 2)
	set howManyFractionalSeconds to howLong mod 1
	set howManyRoundedSeconds to round 1000 * howManyFractionalSeconds rounding as taught in school
	set fractionString to my padNumber(howManyRoundedSeconds, 3)
	return hourString & minuteString & ":" & secondString & "." & fractionString
end makeHHMMSSsss
 
on makeMSS(howLong) -- [Shared subroutine]
	set howManyMinutes to howLong div 60
	set howManySeconds to howLong mod 60 div 1
	return (howManyMinutes as text) & ":" & my padNumber(howManySeconds, 2)
end makeMSS
 
on makeNiceT(howLong) -- [Shared subroutine]
	if howLong < 1 then
		return "less than a second"
	end if
	set howManyHours to howLong div 3600
	if howManyHours is 0 then
		set hourString to ""
	else if howManyHours is 1 then
		set hourString to "1 hour"
	else
		set hourString to (howManyHours as text) & " hours"
	end if
	set howManyMinutes to howLong mod 3600 div 60
	if howManyMinutes is 0 then
		set minuteString to ""
	else if howManyMinutes is 1 then
		set minuteString to "1 minute"
	else
		set minuteString to (howManyMinutes as text) & " minutes"
	end if
	set howManySeconds to howLong mod 60 div 1
	if howManySeconds is 0 then
		set secondString to ""
	else if howManySeconds is 1 then
		set secondString to "1 second"
	else
		set secondString to (howManySeconds as text) & " seconds"
	end if
	set theAmpersand to ""
	if hourString is not "" then
		if minuteString is not "" and secondString is not "" then
			set theAmpersand to ", "
		else if minuteString is not "" or secondString is not "" then
			set theAmpersand to " and "
		end if
	end if
	set theOtherAmpersand to ""
	if minuteString is not "" and secondString is not "" then
		set theOtherAmpersand to " and "
	end if
	return hourString & theAmpersand & minuteString & theOtherAmpersand & secondString
end makeNiceT
 
(* === DATA WRANGLING === *)
 
on lookUpBespoke(lookUpValue, lookUpTable)
	if lookUpValue is not in lookUpTable then
		return lookUpValue & " (no match)"
	end if
	repeat with i from 1 to (count lookUpTable) by 2
		if lookUpValue is item i of lookUpTable then
			return item (i + 1) of lookUpTable
		end if
	end repeat
end lookUpBespoke
 
(* === TEXT WRANGLING === *)
 
on listToDelimitedText(theList, theDelimiter) -- [Shared subroutine]
	set passedTIDs to AppleScript's text item delimiters
	set AppleScript's text item delimiters to theDelimiter
	set delimitedText to theList as text
	set AppleScript's text item delimiters to passedTIDs
	return delimitedText
end listToDelimitedText
 
on padNumber(theNumber, minimumDigits) -- [Shared subroutine]
	set paddedNumber to theNumber as text
	repeat while (count paddedNumber) < minimumDigits
		set paddedNumber to "0" & paddedNumber
	end repeat
	return paddedNumber
end padNumber
 
on recordToDelimitedText(theRecord, theColumnDelimiter, theRowDelimiter) -- [Shared subroutine]
	set passedTIDs to AppleScript's text item delimiters
	set delimitedList to {}
	set AppleScript's text item delimiters to theColumnDelimiter
	repeat with eachItem in theRecord
		set end of delimitedList to (eachItem as list) as text
	end repeat
	set AppleScript's text item delimiters to theRowDelimiter
	set delimitedText to delimitedList as text
	set AppleScript's text item delimiters to passedTIDs
	return delimitedText
end recordToDelimitedText
 
on sortTextIgnoringCase(theText) -- [Shared subroutine]
	return do shell script "echo " & quoted form of theText & " | sort -f "
end sortTextIgnoringCase
 
on noTabs(dirtyText)
	set passedTIDs to AppleScript's text item delimiters
	set AppleScript's text item delimiters to tab
	set dirtyList to text items of dirtyText
	set AppleScript's text item delimiters to userTabCharacterSubstitute
	set cleanText to dirtyList as text
	set AppleScript's text item delimiters to passedTIDs
	return cleanText
end noTabs
 
on noReturns(dirtyText)
	set passedTIDs to AppleScript's text item delimiters
	set dirtyParas to paragraphs of dirtyText
	set AppleScript's text item delimiters to userCarriageReturnsInLongText
	set cleanText to dirtyParas as text
	set AppleScript's text item delimiters to passedTIDs
	return cleanText
end noReturns
 
on coerceTextFormatRecord(theRecord) -- ###FIXME### No customisation implemented; ugly solution!
	tell application id "com.figure53.QLab.4" -- Needs to be inside tell block so that record labels are understood (they're in QLab's sdef)
		set passedTIDs to AppleScript's text item delimiters
		set formatList to {"rangeOffset:" & rangeOffset of range of theRecord, ¬
			"rangeLength:" & rangeLength of range of theRecord, ¬
			"fontFamily:" & fontFamily of theRecord, ¬
			"fontName:" & fontName of theRecord, ¬
			"fontSize:" & fontSize of theRecord, ¬
			"fontStyle:" & fontStyle of theRecord, ¬
			"red:" & red of rgbaColor of theRecord, ¬
			"alpha:" & alpha of rgbaColor of theRecord, ¬
			"blue:" & blue of rgbaColor of theRecord, ¬
			"green:" & green of rgbaColor of theRecord, ¬
			"lineSpacing:" & lineSpacing of theRecord}
		set AppleScript's text item delimiters to "; "
		set formatText to ("{" & formatList as text) & "}"
		set AppleScript's text item delimiters to passedTIDs
		return formatText
	end tell
end coerceTextFormatRecord
 
(* === FILES === *)
 
on makeFileFromText(outputFilePath, fileContents) -- [Shared subroutine]
	copy (open for access outputFilePath with write permission) to theOpenFile
	set eof theOpenFile to 0 -- Clear it out first (just in case it already existed)
	write fileContents to theOpenFile
	close access theOpenFile
end makeFileFromText
 
on offerToSave(theMessage, theButtons, defaultButton, cancelButton, saveButton, textToSave, theAppendix)
	if button returned of (display dialog theMessage with title dialogTitle with icon 1 ¬
		buttons theButtons default button defaultButton cancel button cancelButton) is saveButton then
		if userFileTimestampFormat is false then
			set fileTimeString to ""
		else
			set fileTimeString to " | " & my grabTimeForFilenameBespoke()
		end if
		set savedFile to ((path to desktop) as text) & "QLab | " & dialogTitle & " | " & theAppendix & fileTimeString & ".txt"
		my checkForFile(savedFile)
		my makeFileFromText(savedFile, textToSave)
		tell application "Finder"
			reveal savedFile
			activate
		end tell
		delay 1
		activate
	end if
end offerToSave
 
on grabTimeForFilenameBespoke()
	return do shell script "date " & quoted form of userFileTimestampFormat
end grabTimeForFilenameBespoke
 
on checkForFile(theFile)
	tell application "System Events"
		set fileExists to exists file theFile
	end tell
	if fileExists is true then
		display dialog "The output file already exists…" with title dialogTitle with icon 1 ¬
			buttons {"Abort", "Overwrite"} default button "Overwrite" cancel button "Abort"
	end if
end checkForFile
 
(* END: Make a text file from cues *)
home.txt · Page views to date: 40309 · Last modified: 2021/08/02 16:34 (External edit)