QLab Script - make a text file from cues

Contents

Table of Contents

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

    
    (* Make a text file from cues: ingest/make a tab-delimited text file and populate (a copy of) it with a report of the cues in the current workspace in QLab;
        see second & third display dialogs for further explanation
     
    This script is not designed to be run from within QLab!
     
    v0.9: 12/10/09 Rich Walsh (with thanks to Jeremy Lee for some of the basic concepts)
    v0.9.1: 16/10/09 Now "tested" in Snow Leopard; expanded makeNiceT for hours; fixed nasty mess with missing cue/file target strings;
            added Excel cleanup if renaming levels columns; made progress updates more frequent; general tidying - including first attempts at improving efficiency;
            updates for QLab 2.2.5
    v0.9.2: 27/10/09 Added MSC translation; numerous typos
    v1.0: 11/01/10 Fixed text-cleaning routines; "start value" is read-only, so fixed that; byte combo (and related) origin offset; corrected minor typos;
            added tell front workspace for elegance; wrapped text for better wiki experience; implemented dialogTitle for cross-script pillaging
    v1.1: 31/01/10 Fixed a bug (!) with carriage returns in notes
     
    <<< Last tested with: QLab 2.2.6; Mac OS 10.5.8 & 10.6.2; Microsoft Excel 12.2.0 (tested with Mac OS 10.5.8 only) >>>
     
    <<< 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***
     
    This script has been designed to be highly customisable: you have control over what properties are reported, what the columns are called,
    how QLab's constants are displayed and some other little tweaks too. However, you should record what you've done so you can repeat it
    if the script ever gets updated, and please DON'T release your private version into the wild! 
     
    To change what properties are reported, you can make your own text file or add/modify "presets" below. If, say, you add one with
    "set preset7 to {"file target"}" then make sure you add something to the end of availablePresets so you can choose it, and "preset7"
    to the end of presetMapper so the script can find it.
     
    To change what the columns are called, edit the relevant entry in the "set acceptableColumnHeaders" line, eg: change "continue mode" to "f/on"
    if you prefer (just don't use commas - they identify level-setting columns). You'll need to do this in any presets that refer to that property too, mind.
     
    To change how QLab's constants are returned, edit the contents of the relevant "set constantsXXX" line, eg: change "set constants10_continue_mode"
    entries from "do_not_continue", "auto_continue" & "auto_follow" to "no", "a/c" & "a/f" if you prefer.
     
    Further tweaks are possible under "Customisable reporting translations"; hopefully they're self-explanatory. You can even modify
    the default settings for "Other" in the levels reporting dialogs: see "Level reporting dialog variables (customisable)". *)
     
    -- ###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### What happens if Excel isn't on the computer!?
    -- ###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### If the "Min" level from the Audio preferences page becomes scriptable then item 1 of minusInfinity could be pulled from the workspace
    -- ###ADD### Other options for final file? Numbers, Bento, FileMaker Pro - don't hold your breath though (the first two currently have rubbish scripting hooks!)
     
    -- Declarations
     
    global dialogTitle
    set dialogTitle to "Make a text file from cues"
     
    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 {"is 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", "start value"}
    repeat with eachItem in reportingOnlyColumns
        copy eachItem as string to end of acceptableColumnHeaders
    end repeat
     
    set levelColumns to {} -- If a column header contains "," it will be added to this list
     
    -- This is a list of properties that can be read for each cue type
     
    -- 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 {"Audio", "Fade", "Video", "Animation", "Camera", "MIDI", "Wait"} -- Duration of Audio & Video cues can't be set, only read:
    (* so this line is different from "Make cues from a text file" (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)
    set index62_command_number to {"MSC"} -- (integer)
    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 index82: "is in group" is a custom column for this script that has no direct meaning for QLab
    --set index83_unique ID: every cue has unique ID (text)
    --set index84_broken: every cue has broken (boolean)
    set index85_audio_input_channels to {"Audio", "Video"} -- (integer)
    set index86_start_value to {"MIDI"} -- (integer)
     
    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 returned 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"}
    set constants84_broken 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"}
     
    -- Customisable reporting translations
     
    set crosspointsInEnglish to {"0,0", "Master", "0,", "Output "} -- Replacement text for levels columns
    repeat with i from 1 to 16
        copy (i & ",0") as string to end of crosspointsInEnglish
        copy ("Input " & i) as string to end of crosspointsInEnglish
    end repeat
     
    set carriageReturnsInNotes to "¶" -- Have to remove carriage returns from notes text as they would corrupt the tab-delimited structure
     
    set {missingCueTargetPrefix, missingCueTargetSuffix} to {"[Drag a", " cue here.]"} -- Construct the string to return when a cue's cue target is missing
    set missingCueTargetQualifier to {"Fade", "n audio", "Devamp", "n audio", "Animation", " video", "Target", " goto"}
     
    set invalidFileTarget to {"[Drag an audio file here.]", "[Drag a video file here.]"} -- The strings to return when a cue's file target has not been set
    set missingFileTarget to "missing value" -- The string to return when a cue's file target is missing
    set layerThousandIsTop to {"top"} -- The string to return if a cue's layer is 1000 (and hence displayed as "top" by QLab)
    set cuelistsNotInGroups to "N/A" -- The string to return when asked which group a cue is in, and that cue is a cue list
    set irrelevantCrosspoints to "N/A" -- The string to return when asked to examine crosspoints in rows beyond the number of audio input channels in a cue
    set minusInfinity to {-120, "-INF"} -- If a level is returned below the first item, the second item in this list is substituted;
    (* NB: Excel gets confused by "-INF" in the cells unless you let the script force Excel to open every column as "text"... *)
     
    -- Reporting presets (customisable)
     
    set availablePresets to {"I've rolled my own", "The columns that you see", "Properties for audio & MIDI", "Properties for video", "All the properties", ¬
        "Just the basic facts", "Key properties for a levels report"}
    set preset1 to {"unique ID", "type", "q number", "q name", "cue target", "file target", "pre wait", "duration", "post wait", "continue mode", "broken", ¬
        "notes", "mode", "is in group"}
    set preset2 to {"unique ID", "type", "q number", "q name", "cue target", "file target", "pre wait", "duration", "post wait", "continue mode", "broken", ¬
        "notes", "mode", "is in group", "audio input channels", "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", "command", "channel", "byte one", "byte two", "byte combo", "start value", ¬
        "end value", "fade", "SMPTE format", "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"}
    set preset3 to {"unique ID", "type", "q number", "q name", "cue target", "file target", "pre wait", "duration", "post wait", "continue mode", "broken", ¬
        "notes", "mode", "is in group", "patch", "start time", "end time", "infinite loop", "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"}
    set preset4 to acceptableColumnHeaders
    set preset5 to {"q number", "q name", "continue mode", "notes", "mode", "is in group"}
    set preset6 to {"unique ID", "type", "q number", "q name", "cue target", "file target", "broken", "notes", "audio input channels", "patch"}
    set presetMapper to {preset1, preset2, preset3, preset4, preset5, preset6}
     
    -- Level reporting dialog variables (customisable)
     
    set userDefaultOutputs to "32" -- The default number of outputs to offer to report (stereo inputs is fixed)
    set userOtherRows to "1" -- The default option to offer for rows if "Other" is chosen
    set userOtherColumns to "17" -- The default option to offer for columns if "Other" is chosen
     
    -- General variables
     
    set fileMade to false
    set propertiesToColumns to {}
    set groupMembership 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 ingest a tab-delimited text file, copy it, " & ¬
                            "and then populate the copy with a report of the cues in the current workspace in QLab - returning the properties " & ¬
                            "you specify in the text file.
     
    Alternatively, it also comes with a set of exciting presets, to which you can add levels reporting to suit your needs each time you run the script.
     
    When it's done you can choose to open the file in Excel, forcing Excel to not try to be clever and, therefore, unhelpfully disrupt the formatting. " & ¬
                            "The file you get out at the end is a Unicode text file with no particular application association, regardless of what you put in " & ¬
                            "(this just turned out to be the easiest way).
     
    If you choose to make your own text file then the first row must contain the \"column headers\", ie: it should define which properties you are " & ¬
                            "hoping to get from your cues. Unless you customise the script, these column headers must 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). Note that this list is slightly different from the list in the " & ¬
                            "sister script \"Make cues from a text file\", as it includes properties that are read-only (like \"broken\") - and \"put in group\" " & ¬
                            "becomes \"is in group\". It's also worth noting that you probably won't be able to take the output of this script, run it through " & ¬
                            "that script and end up with the workspace again (QLab has its own \"Save\" routine for that!).
     
    The next page has some details about levels and customisation..." 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 "Since there are currently 833 possible scriptable levels " & ¬
                                "for those cues that take them, it's up to you which levels to request in your file. For any crosspoint you wish to get, " & ¬
                                "add a column header of the form \"row,column\" and those levels will be returned in that column. For example: " & ¬
                                "column header \"0,0\" specifies row 0 column 0 (ie: the Master level), \"2,42\" would be row 2 column 42 " & ¬
                                "(the crosspoint between channel 2 of your audio file and output 42). You'll get the choice to turn some of these abstract " & ¬
                                "numbers into text when the script runs (try it and find out!).
     
    Some other substitution takes place when the values are returned, eg: carriage returns in a cue's \"notes\" are replaced with \"¶\", so as not to " & ¬
                                "break the tab-delimiting. Most of the substitutions are customisable, though.
     
    In fact, this script is highly customisable - just look inside it to find out more.
     
    In terms of speed, this script's not so bad: reporting 100 properties/levels for 100 cues takes about 4 minutes - and the speed doesn't seem to " & ¬
                                "go down too much as the number of cues goes up..." with title dialogTitle with icon 1 ¬
                                buttons {"<<< To page 1", "Copy headers to Clipboard", "Back to start"} default button "Back to start")
                            if instructionButton2 is "Copy headers to Clipboard" then
                                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 (get the name & all the cues at the same time)
     
        tell application "QLab"
            try
                set workspaceName to q number of front workspace
                set countCues to count cues 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
     
        -- Select a preset
     
        set thePresetMenu to (choose from list availablePresets with prompt "Please choose whether to ingest your own file, or use one of my preset reports:" with title ¬
            dialogTitle default items item 1 of availablePresets) as string
     
        if thePresetMenu is "false" then
            return
        end if
     
        set AppleScript's text item delimiters to ""
     
        -- Grab the current time for file-naming later
        -- Written with help from http://www.fluther.com/disc/22258/applescript-get-and-reformat-current-date-then-insert-it-in-any/
     
        set {year:y, month:m, day:d, time string:t} to (current date)
        set dateString to (y * 10000 + m * 100 + d) as string
        set timeString to (t) as string
        set theTime to ¬
            (text items 3 thru 4 of dateString) & "-" & ¬
            (text items 5 thru 6 of dateString) & "-" & ¬
            (text items 7 thru 8 of dateString) & " " & ¬
            (text items 1 thru 2 of timeString) & ¬
            (text items 4 thru 5 of timeString) as string
     
        if thePresetMenu is item 1 of availablePresets then
     
            -- Either get the file (and copy it)...
     
            set originalFile to choose file with prompt "Please select a tab-delimited text file which contains a header row
    for the properties you wish to include in the report:" default location (path to desktop) without invisibles
     
            try
                set theText to read originalFile
            on error
                my exitStrategy()
                return
            end try
            tell application "System Events"
                set theExtension to name extension of originalFile
                if theExtension is "" then
                    set newPath to path of originalFile
                else
                    set theFullPath to path of originalFile
                    set newPath to text 1 through ((length of theFullPath) - (length of theExtension) - 1) of theFullPath
                end if
                set newFile to newPath & " | QLab | " & workspaceName & " | Cues report | " & theTime & "." & theExtension
            end tell
            try
                copy (open for access newFile with write permission) to theOpenFile -- The final output is to a (Unicode) text file, 
                (*regardless of what type of file is ingested; experiments suggested that this was the safest route
                as problems were encountered when trying to put the wrong kind of text into, say, a TextEdit file
                (eg: if starting with a TextEdit file, characters like "¶" are tricky as writing the file out as text makes
                a file TextEdit can't read (but Excel can), whilst writing the file out as class «class utf8» makes a file Excel can't read instead) *)
                set fileMade to true
            on error
                display dialog "Something went wrong when trying to copy and rename the file. I may have left a bit of a mess..." with title ¬
                    dialogTitle with icon 0 buttons {"OK"} default button "OK"
                set AppleScript's text item delimiters to currentTIDs
                return
            end try
     
        else
     
            -- Or make it on the Desktop, with current time appended to name
     
            set newFile to "" & (path to desktop) & "QLab | " & workspaceName & " | " & dialogTitle & " | " & theTime & ".txt"
     
            repeat with i from 2 to count availablePresets
                if thePresetMenu is equal to item i of availablePresets then
                    set chosenPreset to item (i - 1) of presetMapper
                    exit repeat
                end if
            end repeat
     
            -- Ask about levels
     
            set userDefinedButton to "Stereo to " & userDefaultOutputs & " outputs"
            set levelsQuestion to button returned of (display dialog "Do you want to include any levels?" with title ¬
                dialogTitle buttons {"No", userDefinedButton, "Other"} default button userDefinedButton)
            if levelsQuestion is userDefinedButton then
                set {totalRows, totalColumns} to {2, userDefaultOutputs as number}
            else if levelsQuestion is "Other" then
                set rowsQuestion to ""
                repeat until rowsQuestion is not ""
                    set rowsQuestion to text returned of (display dialog "Enter the number of rows you wish to report (ie: a number between 1 & 17):" with title ¬
                        dialogTitle buttons {"OK"} default button "OK" default answer userOtherRows)
                    try
                        set rowsNo to rowsQuestion as number
                        if rowsNo is less than 1 or rowsNo is greater than 17 then
                            set rowsQuestion to ""
                        end if
                    on error
                        set rowsQuestion to ""
                    end try
                end repeat
                set columnsQuestion to ""
                repeat until columnsQuestion is not ""
                    set columnsQuestion to text returned of ¬
                        (display dialog "Enter the number of columns you wish to report (ie: a number between 1 & 49):" with title ¬
                            dialogTitle buttons {"OK"} default button "OK" default answer userOtherColumns)
                    try
                        set columnsNo to columnsQuestion as number
                        if columnsNo is less than 1 or columnsNo is greater than 49 then
                            set columnsQuestion to ""
                        end if
                    on error
                        set columnsQuestion to ""
                    end try
                end repeat
                set {totalRows, totalColumns} to {rowsNo - 1, columnsNo - 1}
            end if
            if levelsQuestion is not "No" then
                repeat with i from 0 to totalRows
                    repeat with j from 0 to totalColumns
                        copy (i & "," & j) as string to end of chosenPreset
                    end repeat
                end repeat
            end if
     
            set AppleScript's text item delimiters to tab
            set theText to chosenPreset as text
            set AppleScript's text item delimiters to ""
            try
                copy (open for access newFile with write permission) to theOpenFile
                set fileMade to true
            on error
                display dialog "Something went wrong when trying to create that file. I may have left some mess on your Desktop..." with title ¬
                    dialogTitle with icon 0 buttons {"OK"} default button "OK"
                set AppleScript's text item delimiters to currentTIDs
                return
            end try
     
        end if
     
        -- 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
     
        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
     
        -- Make level columns headers easier to read
     
        if (count levelColumns) is not 0 then
            set inEnglish to button returned of (display dialog "Now that I've got what I need from the headers for levels reporting, " & ¬
                "shall I translate (some of) them into text for you?" with title dialogTitle with icon 1 ¬
                buttons {"No thanks", "Please do"} default button "Please do")
            if inEnglish is "Please do" then
                set dirtyHeader to paragraph 1 of theText -- Only work on first row (in case there is more text in the file)
                set theRest to rest of paragraphs of theText
                set dirtyColumns to text items of dirtyHeader
                set cleanColumns to {}
                repeat with eachColumn in dirtyColumns
                    if eachColumn contains "," then -- Only work on levels columns
                        set AppleScript's text item delimiters to "\"" -- Strip out Excel formatting: 0,0 becomes "0,0" when exported from Excel
                        set cleanStore to text items of eachColumn
                        set AppleScript's text item delimiters to ""
                        copy (cleanStore as string) to end of cleanColumns
                    else
                        copy (eachColumn as string) to end of cleanColumns
                    end if
                end repeat
                set AppleScript's text item delimiters to tab
                set cleanHeader to cleanColumns as text
                repeat with i from 1 to count crosspointsInEnglish by 2 -- Replace strings in header row
                    set AppleScript's text item delimiters to item i of crosspointsInEnglish
                    set englishHeader to text items of cleanHeader
                    set AppleScript's text item delimiters to item (i + 1) of crosspointsInEnglish
                    set cleanHeader to englishHeader as text
                end repeat
                if (count theRest) is 0 then
                    set theText to cleanHeader
                else
                    set AppleScript's text item delimiters to return
                    set theText to cleanHeader & return & theRest as text -- Stitch the file back together
                    set AppleScript's text item delimiters to tab
                end if
            end if
        end if
     
        -- How long is this going to take?
     
        set fudgeFactor to countCues * ((count headerRow) / 113) -- Account for variable number of steps that will be run for each cue;
        (* testing was done with 113 headers *)
        -- ###FIXME### Current estimate based on second order polynomial interpolation of 3 sample data points (!): get more data
        set theETA to 1.5 + 2 * fudgeFactor + 0 * (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 " & countCues as string) & " cues in this workspace.
     
    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
     
                -- Deal with group membership
     
                if item 82 of propertiesToColumnsRef is not 0 then -- Don't bother unless asked to
                    set allGroups to every cue whose q type is "Group"
                    set allGroupsRef to a reference to allGroups
                    repeat with eachGroup in allGroupsRef
                        set groupTitle to q name of eachGroup as string
                        if groupTitle is "" then
                            set groupTitle to q number of eachGroup as string
                            if groupTitle is "" then
                                set groupTitle to "id: " & uniqueID of eachGroup as string
                            end if
                        end if
                        set childrenCues to uniqueID of every cue in eachGroup
                        set childrenCuesRef to (a reference to childrenCues)
                        repeat with i from 1 to count childrenCuesRef
                            copy item i of childrenCuesRef to end of groupMembership
                            copy groupTitle to end of groupMembership
                            set timeTaken to ((time of (current date)) - startTime) as integer
                            if timeTaken mod 15 is 0 and timeTaken > 0 then -- This bit can seem like nothing's happening, so display a dialog every 15s
                                if application "QLab" is frontmost then
                                    display dialog "Processing group membership..." 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
                    end repeat
                    set groupMembershipRef to a reference to groupMembership
                end if
     
                repeat with i from 1 to countCues
     
                    set theCue to (a reference to cue i)
     
                    -- Prepare a list to hold the properties for this cue
     
                    set theProperties to {}
     
                    repeat while (count theProperties) is less than (count headerRow)
                        copy "" to end of theProperties
                    end repeat
     
                    set thePropertiesRef to (a reference to theProperties)
     
                    set theType to q type of theCue as string
                    if theType is "Group" and mode of theCue is cue_list then
                        set theType to "Cue List" -- This is a kludge to get round the way cue lists are reported as Groups in "cue_list" mode,
                        (* even though they have additional properties that Groups don't have *)
                    end if
     
                    -- These if…then clauses retrieve the relevant property on each cue based on the contents of the text file
                    (* (unlike "Make cues from a text file", try clauses aren't needed here) *)
     
                    if item 1 of propertiesToColumnsRef is not 0 then -- Report the q type
                        set item (item 1 of propertiesToColumnsRef) of thePropertiesRef to theType
                    end if
     
                    if item 2 of propertiesToColumnsRef is not 0 then -- index2_q_number
                        set theItem to q number of theCue as string
                        set item (item 2 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 3 of propertiesToColumnsRef is not 0 then -- index3_q_name
                        set theItem to q name of theCue as string
                        set item (item 3 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 4 of propertiesToColumnsRef is not 0 then -- index4_notes
                        set theItem to notes of theCue as string
                        if theItem is not "missing value" then
                            set cleanText to paragraphs of theItem
                            set AppleScript's text item delimiters to carriageReturnsInNotes
                            set noReturns to cleanText as text
                            set AppleScript's text item delimiters to tab
                            set item (item 4 of propertiesToColumnsRef) of thePropertiesRef to noReturns
                        end if
                    end if
     
                    if theType is in index5_cue_target and item 5 of propertiesToColumnsRef is not 0 then
                        set theItem to cue target of theCue
                        if theItem is missing value then
                            if theType is in missingCueTargetQualifier then
                                repeat with j from 1 to count missingCueTargetQualifier by 2
                                    if theType is item j of missingCueTargetQualifier then
                                        set targetTitle to missingCueTargetPrefix & item (j + 1) of missingCueTargetQualifier & missingCueTargetSuffix
                                        exit repeat
                                    end if
                                end repeat
                            else
                                set targetTitle to missingCueTargetPrefix & missingCueTargetSuffix
                            end if
                        else
                            set targetTitle to (q name of theItem) as string
                            if targetTitle is "" then
                                set targetTitle to (q number of theItem) as string
                                if targetTitle is "" then
                                    set targetTitle to "id: " & (uniqueID of theItem) as string
                                end if
                            end if
                        end if
                        set item (item 5 of propertiesToColumnsRef) of thePropertiesRef to targetTitle
                    end if
     
                    if theType is in index6_file_target and item 6 of propertiesToColumnsRef is not 0 then
                        try
                            set theItem to file target of theCue as string
                            if theItem is "missing value" then
                                set targetTitle to missingFileTarget
                            else
                                set targetTitle to (POSIX path of (theItem as alias)) as string -- Convert to POSIX
                            end if
                        on error
                            repeat with j from 1 to count index6_file_target
                                if theType is item j of index6_file_target then
                                    set targetTitle to item j of invalidFileTarget
                                    exit repeat
                                end if
                            end repeat
                        end try
                        set item (item 6 of propertiesToColumnsRef) of thePropertiesRef to targetTitle
                    end if
     
                    if item 7 of propertiesToColumnsRef is not 0 then -- index7_pre_wait
                        set theTime to pre wait of theCue
                        set theItem to my makeHHMMSSss(theTime)
                        set item (item 7 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index8_duration and item 8 of propertiesToColumnsRef is not 0 then
                        set theTime to duration of theCue
                        set theItem to my makeHHMMSSss(theTime)
                        set item (item 8 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 9 of propertiesToColumnsRef is not 0 then -- index9_post_wait
                        set theTime to post wait of theCue
                        set theItem to my makeHHMMSSss(theTime)
                        set item (item 9 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 10 of propertiesToColumnsRef is not 0 then -- index10_continue_mode
                        set theItem to continue mode of theCue
                        if theItem is do_not_continue then
                            set item (item 10 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants10_continue_mode
                        else if theItem is auto_continue then
                            set item (item 10 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants10_continue_mode
                        else if theItem is auto_follow then
                            set item (item 10 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants10_continue_mode
                        end if
                    end if
     
                    if item 11 of propertiesToColumnsRef is not 0 then -- index11_armed
                        set theItem to armed of theCue
                        if theItem is true then
                            set item (item 11 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants11_armed
                        else if theItem is false then
                            set item (item 11 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants11_armed
                        end if
                    end if
     
                    if item 12 of propertiesToColumnsRef is not 0 then -- index12_midi_trigger
                        set theItem to midi trigger of theCue
                        if theItem is enabled then
                            set item (item 12 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants12_midi_trigger
                        else if theItem is disabled then
                            set item (item 12 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants12_midi_trigger
                        end if
                    end if
     
                    if item 13 of propertiesToColumnsRef is not 0 then -- index13_midi_command
                        set theItem to midi command of theCue
                        if theItem is note_on then
                            set item (item 13 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants13_midi_command
                        else if theItem is note_off then
                            set item (item 13 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants13_midi_command
                        else if theItem is program_change then
                            set item (item 13 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants13_midi_command
                        else if theItem is control_change then
                            set item (item 13 of propertiesToColumnsRef) of thePropertiesRef to item 4 of constants13_midi_command
                        else if theItem is key_pressure then
                            set item (item 13 of propertiesToColumnsRef) of thePropertiesRef to item 5 of constants13_midi_command
                        else if theItem is channel_pressure then
                            set item (item 13 of propertiesToColumnsRef) of thePropertiesRef to item 6 of constants13_midi_command
                        end if
                    end if
     
                    if item 14 of propertiesToColumnsRef is not 0 then -- index14_midi_byte_one
                        set theItem to midi byte one of theCue as string
                        set item (item 14 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 15 of propertiesToColumnsRef is not 0 then -- index15_midi_byte_two
                        if midi command of theCue is not program_change and midi command of theCue is not channel_pressure then
                            set theItem to midi byte two of theCue as string
                            set item (item 15 of propertiesToColumnsRef) of thePropertiesRef to theItem
                        end if
                    end if
     
                    if item 16 of propertiesToColumnsRef is not 0 then -- index16_timecode_trigger
                        set theItem to timecode trigger of theCue
                        if theItem is enabled then
                            set item (item 16 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants16_timecode_trigger
                        else if theItem is disabled then
                            set item (item 16 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants16_timecode_trigger
                        end if
                    end if
     
                    if item 17 of propertiesToColumnsRef is not 0 then -- index17_wall_clock_trigger
                        set theItem to wall clock trigger of theCue
                        if theItem is enabled then
                            set item (item 17 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants17_wall_clock_trigger
                        else if theItem is disabled then
                            set item (item 17 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants17_wall_clock_trigger
                        end if
                    end if
     
                    if item 18 of propertiesToColumnsRef is not 0 then -- index18_wall_clock_hours
                        set theItem to wall clock hours of theCue as string
                        set item (item 18 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 19 of propertiesToColumnsRef is not 0 then -- index19_wall_clock_minutes
                        set theItem to wall clock minutes of theCue as string
                        set item (item 19 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 20 of propertiesToColumnsRef is not 0 then -- index20_wall_clock_seconds
                        set theItem to wall clock seconds of theCue as string
                        set item (item 20 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index21_mode and item 21 of propertiesToColumnsRef is not 0 then
                        set theItem to mode of theCue
                        if theItem is cue_list then
                            set item (item 21 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants21_mode
                        else if theItem is fire_first_enter_group then
                            set item (item 21 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants21_mode
                        else if theItem is fire_first_go_to_next_cue then
                            set item (item 21 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants21_mode
                        else if theItem is fire_all then
                            set item (item 21 of propertiesToColumnsRef) of thePropertiesRef to item 4 of constants21_mode
                        else if theItem is fire_random then
                            set item (item 21 of propertiesToColumnsRef) of thePropertiesRef to item 5 of constants21_mode
                        end if
                    end if
     
                    if theType is in index22_sync_to_timecode and item 22 of propertiesToColumnsRef is not 0 then
                        set theItem to sync to timecode of theCue
                        if theItem is enabled then
                            set item (item 22 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants22_sync_to_timecode
                        else if theItem is disabled then
                            set item (item 22 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants22_sync_to_timecode
                        end if
                    end if
     
                    if theType is in index23_sync_mode and item 23 of propertiesToColumnsRef is not 0 then
                        set theItem to sync mode of theCue
                        if theItem is mtc then
                            set item (item 23 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants23_sync_mode
                        else if theItem is ltc then
                            set item (item 23 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants23_sync_mode
                        end if
                    end if
     
                    if theType is in index24_smpte_format and item 24 of propertiesToColumnsRef is not 0 then
                        set theItem to smpte format of theCue
                        if theItem is fps_24 then
                            set item (item 24 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants24_smpte_format
                        else if theItem is fps_25 then
                            set item (item 24 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants24_smpte_format
                        else if theItem is fps_30_drop then
                            set item (item 24 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants24_smpte_format
                        else if theItem is fps_30_non_drop then
                            set item (item 24 of propertiesToColumnsRef) of thePropertiesRef to item 4 of constants24_smpte_format
                        end if
                    end if
     
                    if theType is in index25_mtc_sync_source_name and item 25 of propertiesToColumnsRef is not 0 then
                        set theItem to mtc sync source name of theCue as string
                        if theItem is not "missing value" then
                            set item (item 25 of propertiesToColumnsRef) of thePropertiesRef to theItem
                        end if
                    end if
     
                    if theType is in index26_ltc_sync_channel and item 26 of propertiesToColumnsRef is not 0 then
                        set theItem to ltc sync channel of theCue as string
                        set item (item 26 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index27_patch and item 27 of propertiesToColumnsRef is not 0 then
                        set theItem to patch of theCue as string
                        set item (item 27 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index28_start_time and item 28 of propertiesToColumnsRef is not 0 then
                        set theTime to start time of theCue
                        if theTime is missing value then -- ### Mac OS X 10.6.2 fix for incorrectly returned items
                            set theTime to 0
                        end if
                        set theItem to my makeHHMMSSsss(theTime)
                        set item (item 28 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
     
                    if theType is in index29_end_time and item 29 of propertiesToColumnsRef is not 0 then
                        set theTime to end time of theCue
                        set theItem to my makeHHMMSSsss(theTime)
                        set item (item 29 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index30_loop_start_time and item 30 of propertiesToColumnsRef is not 0 then
                        set theTime to loop start time of theCue
                        if theTime is missing value then -- ### Mac OS X 10.6.2 fix for incorrectly returned items
                            set theTime to 0
                        end if
                        set theItem to my makeHHMMSSsss(theTime)
                        set item (item 30 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index31_loop_end_time and item 31 of propertiesToColumnsRef is not 0 then
                        set theTime to loop end time of theCue
                        set theItem to my makeHHMMSSsss(theTime)
                        set item (item 31 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index32_loop_count and item 32 of propertiesToColumnsRef is not 0 then
                        set theItem to loop count of theCue as string
                        set item (item 32 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index33_infinite_loop and item 33 of propertiesToColumnsRef is not 0 then
                        set theItem to infinite loop of theCue
                        if theItem is true then
                            set item (item 33 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants33_infinite_loop
                        else if theItem is false then
                            set item (item 33 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants33_infinite_loop
                        end if
                    end if
     
                    if theType is in index34_guarantee_sync and item 34 of propertiesToColumnsRef is not 0 then
                        set theItem to guarantee sync of theCue
                        if theItem is true then
                            set item (item 34 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants34_guarantee_sync
                        else if theItem is false then
                            set item (item 34 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants34_guarantee_sync
                        end if
                    end if
     
                    if theType is in index35_integrated_fade and item 35 of propertiesToColumnsRef is not 0 then
                        set theItem to integrated fade of theCue
                        if theItem is enabled then
                            set item (item 35 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants35_integrated_fade
                        else if theItem is disabled then
                            set item (item 35 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants35_integrated_fade
                        end if
                    end if
     
                    if theType is in index36_fade_mode and item 36 of propertiesToColumnsRef is not 0 then
                        set theItem to fade mode of theCue
                        if theItem is absolute then
                            set item (item 36 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants36_fade_mode
                        else if theItem is relative then
                            set item (item 36 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants36_fade_mode
                        end if
                    end if
     
                    if theType is in index37_stop_target_when_done and item 37 of propertiesToColumnsRef is not 0 then
                        set theItem to stop target when done of theCue
                        if theItem is true then
                            set item (item 37 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants37_stop_target_when_done
                        else if theItem is false then
                            set item (item 37 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants37_stop_target_when_done
                        end if
                    end if
     
                    if theType is in index38_auto_stop and item 38 of propertiesToColumnsRef is not 0 then
                        set theItem to auto stop of theCue
                        if theItem is true then
                            set item (item 38 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants38_auto_stop
                        else if theItem is false then
                            set item (item 38 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants38_auto_stop
                        end if
                    end if
     
                    if theType is in index39_layer and item 39 of propertiesToColumnsRef is not 0 then
                        set theItem to layer of theCue as string
                        if theItem is "1000" then
                            set theItem to layerThousandIsTop
                        end if
                        set item (item 39 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index40_full_screen and item 40 of propertiesToColumnsRef is not 0 then
                        set theItem to full screen of theCue
                        if theItem is true then
                            set item (item 40 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants40_full_screen
                        else if theItem is false then
                            set item (item 40 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants40_full_screen
                        end if
                    end if
     
                    if theType is in index41_preserve_aspect_ratio and item 41 of propertiesToColumnsRef is not 0 then
                        set theItem to preserve aspect ratio of theCue
                        if theItem is true then
                            set item (item 41 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants41_preserve_aspect_ratio
                        else if theItem is false then
                            set item (item 41 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants41_preserve_aspect_ratio
                        end if
                    end if
     
                    if theType is in index42_opacity and item 42 of propertiesToColumnsRef is not 0 then
                        set theItem to opacity of theCue as string
                        set item (item 42 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index43_translation_x and item 43 of propertiesToColumnsRef is not 0 then
                        set theItem to translation x of theCue as string
                        set item (item 43 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index44_translation_y and item 44 of propertiesToColumnsRef is not 0 then
                        set theItem to translation y of theCue as string
                        set item (item 44 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index45_rotation and item 45 of propertiesToColumnsRef is not 0 then
                        set theItem to rotation of theCue as string
                        set item (item 45 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index46_scale_x and item 46 of propertiesToColumnsRef is not 0 then
                        set theItem to scale x of theCue as string
                        set item (item 46 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index47_scale_y and item 47 of propertiesToColumnsRef is not 0 then
                        set theItem to scale y of theCue as string
                        set item (item 47 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index48_scale_locked and item 48 of propertiesToColumnsRef is not 0 then
                        set theItem to scale locked of theCue
                        if theItem is true then
                            set item (item 48 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants48_scale_locked
                        else if theItem is false then
                            set item (item 48 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants48_scale_locked
                        end if
                    end if
     
                    if theType is in index49_custom_rendering and item 49 of propertiesToColumnsRef is not 0 then
                        set theItem to custom rendering of theCue
                        if theItem is true then
                            set item (item 49 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants49_custom_rendering
                        else if theItem is false then
                            set item (item 49 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants49_custom_rendering
                        end if
                    end if
     
                    if theType is in index50_do_opacity and item 50 of propertiesToColumnsRef is not 0 then
                        set theItem to do opacity of theCue
                        if theItem is true then
                            set item (item 50 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants50_do_opacity
                        else if theItem is false then
                            set item (item 50 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants50_do_opacity
                        end if
                    end if
     
                    if theType is in index51_do_translation and item 51 of propertiesToColumnsRef is not 0 then
                        set theItem to do translation of theCue
                        if theItem is true then
                            set item (item 51 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants51_do_translation
                        else if theItem is false then
                            set item (item 51 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants51_do_translation
                        end if
                    end if
     
                    if theType is in index52_do_rotation and item 52 of propertiesToColumnsRef is not 0 then
                        set theItem to do rotation of theCue
                        if theItem is true then
                            set item (item 52 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants52_do_rotation
                        else if theItem is false then
                            set item (item 52 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants52_do_rotation
                        end if
                    end if
     
                    if theType is in index53_do_scale and item 53 of propertiesToColumnsRef is not 0 then
                        set theItem to do scale of theCue
                        if theItem is true then
                            set item (item 53 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants53_do_scale
                        else if theItem is false then
                            set item (item 53 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants53_do_scale
                        end if
                    end if
     
                    if theType is in index54_command and item 54 of propertiesToColumnsRef is not 0 then
                        set theItem to command of theCue
                        if theItem is note_on then
                            set item (item 54 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants54_command
                        else if theItem is note_off then
                            set item (item 54 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants54_command
                        else if theItem is program_change then
                            set item (item 54 of propertiesToColumnsRef) of thePropertiesRef to item 3 of constants54_command
                        else if theItem is control_change then
                            set item (item 54 of propertiesToColumnsRef) of thePropertiesRef to item 4 of constants54_command
                        else if theItem is key_pressure then
                            set item (item 54 of propertiesToColumnsRef) of thePropertiesRef to item 5 of constants54_command
                        else if theItem is channel_pressure then
                            set item (item 54 of propertiesToColumnsRef) of thePropertiesRef to item 6 of constants54_command
                        else if theItem is pitch_bend then
                            set item (item 54 of propertiesToColumnsRef) of thePropertiesRef to item 7 of constants54_command
                        end if
                    end if
     
                    if theType is in index55_channel and item 55 of propertiesToColumnsRef is not 0 then
                        set theItem to channel of theCue as string
                        set item (item 55 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index56_byte_one and item 56 of propertiesToColumnsRef is not 0 then
                        if command of theCue is not pitch_bend then
                            set theItem to byte one of theCue as string
                            set item (item 56 of propertiesToColumnsRef) of thePropertiesRef to theItem
                        end if
                    end if
     
                    if theType is in index57_byte_two and item 57 of propertiesToColumnsRef is not 0 then
                        if command of theCue is not pitch_bend and command of theCue is not program_change and ¬
                            command of theCue is not channel_pressure then
                            set theItem to byte two of theCue as string
                            set item (item 57 of propertiesToColumnsRef) of thePropertiesRef to theItem
                        end if
                    end if
     
                    if theType is in index58_byte_combo and item 58 of propertiesToColumnsRef is not 0 then
                        if command of theCue is pitch_bend then
                            set theItem to ((byte combo of theCue) - 8192) as string
                            set item (item 58 of propertiesToColumnsRef) of thePropertiesRef to theItem
                        end if
                    end if
     
                    if theType is in index59_end_value and item 59 of propertiesToColumnsRef is not 0 then
                        if command of theCue is not pitch_bend then
                            set theItem to end value of theCue as string
                        else
                            set theItem to ((end value of theCue) - 8192) as string
                        end if
                        set item (item 59 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index60_fade and item 60 of propertiesToColumnsRef is not 0 then
                        set theItem to fade of theCue
                        if theItem is enabled then
                            set item (item 60 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants60_fade
                        else if theItem is disabled then
                            set item (item 60 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants60_fade
                        end if
                    end if
     
                    if theType is in index61_command_format and item 61 of propertiesToColumnsRef is not 0 then
                        set theItem to command format of theCue as string
                        if theItem is in translation61_command_format then
                            repeat with j from 1 to count translation61_command_format by 2
                                if theItem is item j of translation61_command_format then
                                    set theItem to item (j + 1) of translation61_command_format
                                    exit repeat
                                end if
                            end repeat
                        else
                            set theItem to theItem & " (no match)"
                        end if
                        set item (item 61 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index62_command_number and item 62 of propertiesToColumnsRef is not 0 then
                        set theItem to command number of theCue as string
                        if theItem is in translation62_command_number then
                            repeat with j from 1 to count translation62_command_number by 2
                                if theItem is item j of translation62_command_number then
                                    set theItem to item (j + 1) of translation62_command_number
                                    exit repeat
                                end if
                            end repeat
                        else
                            set theItem to theItem & " (no match)"
                        end if
                        set item (item 62 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index63_q__number and item 63 of propertiesToColumnsRef is not 0 then
                        set theItem to q_number of theCue as string
                        set item (item 63 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index64_q__list and item 64 of propertiesToColumnsRef is not 0 then
                        set theItem to q_list of theCue as string
                        set item (item 64 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index65_q__path and item 65 of propertiesToColumnsRef is not 0 then
                        set theItem to q_path of theCue as string
                        set item (item 65 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index66_macro and item 66 of propertiesToColumnsRef is not 0 then
                        set theItem to macro of theCue as string
                        set item (item 66 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index67_control_number and item 67 of propertiesToColumnsRef is not 0 then
                        set theItem to control number of theCue as string
                        set item (item 67 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index68_control_value and item 68 of propertiesToColumnsRef is not 0 then
                        set theItem to control value of theCue as string
                        set item (item 68 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index69_hours and item 69 of propertiesToColumnsRef is not 0 then
                        set theItem to hours of theCue as string
                        set item (item 69 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index70_minutes and item 70 of propertiesToColumnsRef is not 0 then
                        set theItem to minutes of theCue as string
                        set item (item 70 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index71_seconds and item 71 of propertiesToColumnsRef is not 0 then
                        set theItem to seconds of theCue as string
                        set item (item 71 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index72_frames and item 72 of propertiesToColumnsRef is not 0 then
                        set theItem to frames of theCue as string
                        set item (item 72 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index73_subframes and item 73 of propertiesToColumnsRef is not 0 then
                        set theItem to subframes of theCue as string
                        set item (item 73 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index74_send_time_with_set and item 74 of propertiesToColumnsRef is not 0 then
                        set theItem to send time with set of theCue
                        if theItem is true then
                            set item (item 74 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants74_send_time_with_set
                        else if theItem is false then
                            set item (item 74 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants74_send_time_with_set
                        end if
                    end if
     
                    if theType is in index75_sysex_message and item 75 of propertiesToColumnsRef is not 0 then
                        set theItem to sysex message of theCue as string
                        set item (item 75 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index76_midi_destination and item 76 of propertiesToColumnsRef is not 0 then
                        set theItem to midi destination of theCue as string
                        set item (item 76 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index77_start_time_offset and item 77 of propertiesToColumnsRef is not 0 then
                        set theTime to start time offset of theCue
                        set theItem to my makeHHMMSSsss(theTime)
                        set item (item 77 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index78_fire_next_cue_when_loop_ends and item 78 of propertiesToColumnsRef is not 0 then
                        set theItem to fire next cue when loop ends of theCue
                        if theItem is true then
                            set item (item 78 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants78_fire_next_cue_when_loop_ends
                        else if theItem is false then
                            set item (item 78 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants78_fire_next_cue_when_loop_ends
                        end if
                    end if
     
                    if theType is in index79_stop_target_when_loop_ends and item 79 of propertiesToColumnsRef is not 0 then
                        set theItem to stop target when loop ends of theCue
                        if theItem is true then
                            set item (item 79 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants79_stop_target_when_loop_ends
                        else if theItem is false then
                            set item (item 79 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants79_stop_target_when_loop_ends
                        end if
                    end if
     
                    if theType is in index80_load_time and item 80 of propertiesToColumnsRef is not 0 then
                        set theTime to load time of theCue
                        set theItem to my makeHHMMSSsss(theTime)
                        set item (item 80 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index81_assigned_number and item 81 of propertiesToColumnsRef is not 0 then
                        set theItem to assigned number of theCue as string
                        set item (item 81 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 82 of propertiesToColumnsRef is not 0 then -- Identify which group this cue is in
                        set thisCue to uniqueID of theCue as string
                        set theItem to cuelistsNotInGroups -- Hopefully only cue lists will not have this variable reset by the next step
                        repeat with j from 1 to count groupMembershipRef by 2
                            if thisCue is item j of groupMembershipRef then
                                set theItem to item (j + 1) of groupMembershipRef
                                exit repeat
                            end if
                        end repeat
                        set item (item 82 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 83 of propertiesToColumnsRef is not 0 then -- index83_unique
                        set theItem to uniqueID of theCue as string
                        set item (item 83 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if item 84 of propertiesToColumnsRef is not 0 then -- index84_broken
                        set theItem to broken of theCue
                        if theItem is true then
                            set item (item 84 of propertiesToColumnsRef) of thePropertiesRef to item 1 of constants84_broken
                        else if theItem is false then
                            set item (item 84 of propertiesToColumnsRef) of thePropertiesRef to item 2 of constants84_broken
                        end if
                    end if
     
                    if theType is in index85_audio_input_channels and item 85 of propertiesToColumnsRef is not 0 then -- index85_audio_input_channels
                        set theItem to audio input channels of theCue as string
                        set item (item 85 of propertiesToColumnsRef) of thePropertiesRef to theItem
                    end if
     
                    if theType is in index86_start_value and item 86 of propertiesToColumnsRef is not 0 then
                        if command of theCue is not pitch_bend then
                            set theItem to start value of theCue as string
                        else
                            set theItem to ((start value of theCue) - 8192) as string -- No need to protect against trying to subract from "missing value"
                        end if
                        if theItem is not "missing value" then
                            set item (item 86 of propertiesToColumnsRef) of thePropertiesRef to theItem
                        end if
                    end if
     
                    if theType is in index_setLevel then
                        if theType is not "Fade" then -- This bit stops the script from reporting rows that aren't valid
                            set audioChannels to audio input channels of theCue
                        else
                            if broken of theCue is false then
                                try -- This protects against the cue target being a Group Cue
                                    set audioChannels to audio input channels of cue target of theCue
                                on error
                                    set audioChannels to 16
                                end try
                            else
                                set audioChannels to 16
                            end if
                        end if
                        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 ","
                            set theRow to text item 1 of excelCleanup as integer
                            if theRow is greater than audioChannels then
                                set theLevel to irrelevantCrosspoints
                            else
                                set theColumn to text item 2 of excelCleanup as integer
                                set theLevel to theCue getLevel row theRow column theColumn as number
                                if theLevel  item 1 of minusInfinity then
                                    set theLevel to item 2 of minusInfinity
                                end if
                            end if
                            set item eachLevelColumn of thePropertiesRef to theLevel
                        end repeat
                        set AppleScript's text item delimiters to tab
                    end if
     
                    -- Add thePropertiesRef to the end of theText
     
                    set theText to theText & return & thePropertiesRef as string
     
                    -- Countdown timer (and opportunity to escape)
     
                    if i mod 10 is 0 and (countCues - i) > 5 then
                        if application "QLab" is frontmost then
                            set timeTaken to ((time of (current date)) - startTime) as integer
                            set timeString to my makeMMSS(timeTaken)
                            display dialog (("Time elapsed: " & timeString & " - " & i as string) & " of " & countCues as string) & " cues 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
     
            end tell
     
        end tell
     
        -- Write the text back out to the file
     
        write theText to theOpenFile
        close access theOpenFile
     
        -- All done. Hoopla!
     
        tell application "QLab"
            set timeTaken to ((time of (current date)) - startTime) as integer
            set timeString to my makeNiceT(timeTaken)
            activate
            set whatNext to "" -- In case dialog gives up
            set whatNext to button returned of (display dialog "Done. 
     
    (That took " & timeString & ".)" with title dialogTitle with icon 1 ¬
                buttons {"Open in Excel", "Open in TextEdit", "Reveal in Finder"} default button "Reveal in Finder" giving up after 60)
        end tell
     
        set AppleScript's text item delimiters to currentTIDs
     
        if whatNext is "Open in Excel" then
            try
                tell application "Microsoft Excel"
                    set openAllColumnsAsText to {}
                    repeat with i from 1 to count headerRow
                        copy {i, text format} to end of openAllColumnsAsText
                    end repeat
                    activate
                    open text file filename newFile field info openAllColumnsAsText with tab
                end tell
            on error
                display dialog "That didn't work for some reason..." with title dialogTitle with icon 0 buttons {"OK"} default button "OK"
            end try
        else if whatNext is "Open in TextEdit" then
            try
                tell application "TextEdit"
                    activate
                    open newFile
                    set zoomed of front window to true
                end tell
            on error
                display dialog "That didn't work for some reason..." with title dialogTitle with icon 0 buttons {"OK"} default button "OK"
            end try
        else
            tell application "Finder"
                reveal newFile
            end tell
        end if
     
    on error number -128
        set AppleScript's text item delimiters to currentTIDs
        if fileMade is true then
            display dialog "You've cancelled too late in the process to stop me making a file, but here it is for you to dispose of..." with title ¬
                dialogTitle with icon 0 buttons {"Reveal in Finder"} default button "Reveal in Finder"
            try
                write theText to theOpenFile
                close access theOpenFile
            end try
            tell application "Finder"
                reveal newFile
                activate
            end tell
        end if
    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 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 makeHHMMSSss(howLong)
        set howManyHours to howLong div 3600
        if howManyHours > 9 then
            set hourString to (howManyHours as string) & ":"
        else if howManyHours > 0 then
            set hourString to "0" & (howManyHours as string) & ":"
        else
            set hourString to ""
        end if
        set howManyMinutes to (howLong mod 3600) div 60
        if howManyMinutes > 9 then
            set minuteString to (howManyMinutes as string)
        else
            set minuteString to "0" & (howManyMinutes as string)
        end if
        set howManySeconds to howLong mod 60
        set integralSeconds to howManySeconds div 1
        if integralSeconds > 9 then
            set secondString to (integralSeconds as string)
        else
            set secondString to "0" & (integralSeconds as string)
        end if
        set fractionalSeconds to howManySeconds mod 1
        set fractionalSeconds to round (100 * fractionalSeconds) rounding as taught in school
        if fractionalSeconds > 9 then
            set theFractionString to fractionalSeconds as string
        else
            set theFractionString to "0" & fractionalSeconds as string
        end if
        return hourString & minuteString & ":" & secondString & "." & theFractionString
    end makeHHMMSSss
     
    on makeHHMMSSsss(howLong)
        set howManyHours to howLong div 3600
        if howManyHours > 9 then
            set hourString to (howManyHours as string) & ":"
        else if howManyHours > 0 then
            set hourString to "0" & (howManyHours as string) & ":"
        else
            set hourString to ""
        end if
        set howManyMinutes to (howLong mod 3600) div 60
        if howManyMinutes > 9 then
            set minuteString to (howManyMinutes as string)
        else
            set minuteString to "0" & (howManyMinutes as string)
        end if
        set howManySeconds to howLong mod 60
        set integralSeconds to howManySeconds div 1
        if integralSeconds > 9 then
            set secondString to (integralSeconds as string)
        else
            set secondString to "0" & (integralSeconds as string)
        end if
        set fractionalSeconds to howManySeconds mod 1
        set fractionalSeconds to round (1000 * fractionalSeconds) rounding as taught in school
        if fractionalSeconds > 99 then
            set theFractionString to fractionalSeconds as string
        else if fractionalSeconds > 9 then
            set theFractionString to "0" & fractionalSeconds as string
        else
            set theFractionString to "00" & fractionalSeconds as string
        end if
        return hourString & minuteString & ":" & secondString & "." & theFractionString
    end makeHHMMSSsss
     
    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 a text file from cues *)