QLab Script - make cues from a text file

Contents

Table of Contents

    This is an "external script" - see QLab Scripts and Macros

     
    (* Make cues from a text file: add cues from a tab-delimited text file to a workspace in QLab based on the data in the file;
        see second & third display dialogs for further explanation
     
    This script is not designed to be run from within QLab!
     
    v0.0: 06/10/09 Rich Walsh (very early, barely-written, proof of concept)
    v0.5: 06/10/09 Starting to finish basic shape; have cracked groups & levels (I think)
    v0.9: 07/10/09 Fixed a couple of mistakes; finished creating all commands
    v0.9.1: 12/10/09 Snow Leopard can't "get running", so rewrote a sequence; also added some more instructions and the option to set
            a common root path for media; corrected some more mistakes; changed ETA
    v0.9.2: 16/10/09 Now "tested" in Snow Leopard; expanded makeNiceT for hours; general tidying; updates for QLab 2.2.5; new ETA figures
    v0.9.3: 27/10/09 Added MSC translation; new note about file targets
    v1.0: 11/01/10 Added better text-cleaning routine; "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
     
    <<< Last tested with: QLab 2.2.6; Mac OS 10.5.8 & 10.6.2 >>>
     
    <<< A LITTLE DISCLAIMER: This was quite a complex bit of work for me, so I make no guarantees whatsoever that it will work for you: if it does, use it;
        if it doesn't, I'd like to know, but I may choose not to do anything about it... >>>
     
    ***CUSTOMISATION***
     
    If you would prefer not to use the terms from QLab's AppleScript dictionary it is relatively painless to personalise this script
    (although 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 the strings that you need to use in the first row of your text file to associate a column with a property,
    edit the relevant entry in the "set acceptableColumnHeaders" line, eg: change "continue mode" to "f/on" if you prefer
    (just don't use commas - they identify level-setting columns).
     
    To change the strings that you need to use in the rows to call a QLab constant, edit the contents of the relevant
    "set constantsXXX" line, eg: change "set constants10_continue_mode" entries from "do_not_continue", "auto_continue" & "auto_follow"
    to "no", "a/c" & "a/f" if you prefer. *)
     
    -- ###FIXME### See 2x embedded comments in script
    -- ###FIXME### QLab throws an error when trying to get/set smpte format for an MSC Cue (checked in 2.2.6 Build 1084)
    -- ###FIXME### QLab and/or the "script runner" is, generally, increasingly unresponsive to scripts the more cues there are in a workspace
    -- ###FIXME### Review dialogs in light of this (especially ETA)
    -- ###ADD### Skip level setting for rows beyond number of audio input channels (?)
    -- ###ADD### Maybe one day make it possible to make cuelists (NB: simpling changing the mode of a Group Cue to cue_list _doesn't_ do it!)
     
    -- Declarations
     
    global dialogTitle
    set dialogTitle to "Make cues from a text file"
     
    global currentTIDs
    set currentTIDs to AppleScript's text item delimiters
     
    set acceptableColumnHeaders to {"type", "q number", "q name", "notes", "cue target", "file target", "pre wait", "duration", "post wait", "continue mode", ¬
        "armed", "midi trigger", "midi command", "midi byte one", "midi byte two", "timecode trigger", "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", ¬
        "loop start time", "loop end time", "loop count", "infinite loop", "guarantee sync", "integrated fade", "fade mode", "stop target when done", "auto stop", "layer", ¬
        "full screen", "preserve aspect ratio", "opacity", "translation x", "translation y", "rotation", "scale x", "scale y", "scale locked", "custom rendering", "do opacity", ¬
        "do translation", "do rotation", "do scale", "command", "channel", "byte one", "byte two", "byte combo", "end value", "fade", "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", "midi destination", "start time offset", "fire next cue when loop ends", "stop target when loop ends", "load time", ¬
        "assigned number"} -- All possible properties (order is based on entries in QLab's AppleScript dictionary)
     
    set customColumnHeaders to {"put in group"} -- Additional columns this script will understand
    repeat with eachItem in customColumnHeaders
        copy eachItem as string to end of acceptableColumnHeaders
    end repeat
     
    set reportingOnlyColumns to {"unique ID", "broken", "audio input channels"} -- Not needed in this script; for reference {text, boolean, integer}
     
    set levelColumns to {} -- If a column header contains "," it will be added to this list
     
    set acceptableTypes to {"Group", "Audio", "Fade", "Video", "Animation", "Camera", "MIDI", "MSC", "MIDI SysEx", "MTC", "Start", "Stop", "Pause", "Reset", ¬
        "Devamp", "Load", "Script", "Goto", "Target", "Wait", "Memo", "Arm", "Disarm", "SoundMan"} -- The script will not process "Cue List" as it is too cumbersome
     
    -- This is a list of properties that can be set for each cue type
     
    -- set index2_q_number: every cue has q number (text)
    -- set index3_q_name: every cue has q name (text)
    -- set index4_notes: every cue has notes (text)
    set index5_cue_target to {"Fade", "Animation", "Start", "Stop", "Pause", "Reset", "Devamp", "Load", "Goto", "Target", "Arm", "Disarm"} -- (cue)
    set index6_file_target to {"Audio", "Video"} -- (file)
    -- set index7_pre_wait: every cue has pre wait (real)
    set index8_duration to {"Fade", "Animation", "Camera", "MIDI", "Wait"} -- Duration of Audio & Video cues can't be set, only read (real)
    -- set index9_post_wait: every cue has post wait (real)
    -- set index10_continue_mode: every cue has continue mode (constants)
    -- set index11_armed: every cue has armed (boolean)
    -- set index12_midi_trigger: every cue has midi trigger (constants)
    -- set index13_midi_command: every cue has midi command (constants)
    -- set index14_midi_byte_one: every cue has midi byte one (integer)
    -- set index15_midi_byte_two: every cue has midi byte two (integer)
    -- set index16_timecode_trigger: every cue has timecode trigger (constants)
    -- set index17_wall_clock_trigger: every cue has wall clock trigger (constants)
    -- set index18_wall_clock_hours: every cue has wall clock hours (integer)
    -- set index19_wall_clock_minutes: every cue has wall clock minutes (integer)
    -- set index20_wall_clock_seconds: every cue has wall clock seconds (integer)
    set index21_mode to {"Cue List", "Group"} -- (constants)
    set index22_sync_to_timecode to {"Cue List"} -- (constants)
    set index23_sync_mode to {"Cue List"} -- (constants)
    set index24_smpte_format to {"Cue List", "MTC"} -- (constants) ###FIXME### "MSC" removed from list as QLab throws an error
    set index25_mtc_sync_source_name to {"Cue List"} -- (text)
    set index26_ltc_sync_channel to {"Cue List"} -- (integer)
    set index27_patch to {"Audio", "Video", "Camera", "MIDI", "MSC", "MIDI SysEx"} -- (integer)
    set index28_start_time to {"Audio", "Video"} -- (real)
    set index29_end_time to {"Audio", "Video"} -- (real)
    set index30_loop_start_time to {"Audio"} -- (real)
    set index31_loop_end_time to {"Audio"} -- (real)
    set index32_loop_count to {"Audio"} -- (integer)
    set index33_infinite_loop to {"Audio", "Video"} -- (boolean)
    set index34_guarantee_sync to {"Audio"} -- (boolean)
    set index35_integrated_fade to {"Audio"} -- (constants)
    set index36_fade_mode to {"Fade"} -- (constants)
    set index37_stop_target_when_done to {"Fade", "Animation"} -- (boolean)
    set index38_auto_stop to {"Video"} -- (boolean)
    set index39_layer to {"Video", "Camera"} -- (integer)
    set index40_full_screen to {"Video", "Camera"} -- (boolean)
    set index41_preserve_aspect_ratio to {"Video", "Camera"} -- (boolean)
    set index42_opacity to {"Video", "Animation", "Camera"} -- (real)
    set index43_translation_x to {"Video", "Animation", "Camera"} -- (real)
    set index44_translation_y to {"Video", "Animation", "Camera"} -- (real)
    set index45_rotation to {"Video", "Animation", "Camera"} -- (real)
    set index46_scale_x to {"Video", "Animation", "Camera"} -- (real)
    set index47_scale_y to {"Video", "Animation", "Camera"} -- (real)
    set index48_scale_locked to {"Video", "Animation", "Camera"} -- (boolean)
    set index49_custom_rendering to {"Video", "Camera"} -- (boolean)
    set index50_do_opacity to {"Animation"} -- (boolean)
    set index51_do_translation to {"Animation"} -- (boolean)
    set index52_do_rotation to {"Animation"} -- (boolean)
    set index53_do_scale to {"Animation"} -- (boolean)
    set index54_command to {"MIDI"} -- (constants)
    set index55_channel to {"MIDI"} -- (integer)
    set index56_byte_one to {"MIDI"} -- (integer)
    set index57_byte_two to {"MIDI"} -- (integer)
    set index58_byte_combo to {"MIDI"} -- (integer)
    set index59_end_value to {"MIDI"} -- (integer)
    set index60_fade to {"MIDI"} -- (constants)
    set index61_command_format to {"MSC"} -- (integer - with translation)
    set index62_command_number to {"MSC"} -- (integer - with translation)
    set index63_q__number to {"MSC"} -- (text)
    set index64_q__list to {"MSC"} -- (text)
    set index65_q__path to {"MSC"} -- (text)
    set index66_macro to {"MSC"} -- (integer)
    set index67_control_number to {"MSC"} -- (integer)
    set index68_control_value to {"MSC"} -- (integer)
    set index69_hours to {"MSC"} -- (integer)
    set index70_minutes to {"MSC"} -- (integer)
    set index71_seconds to {"MSC"} -- (integer)
    set index72_frames to {"MSC"} -- (integer)
    set index73_subframes to {"MSC"} -- (integer)
    set index74_send_time_with_set to {"MSC"} -- (boolean)
    set index75_sysex_message to {"MIDI SysEx"} -- (text)
    set index76_midi_destination to {"MTC"} -- (text)
    set index77_start_time_offset to {"MTC"} -- (real)
    set index78_fire_next_cue_when_loop_ends to {"Devamp"} -- (boolean)
    set index79_stop_target_when_loop_ends to {"Devamp"} -- (boolean)
    set index80_load_time to {"Load"} -- (real)
    set index81_assigned_number to {"Target"} -- (text)
     
    set index_setLevel to {"Audio", "Fade", "Video"} -- Special private index for custom column headers
     
    -- This is a list of values for any constants (which can be used to customise the entries required in the text file)
     
    set constants10_continue_mode to {"do_not_continue", "auto_continue", "auto_follow"}
    set constants11_armed to {"true", "false"}
    set constants12_midi_trigger to {"enabled", "disabled"}
    set constants13_midi_command to {"note_on", "note_off", "program_change", "control_change", "key_pressure", "channel_pressure"}
    set constants16_timecode_trigger to {"enabled", "disabled"}
    set constants17_wall_clock_trigger to {"enabled", "disabled"}
    set constants21_mode to {"cue_list", "fire_first_enter_group", "fire_first_go_to_next_cue", "fire_all", "fire_random"}
    set constants22_sync_to_timecode to {"enabled", "disabled"}
    set constants23_sync_mode to {"mtc", "ltc"}
    set constants24_smpte_format to {"fps_24", "fps_25", "fps_30_drop", "fps_30_non_drop"}
    set constants33_infinite_loop to {"true", "false"}
    set constants34_guarantee_sync to {"true", "false"}
    set constants35_integrated_fade to {"enabled", "disabled"}
    set constants36_fade_mode to {"absolute", "relative"}
    set constants37_stop_target_when_done to {"true", "false"}
    set constants38_auto_stop to {"true", "false"}
    set constants40_full_screen to {"true", "false"}
    set constants41_preserve_aspect_ratio to {"true", "false"}
    set constants48_scale_locked to {"true", "false"}
    set constants49_custom_rendering to {"true", "false"}
    set constants50_do_opacity to {"true", "false"}
    set constants51_do_translation to {"true", "false"}
    set constants52_do_rotation to {"true", "false"}
    set constants53_do_scale to {"true", "false"}
    set constants54_command to {"note_on", "note_off", "program_change", "control_change", "key_pressure", "channel_pressure", "pitch_bend"}
    set constants60_fade to {"enabled", "disabled"}
    set constants74_send_time_with_set to {"true", "false"}
    set constants78_fire_next_cue_when_loop_ends to {"true", "false"}
    set constants79_stop_target_when_loop_ends to {"true", "false"}
     
    -- These variables are used to translate MSC integers into English
     
    set translation61_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 translation62_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"}
     
    -- General variables
     
    set propertiesToColumns to {}
    set annotateEveryLine to "No"
    set cuesToMove to {}
     
    try -- This overall try makes sure TIDs are reset 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 "This script will take a tab-delimited text file and attempt " & ¬
                            "to turn it into a set of cues in QLab's current cue list.
     
    Although it is relatively non-prescriptive in its approach, the script does ask a few things:
     
    1. The first row of the text file must contain the \"column headers\", ie: it should define which properties you are hoping to set in your cues. " & ¬
                            "Unless you customise the script (look inside it to find out more about that), these column headers must exactly match 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).
     
    2. You have to have a \"type\" column to tell the script what to make!
     
    3. Entries in the \"cells\" should also conform to QLab's AppleScript expectations, eg: \"do_not_continue\", \"auto_continue\" & \"auto_follow\" " & ¬
                            "for \"continue mode\"; again, these are customisable at script level. An exception to this is that MSC command formats & numbers " & ¬
                            "should be expressed as the text you see in the Inspector, not as the integers that AppleScript deals with.
     
    The script will make any kind of cue (except Cue Lists: too complicated; make a Group Cue instead and move it later) and set any kind of property " & ¬
                            "currently defined in the AppleScript hooks. File targets work if you paste the full path in \"POSIX\" form, ie: " & ¬
                            "\"/System/Library/Sounds/Basso.aiff\". Optionally, if all your media files share a common root path, you can choose this folder " & ¬
                            "and the script will add this to the file targets - saving you a bit of typing. Since a file target column is processed before most " & ¬
                            "other columns (regardless of the order in the text file), you don't need to worry about settings being reset when a file target changes...
     
    One obvious, but significant, limiting factor is that you can't of course target cues that don't exist yet - since the script trundles through your text file " & ¬
                            "in a linear way, making cues as it goes, if Q3 is supposed to arm Q4 that won't work because Q4 hasn't been made yet " & ¬
                            "when Q3 is made. Clear?
     
    The next page has some details about error protection, groups and levels..." 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
                            my toTheClipboard(acceptableColumnHeaders)
                        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 "There's not a lot of protection against you getting things " & ¬
                                "in your file wrong; there are no doubt hundreds of ways you can find to break the script. There is a basic error flag: \"!!!!\" " & ¬
                                "will be prepended to the q name of any cue when a process involved in making the cue went wrong; the line used to make the " & ¬
                                "cue will be copied to the cue's notes for reference: it's over to you to figure it out (trying to flag what exactly went wrong " & ¬
                                "is too complicated).
     
    The script is capable of moving cues into groups when it's finished making them all: add a column called \"put in group\" and enter the q number " & ¬
                                "of the destination group in that cell for the cues you wish to move. This only works if you have set q numbers for both " & ¬
                                "the cue and the group.
     
    In fact, quite a lot of the actions rely on cues having q numbers (eg: anything that requires setting a cue target). There are probably complex ways " & ¬
                                "round this, but for now you'll have to make do with using q numbers for most of the cues in the text file and then removing " & ¬
                                "those you don't want from the workspace. You can entertain yourself working out when you can get away without a q number! " & ¬
                                "All kinds of exciting things can go wrong if a q number is missing or invalid (eg: if you move a cue to a group that doesn't exist, " & ¬
                                "the cue vanishes...). Watch out for q numbers that already exist in the workspace as they will be unceremoniously dropped... 
     
    Since there are currently 833 possible scriptable levels for those cues that take them, it's up to you which levels to set in your file. " & ¬
                                "For any crosspoint you wish to set, add a column header of the form \"row,column\" and enter the levels 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). Watch out for default gangs in your workspace, " & ¬
                                "as they'll override any attempt to set levels independently.
     
    For the sake of simplicity, if you want \"-INF\" you'll need to put \"-120\" in your text file - although, since it's the default level, why bother? " & ¬
                                "Likewise, for the \"top\" layer, use \"1000\".
     
    The bigger the file the longer and longer the script takes, in a kind of exponential way (so you may want to split the file up into chunks, and do a bit at a time). " & ¬
                                "The speed also depends on the number of columns; with 200 lines setting 100 properties/levels expect it to take at least " & ¬
                                "15 minutes..." with title dialogTitle with icon 1 ¬
                                buttons {"<<< To page 1", "Copy headers to Clipboard", "Back to start"} default button "Get on with it")
                            if instructionButton2 is "Copy headers to Clipboard" then
                                my toTheClipboard(acceptableColumnHeaders)
                            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 count (every process whose name is "QLab")
        end tell
        if qLabIsRunning is 0 then
            display dialog "QLab is not running." with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after 5
            return
        end if
     
        -- Test for a workspace
     
        tell application "QLab"
            try
                get selected of front workspace
            on error
                display dialog "There is no workspace open in QLab." with title dialogTitle with icon 0 ¬
                    buttons {"OK"} default button "OK" giving up after 5
                return
            end try
        end tell
     
        -- Get the file
     
        set theFile to choose file with prompt "Please select a tab-delimited text file:" default location (path to desktop) without invisibles
     
        set AppleScript's text item delimiters to ""
     
        try
            set theText to read theFile
        on error
            my exitStrategy()
            return
        end try
     
        tell application "System Events" -- Get just the name of the file, without the extension
            set theExtension to name extension of theFile
            if theExtension is "" then
                set theName to name of theFile
            else
                set theFullName to name of theFile
                set theName to text 1 through ((length of theFullName) - (length of theExtension) - 1) of theFullName
            end if
        end tell
     
        -- Set up translation matrices
     
        set AppleScript's text item delimiters to tab
     
        try
            set headerRow to every text item of paragraph 1 of theText -- Pull headers from file
        on error
            my exitStrategy()
            return
        end try
     
        if "type" is not in headerRow then -- Won't know what cues to make without a type column!
            display dialog "The header row of this file doesn't contain the word \"type\", " & ¬
                "so I don't know which column to look in to specify what cues to make..." with title dialogTitle with icon 0 ¬
                buttons {"OK"} default button "OK"
            set AppleScript's text item delimiters to currentTIDs
            return
        end if
     
        repeat with i from 1 to count acceptableColumnHeaders -- Find which properties are in text file, and which column they are in
            copy 0 to end of propertiesToColumns
            repeat with j from 1 to count headerRow
                if item j of headerRow is item i of acceptableColumnHeaders then
                    set item i of propertiesToColumns to j
                end if
            end repeat
        end repeat
     
        set propertiesToColumnsRef to a reference to propertiesToColumns
     
        repeat with i from 1 to count headerRow -- Make a list of all columns flagged as levels
            if item i of headerRow contains "," then
                copy i to end of levelColumns
            end if
        end repeat
     
        set typeColumn to item 1 of propertiesToColumnsRef
     
        -- Add a root path?
     
        set setRootPath to button returned of (display dialog "Would you like me to prefix the file targets in your text file with a common root path?" with title ¬
            dialogTitle with icon 1 buttons {"Yes", "No"} default button "No")
     
        if setRootPath is "Yes" then
            set pathFinder to (choose folder with prompt ¬
                "Please select the folder that contains your audio & video files:" default location (path to home folder) without invisibles)
            set pathPrefix to POSIX path of pathFinder
        else
            set pathPrefix to ""
        end if
     
        -- Annotate every line?
     
        delay 0.5 -- Otherwise you don't notice the dialog has changed!
     
        set annotateEveryLine to button returned of ¬
            (display dialog "Would you like me to copy each line from the text file into the notes of the cue that is made from it?" with title ¬
                dialogTitle with icon 1 buttons {"Yes", "No"} default button "No")
     
        -- How long is this going to take?
     
        set countText to count paragraphs of theText
        set fudgeFactor to countText * ((count headerRow) / 100) -- Account for variable number of steps that will be run for each cue;
        (* testing was done with 100 headers *)
        -- ###FIXME### Current estimate based on second order polynomial interpolation of 3 sample data points (!): get more data
        set theETA to 70 + 0.25 * fudgeFactor + 0.019 * (fudgeFactor ^ 2)
        if theETA is greater than 40 then -- Don't waste 10s telling you it's going to take 30s!
            set timeString to my makeNiceT(theETA)
            set spuriousPlurals to " seconds"
            repeat with i from 10 to 1 by -1
                if i is 1 then
                    set spuriousPlurals to " second"
                end if
                set goOnThen to button returned of (display dialog (("There are " & countText as string) & " lines in this text file.
     
    Based on current tests, this may take about " & timeString & " to process, possibly longer. 
     
    You have " & i as string) & spuriousPlurals & " to hit \"Cancel\"..." with title dialogTitle with icon 0 ¬
                    buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 1)
                if goOnThen is "OK" then
                    exit repeat
                end if
            end repeat
        end if
     
        -- Now, to business
     
        tell application "QLab"
     
            activate
     
            display dialog "One moment caller..." with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 1
     
            set startTime to time of (current date)
     
            tell front workspace
     
                -- Record the header row, if required
     
                if annotateEveryLine is "Yes" then
                    set niceParagraph to my makeNiceParagraph(headerRow)
                    make type "Memo"
                    set newCue to last item of (selected as list)
                    set q name of newCue to "<<<< Header from text file >>>>"
                    set notes of newCue to "Line 1 of text file \"" & theName & "\": | " & niceParagraph & " |"
                end if
     
                repeat with i from 2 to countText -- Skip the header row
     
                    set theRecord to every text item of paragraph i of theText
     
                    set niceParagraph to my makeNiceParagraph(theRecord)
     
                    repeat while (count theRecord) is less than (count headerRow) -- Pad out any missing cells from end of line, just in case
                        copy "" as string to end of theRecord
                    end repeat
     
                    set theRecordRef to (a reference to theRecord)
     
                    try
                        set theType to item typeColumn of theRecordRef
                    on error
                        set theType to "Unacceptable" -- Flag it for the next stage
                    end try
     
                    if theType is not in acceptableTypes then
                        make type "Memo"
                        set newCue to last item of (selected as list)
                        set q name of newCue to "!!!! Unprocessed line !!!!"
                        set notes of newCue to "Unprocessed line " & i & " of text file \"" & theName & "\": | " & niceParagraph & " |"
                    else
                        make type theType
                        set newCue to last item of (selected as list)
     
                        if annotateEveryLine is "Yes" then
                            set notes of newCue to "Made from line " & i & " of text file \"" & theName & "\": | " & niceParagraph & " |"
                        end if
     
                        set ohDear to false -- A simple flag to detect problems (not worth the effort of trying to attach a code to show which property(s) failed)
     
                        -- These if…then clauses set the relevant property on newCue based on the contents of the text file
     
                        if item 2 of propertiesToColumnsRef is not 0 then -- index2_q_number
                            try -- Putting try round every step stops the whole thing grinding to a halt if a field has a rogue entry that QLab doesn't like
                                set theItem to item (item 2 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then -- Had to put all these in to stop empty lines being interpreted as "0" (etc)
                                    set q number of newCue to theItem
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 3 of propertiesToColumnsRef is not 0 then -- index3_q_name
                            try
                                set theItem to item (item 3 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set q name of newCue to theItem
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 4 of propertiesToColumnsRef is not 0 then -- index4_notes
                            try
                                set theItem to item (item 4 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then -- Prevents returns being added before line from text file if no notes given for cue
                                    set existingNotes to notes of newCue as string
                                    if existingNotes is not "missing value" then
                                        set notes of newCue to theItem & return & return & existingNotes
                                    else
                                        set notes of newCue to theItem
                                    end if
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index5_cue_target and item 5 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 5 of propertiesToColumnsRef) of theRecordRef as string
                                if theItem is not "" then
                                    set targetCue to (first cue whose q number is theItem)
                                    set cue target of newCue to targetCue
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index6_file_target and item 6 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 6 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    tell application "System Events"
                                        file (pathPrefix & theItem) -- Check file exists
                                    end tell
                                    set file target of newCue to (pathPrefix & theItem)
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 7 of propertiesToColumnsRef is not 0 then -- index7_pre_wait
                            try
                                set theItem to item (item 7 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set pre wait of newCue to theItem as real -- Try to protect against invalid entry 
                                    (* (anything that expects text should be fine with a string from theText;
                                    hopefully this qualifier means only numbers are passed when numbers are expected) *)
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index8_duration and item 8 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 8 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set duration of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 9 of propertiesToColumnsRef is not 0 then -- index9_post_wait
                            try
                                set theItem to item (item 9 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set post wait of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 10 of propertiesToColumnsRef is not 0 then -- index10_continue_mode
                            try
                                set theItem to item (item 10 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants10_continue_mode then
                                    set continue mode of newCue to do_not_continue
                                else if theItem is item 2 of constants10_continue_mode then
                                    set continue mode of newCue to auto_continue
                                else if theItem is item 3 of constants10_continue_mode then
                                    set continue mode of newCue to auto_follow
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 11 of propertiesToColumnsRef is not 0 then -- index11_armed
                            try
                                set theItem to item (item 11 of propertiesToColumnsRef) of theRecordRef -- This prevents empty entries being treated as "false"
                                if theItem is item 1 of constants11_armed then
                                    set armed of newCue to true
                                else if theItem is item 2 of constants11_armed then
                                    set armed of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 12 of propertiesToColumnsRef is not 0 then -- index12_midi_trigger
                            try
                                set theItem to item (item 12 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants12_midi_trigger then
                                    set midi trigger of newCue to enabled
                                else if theItem is item 2 of constants12_midi_trigger then
                                    set midi trigger of newCue to disabled
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 13 of propertiesToColumnsRef is not 0 then -- index13_midi_command
                            try
                                set theItem to item (item 13 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants13_midi_command then
                                    set midi command of newCue to note_on
                                else if theItem is item 2 of constants13_midi_command then
                                    set midi command of newCue to note_off
                                else if theItem is item 3 of constants13_midi_command then
                                    set midi command of newCue to program_change
                                else if theItem is item 4 of constants13_midi_command then
                                    set midi command of newCue to control_change
                                else if theItem is item 5 of constants13_midi_command then
                                    set midi command of newCue to key_pressure
                                else if theItem is item 6 of constants13_midi_command then
                                    set midi command of newCue to channel_pressure
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 14 of propertiesToColumnsRef is not 0 then -- index14_midi_byte_one
                            try
                                set theItem to item (item 14 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set midi byte one of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 15 of propertiesToColumnsRef is not 0 then -- index15_midi_byte_two
                            try
                                set theItem to item (item 15 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set midi byte two of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 16 of propertiesToColumnsRef is not 0 then -- index16_timecode_trigger
                            try
                                set theItem to item (item 16 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants16_timecode_trigger then
                                    set timecode trigger of newCue to enabled
                                else if theItem is item 2 of constants16_timecode_trigger then
                                    set timecode trigger of newCue to disabled
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 17 of propertiesToColumnsRef is not 0 then -- index17_wall_clock_trigger
                            try
                                set theItem to item (item 17 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants17_wall_clock_trigger then
                                    set wall clock trigger of newCue to enabled
                                else if theItem is item 2 of constants17_wall_clock_trigger then
                                    set wall clock trigger of newCue to disabled
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 18 of propertiesToColumnsRef is not 0 then -- index18_wall_clock_hours
                            try
                                set theItem to item (item 18 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set wall clock hours of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 19 of propertiesToColumnsRef is not 0 then -- index19_wall_clock_minutes
                            try
                                set theItem to item (item 19 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set wall clock minutes of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 20 of propertiesToColumnsRef is not 0 then -- index20_wall_clock_seconds
                            try
                                set theItem to item (item 20 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set wall clock seconds of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index21_mode and item 21 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 21 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants21_mode then
                                    set mode of newCue to cue_list
                                else if theItem is item 2 of constants21_mode then
                                    set mode of newCue to fire_first_enter_group
                                else if theItem is item 3 of constants21_mode then
                                    set mode of newCue to fire_first_go_to_next_cue
                                else if theItem is item 4 of constants21_mode then
                                    set mode of newCue to fire_all
                                else if theItem is item 5 of constants21_mode then
                                    set mode of newCue to fire_random
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index22_sync_to_timecode and item 22 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 22 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants22_sync_to_timecode then
                                    set sync to timecode of newCue to enabled
                                else if theItem is item 2 of constants22_sync_to_timecode then
                                    set sync to timecode of newCue to disabled
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index23_sync_mode and item 23 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 23 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants23_sync_mode then
                                    set sync mode of newCue to mtc
                                else if theItem is item 2 of constants23_sync_mode then
                                    set sync mode of newCue to ltc
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index24_smpte_format and item 24 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 24 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants24_smpte_format then
                                    set smpte format of newCue to fps_24
                                else if theItem is item 2 of constants24_smpte_format then
                                    set smpte format of newCue to fps_25
                                else if theItem is item 3 of constants24_smpte_format then
                                    set smpte format of newCue to fps_30_drop
                                else if theItem is item 4 of constants24_smpte_format then
                                    set smpte format of newCue to fps_30_non_drop
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index25_mtc_sync_source_name and item 25 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 25 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set mtc sync source name of newCue to theItem
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index26_ltc_sync_channel and item 26 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 26 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set ltc sync channel of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index27_patch and item 27 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 27 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set patch of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index28_start_time and item 28 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 28 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set start time of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
     
                        if theType is in index29_end_time and item 29 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 29 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set end time of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index30_loop_start_time and item 30 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 30 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set loop start time of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index31_loop_end_time and item 31 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 31 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set loop end time of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index32_loop_count and item 32 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 32 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set loop count of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index33_infinite_loop and item 33 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 33 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants33_infinite_loop then
                                    set infinite loop of newCue to true
                                else if theItem is item 2 of constants33_infinite_loop then
                                    set infinite loop of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index34_guarantee_sync and item 34 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 34 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants34_guarantee_sync then
                                    set guarantee sync of newCue to true
                                else if theItem is item 2 of constants34_guarantee_sync then
                                    set guarantee sync of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index35_integrated_fade and item 35 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 35 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants35_integrated_fade then
                                    set integrated fade of newCue to enabled
                                else if theItem is item 2 of constants35_integrated_fade then
                                    set integrated fade of newCue to disabled
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index36_fade_mode and item 36 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 36 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants36_fade_mode then
                                    set fade mode of newCue to absolute
                                else if theItem is item 2 of constants36_fade_mode then
                                    set fade mode of newCue to relative
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index37_stop_target_when_done and item 37 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 37 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants37_stop_target_when_done then
                                    set stop target when done of newCue to true
                                else if theItem is item 2 of constants37_stop_target_when_done then
                                    set stop target when done of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index38_auto_stop and item 38 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 38 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants38_auto_stop then
                                    set auto stop of newCue to true
                                else if theItem is item 2 of constants38_auto_stop then
                                    set auto stop of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index39_layer and item 39 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 39 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set layer of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index40_full_screen and item 40 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 40 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants40_full_screen then
                                    set full screen of newCue to true
                                else if theItem is item 2 of constants40_full_screen then
                                    set full screen of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index41_preserve_aspect_ratio and item 41 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 41 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants41_preserve_aspect_ratio then
                                    set preserve aspect ratio of newCue to true
                                else if theItem is item 2 of constants41_preserve_aspect_ratio then
                                    set preserve aspect ratio of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index42_opacity and item 42 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 42 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set opacity of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index43_translation_x and item 43 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 43 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set translation x of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index44_translation_y and item 44 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 44 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set translation y of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index45_rotation and item 45 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 45 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set rotation of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index46_scale_x and item 46 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 46 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set scale x of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index47_scale_y and item 47 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 47 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set scale y of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index48_scale_locked and item 48 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 48 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants48_scale_locked then
                                    set scale locked of newCue to true
                                else if theItem is item 2 of constants48_scale_locked then
                                    set scale locked of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index49_custom_rendering and item 49 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 49 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants49_custom_rendering then
                                    set custom rendering of newCue to true
                                else if theItem is item 2 of constants49_custom_rendering then
                                    set custom rendering of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index50_do_opacity and item 50 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 50 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants50_do_opacity then
                                    set do opacity of newCue to true
                                else if theItem is item 2 of constants50_do_opacity then
                                    set do opacity of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index51_do_translation and item 51 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 51 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants51_do_translation then
                                    set do translation of newCue to true
                                else if theItem is item 2 of constants51_do_translation then
                                    set do translation of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index52_do_rotation and item 52 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 52 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants52_do_rotation then
                                    set do rotation of newCue to true
                                else if theItem is item 2 of constants52_do_rotation then
                                    set do rotation of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index53_do_scale and item 53 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 53 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants53_do_scale then
                                    set do scale of newCue to true
                                else if theItem is item 2 of constants53_do_scale then
                                    set do scale of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index54_command and item 54 of propertiesToColumnsRef is not 0 then -- index13_midi_command
                            try
                                set theItem to item (item 54 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants54_command then
                                    set command of newCue to note_on
                                else if theItem is item 2 of constants54_command then
                                    set command of newCue to note_off
                                else if theItem is item 3 of constants54_command then
                                    set command of newCue to program_change
                                else if theItem is item 4 of constants54_command then
                                    set command of newCue to control_change
                                else if theItem is item 5 of constants54_command then
                                    set command of newCue to key_pressure
                                else if theItem is item 6 of constants54_command then
                                    set command of newCue to channel_pressure
                                else if theItem is item 7 of constants54_command then
                                    set command of newCue to pitch_bend
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index55_channel and item 55 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 55 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set channel of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index56_byte_one and item 56 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 56 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set byte one of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index57_byte_two and item 57 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 57 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set byte two of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index58_byte_combo and item 58 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 58 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set byte combo of newCue to ((theItem as integer) + 8192) -- Pitch bend of 0 needs to be set as 8192
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index59_end_value and item 59 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 59 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    if command of newCue is not pitch_bend then
                                        set end value of newCue to theItem as integer
                                    else
                                        set end value of newCue to ((theItem as integer) + 8192) -- This origin shift won't work if command isn't set!
                                    end if
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index60_fade and item 60 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 60 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants60_fade then
                                    set fade of newCue to enabled
                                else if theItem is item 2 of constants60_fade then
                                    set fade of newCue to disabled
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index61_command_format and item 61 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 61 of propertiesToColumnsRef) of theRecordRef
                                if theItem is in translation61_command_format then
                                    repeat with j from 2 to count translation61_command_format by 2
                                        if theItem is item j of translation61_command_format then
                                            set command format of newCue to item (j - 1) of translation61_command_format as integer
                                            exit repeat
                                        end if
                                    end repeat
                                else
                                    set ohDear to true
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index62_command_number and item 62 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 62 of propertiesToColumnsRef) of theRecordRef
                                if theItem is in translation62_command_number then
                                    repeat with j from 2 to count translation62_command_number by 2
                                        if theItem is item j of translation62_command_number then
                                            set command number of newCue to item (j - 1) of translation62_command_number as integer
                                            exit repeat
                                        end if
                                    end repeat
                                else
                                    set ohDear to true
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index63_q__number and item 63 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 63 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set q_number of newCue to theItem
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index64_q__list and item 64 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 64 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set q_list of newCue to theItem
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index65_q__path and item 65 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 65 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set q_path of newCue to theItem
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index66_macro and item 66 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 66 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set macro of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index67_control_number and item 67 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 67 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set control number of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index68_control_value and item 68 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 68 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set control value of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index69_hours and item 69 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 69 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set hours of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index70_minutes and item 70 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 70 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set minutes of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index71_seconds and item 71 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 71 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set seconds of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index72_frames and item 72 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 72 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set frames of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index73_subframes and item 73 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 73 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set subframes of newCue to theItem as integer
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index74_send_time_with_set and item 74 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 74 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants74_send_time_with_set then
                                    set send time with set of newCue to true
                                else if theItem is item 2 of constants74_send_time_with_set then
                                    set send time with set of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index75_sysex_message and item 75 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 75 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set sysex message of newCue to theItem
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index76_midi_destination and item 76 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 76 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set midi destination of newCue to theItem
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index77_start_time_offset and item 77 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 77 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set start time offset of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index78_fire_next_cue_when_loop_ends and item 78 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 78 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants78_fire_next_cue_when_loop_ends then
                                    set fire next cue when loop ends of newCue to true
                                else if theItem is item 2 of constants78_fire_next_cue_when_loop_ends then
                                    set fire next cue when loop ends of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index79_stop_target_when_loop_ends and item 79 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 79 of propertiesToColumnsRef) of theRecordRef
                                if theItem is item 1 of constants79_stop_target_when_loop_ends then
                                    set stop target when loop ends of newCue to true
                                else if theItem is item 2 of constants79_stop_target_when_loop_ends then
                                    set stop target when loop ends of newCue to false
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index80_load_time and item 80 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 80 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set load time of newCue to theItem as real
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if theType is in index81_assigned_number and item 81 of propertiesToColumnsRef is not 0 then
                            try
                                set theItem to item (item 81 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    set assigned number of newCue to theItem
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        if item 82 of propertiesToColumnsRef is not 0 then -- Make a list of cues to move later
                            try
                                set theItem to item (item 82 of propertiesToColumnsRef) of theRecordRef
                                if theItem is not "" then
                                    copy item (item 2 of propertiesToColumnsRef) of theRecordRef to end of cuesToMove -- This cue's q number
                                    copy theItem to end of cuesToMove -- The destination group
                                end if
                            on error
                                set ohDear to true
                            end try
                        end if
     
                        -- Deal with levels
     
                        if theType is in index_setLevel then
                            repeat with eachLevelColumn in levelColumns
                                set AppleScript's text item delimiters to "\""
                                set excelCleanup to word 1 of item eachLevelColumn of headerRow
                                -- Deal with Excel formatting: 0,0 becomes "0,0" when exported
                                set AppleScript's text item delimiters to ","
                                try
                                    if item eachLevelColumn of theRecordRef is not "" then
                                        set theRow to text item 1 of excelCleanup as integer
                                        set theColumn to text item 2 of excelCleanup as integer
                                        set theLevel to item eachLevelColumn of theRecordRef as real
                                        newCue setLevel row theRow column theColumn db theLevel
                                    end if
                                on error
                                    set ohDear to true
                                end try
                            end repeat
                            set AppleScript's text item delimiters to tab
                        end if
     
                        -- Flag any errors
     
                        if ohDear is true then
                            set existingName to q name of newCue as string
                            if existingName is not "" then -- q name returns "" if it's empty; notes returns "missing value"; go figure (53)... ;-)
                                set q name of newCue to "!!!! " & existingName
                            else
                                set q name of newCue to "!!!!"
                            end if
                            if annotateEveryLine is "No" then
                                set existingNotes to notes of newCue as string
                                if existingNotes is not "missing value" then
                                    set notes of newCue to existingNotes & return & return & "Made from line " & i & " of text file \"" & ¬
                                        theName & "\": | " & niceParagraph & " |"
                                else
                                    set notes of newCue to "Made from line " & i & " of text file \"" & theName & "\": | " & niceParagraph & " |"
                                end if
                            end if
                        end if
     
                    end if
     
                    -- Countdown timer (and opportunity to escape)
     
                    if i mod 50 is 0 and (countText - i) > 25 then
                        set timeTaken to ((time of (current date)) - startTime) as integer
                        set timeString to my makeMMSS(timeTaken)
                        if application "QLab" is frontmost then
                            display dialog (("Time elapsed: " & timeString & " - " & i as string) & " of " & countText as string) & " lines done..." with title ¬
                                dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 1
                        end if
                    end if
     
                end repeat
     
                -- Move cues into groups
     
                repeat with i from 1 to count cuesToMove by 2
                    try
                        set cueToMoveNumber to item i of cuesToMove
                        set destinationNumber to item (i + 1) of cuesToMove
                        move cue cueToMoveNumber of current cue list to end of cue destinationNumber
                    end try
                end repeat
     
            end tell
     
            -- All done. Hoopla!
     
            set timeTaken to ((time of (current date)) - startTime) as integer
            set timeString to my makeNiceT(timeTaken)
            activate
            display dialog "Done. 
     
    (That took " & timeString & ".)
     
    I hope it all worked out OK..." with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 60
     
        end tell
     
        set AppleScript's text item delimiters to currentTIDs
     
    on error number -128
        set AppleScript's text item delimiters to currentTIDs
    end try
     
    -- Subroutines
     
    on toTheClipboard(textToCopy)
        set AppleScript's text item delimiters to tab
        set the clipboard to textToCopy as text
        set AppleScript's text item delimiters to currentTIDs
    end toTheClipboard
     
    on exitStrategy()
        display dialog "I'm afraid that file tasted funny so I've had to spit it out. Please check the file and try again. Sorry." with title ¬
            dialogTitle with icon 0 buttons {"OK"} default button "OK"
        set AppleScript's text item delimiters to currentTIDs
    end exitStrategy
     
    on makeNiceParagraph(dirtyColumns)
        set tempTIDS to AppleScript's text item delimiters
        set AppleScript's text item delimiters to ""
        set cleanRow to ""
        set theSeparator to ""
        repeat with eachColumn in dirtyColumns
            if eachColumn starts with "\"" then
                if eachColumn does not end with "\"" then
                    set cleanRow to cleanRow & theSeparator & (rest of characters of eachColumn as string)
                else
                    set cleanRow to cleanRow & theSeparator & (characters 2 thru ((count eachColumn) - 1) of eachColumn as string)
                end if
            else if eachColumn does not end with "\"" then
                set cleanRow to cleanRow & theSeparator & eachColumn
            else
                set cleanRow to cleanRow & theSeparator & (characters 1 thru ((count eachColumn) - 1) of eachColumn as string)
            end if
            set theSeparator to " | "
        end repeat
        set AppleScript's text item delimiters to tempTIDS
        return cleanRow
    end makeNiceParagraph
     
    on makeNiceT(howLong)
        if howLong is 0 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 string) & " 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 string) & " minutes"
        end if
        set howManySeconds to howLong mod 60 as integer
        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 string) & " seconds"
        end if
        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 "
            else
                set theAmpersand to ""
            end if
        else
            set theAmpersand to ""
        end if
        if minuteString is not "" and secondString is not "" then
            set theOtherAmpersand to " and "
        else
            set theOtherAmpersand to ""
        end if
        return hourString & theAmpersand & minuteString & theOtherAmpersand & secondString
    end makeNiceT
     
    on makeMMSS(howLong)
        set howManyMinutes to howLong div 60
        set minuteString to (howManyMinutes as string)
        set howManySeconds to howLong mod 60 as integer
        if howManySeconds > 9 then
            set secondString to (howManySeconds as string)
        else
            set secondString to "0" & (howManySeconds as string)
        end if
        return minuteString & ":" & secondString
    end makeMMSS
     
    (* END: Make cues from a text file *)