====== 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 ==== Navigation ==== === 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 === Navigate cues with shared Cue Target === 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: [[home#Make a soundcheck sequence|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 *)