This is an "external script" - see QLab Scripts and Macros
(* Bodging a MIDI player: turn a Standard MIDI File into a sequence in QLab via a text file from http://jjlee.com/midi/mid2txt.php;
see second display dialog for further explanation
You can also host the php scripts on your own website: get them from http://staff.dasdeck.de/valentin/midi/; put the folder in ~/Sites;
give the user "www" read & write permissions to the folder & fire up Web Sharing...
This script is not designed to be run from within QLab!
v1.0: 26/09/09 Rich Walsh (with advice from Jeremy Lee, Sean Dougall & Christopher Ashworth)
v1.1: 27/09/09 Fixed a little bug with notTheFirstTempo; general tidy up
v1.2: 29/09/09 Added option to add cue for every line, countdown timer, time taken, more sophisticated ETA;
also implemented Jeremy Lee's modifications: Note On @ 0 shown as "Note Off" (made optional),
original text file details into notes of each cue (inc line #), bar|beat|tick added to names (my routine)
v1.3: 04/10/09 Found more efficient way of getting existingNumbers & handling lists
v1.4: 12/10/09 Snow Leopard can't "get running", so rewrote a sequence; also minor fix to subroutine
v1.5: 16/10/09 Now "tested" in Snow Leopard; expanded makeNiceT for hours; fixed a bug with first program change not working;
general tidying; new ETA figures
v1.6: 11/01/10 Added ability to deal with escape characters in text file (track names can contain spaces);
worked out less cumbersome way of moving into groups; byte combo origin offset; corrected minor typos; added tell front workspace for elegance;
wrapped text for better wiki experience; implemented dialogTitle for cross-script pillaging
<<< Last tested with: QLab 2.2.6; Mac OS 10.5.8 & 10.6.2 >>>
<<< A LITTLE DISCLAIMER: This was quite a complex bit of work for me, so I make no guarantees whatsoever that it will work for you: if it does, use it;
if it doesn't, I'd like to know, but I may choose not to do anything about it... >>> *)
-- ###FIXME### See 3x embedded comments in script
-- ###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)
-- ###BUG TBC### The two's complement hack to extract Key Signature might be wrong (the code might fail, or the reported key might be wrong)
-- ###BUG TBC### Not yet convinced that bar|beat|tick follows correct musical rules
-- ###ADD### Add Hot Key § at start of All Notes Off sequence - not currently scriptable...
-- ###ADD### Bypass the php altogether and pull the binary data from the file (!)
-- Declarations
global dialogTitle
set dialogTitle to "Bodging a MIDI player"
global currentTIDs
set currentTIDs to AppleScript's text item delimiters
global ticksPerQuarter, currentBar, currentBeat, currentTick, tickModulus, barModulus, barsBeatsTicks, combinedDeltaTick -- Used in bar|beat|tick counter
set acceptableStrings to {"On", "Off", "PoPr", "ChPr", "Par", "Pb", "PrCh"} -- List of strings that will be processed for straightforward MIDI events
set exoticStrings to {"Tempo", "SysEx", "KeySig", "TimeSig"} -- List of more complex strings to deal with
set keySignatures to {"C flat", "G flat", "D flat", "A flat", "E flat", "B flat", "F", "C", "G", "D", "A", "E", "B", "F sharp", "C sharp"}
-- Convert numbers in text file to familiar names (this is the order in an SMF)
set removalStrings to {"ch=", "n=", "v=", "c=", "p="} -- List of strings to remove from the text so as to only have numbers to deal with
set deltaTick to 0 -- Don't miss out times for lines that aren't "valid" events
set combinedDeltaTick to 0 -- Used to pass cumulative increments to bar|beat|tick counter
set channelsUsed to {} -- Tracker for all notes off at end
set notTheFirstTempo to false -- Don't create duplicate Memo Cue for first tempo event
set notTheFirstTimeSig to false -- Don't create duplicate Memo Cue for first time signature event
set skipTheProcessingAsNoCueWasMade to false -- If you don't make a cue, don't try to name it (etc) and end up naming the one before!
set sysExed to false -- Warning dialog for SysEx
set trackNames to {} -- Make a list of track names as we go
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 visitWebsite to button returned of (display dialog "This script will take the text output of the MIDI-to-text converter Jeremy Lee " & ¬
"has kindly hosted on his website and attempt to turn it into a Group Cue in QLab of those MIDI events.
For this to work, the files need to be Type 0 (single track) Standard MIDI Files before they are converted to text; if you need to flatten " & ¬
"them visit the type conversion website using the button below.
The next step is to run your MIDI file through the converter at the MIDI-to-text website, then copy the output (from \"MFile\" to \"TrkEnd\") " & ¬
"into a plain text file. Make sure to use the option \"Delta\" for TimestampType, and save the file with the name you would like to use for the cue.
You'll need a workspace open in QLab, but for best results don't have any cues selected. The bigger the file the longer and longer it takes, " & ¬
"in a kind of exponential way. With 500+ lines expect it to take at least 15 minutes!
The order of the events is entirely dictated by the order of the lines in the text file. If you particularly want things in the right order, " & ¬
"massage the text file first (the order of any pair of lines that both start with \"0\" can be reversed with no ill effects).
Don't be surprised if this isn't 100% successful (feel free to emit a small whoop if it is though)." with title dialogTitle with icon 1 ¬
buttons {"Type conversion website", "MIDI-to-text website", "Back to start"} default button "Back to start")
if visitWebsite is "MIDI-to-text website" then
open location "http://jjlee.com/midi/mid2txt.php"
return
else if visitWebsite is "Type conversion website" then
open location "http://jjlee.com/midi/convert.php"
return
end if
end if
end repeat
-- Check QLab is running
tell application "System Events"
set qLabIsRunning to count (every process whose name is "QLab")
end tell
if qLabIsRunning is 0 then
display dialog "QLab is not running." with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after 5
return
end if
-- Test for a workspace
tell application "QLab"
try
get selected of front workspace
on error
display dialog "There is no workspace open in QLab." with title dialogTitle with icon 0 buttons {"OK"} default button "OK" giving up after 5
return
end try
end tell
-- Get the file
set theFile to choose file with prompt ¬
"Please select the file that contains the output of the MIDI-to-text converter:" default location (path to desktop) without invisibles
set AppleScript's text item delimiters to ""
try
set rawText to read theFile
on error
my exitStrategy()
end try
tell application "System Events" -- Get just the name of the file, without the extension
set theExtension to name extension of theFile
if theExtension is "" then
set theName to name of theFile
else
set theFullName to name of theFile
set theName to text 1 through ((length of theFullName) - (length of theExtension) - 1) of theFullName
end if
end tell
-- Check the first line looks promising
set AppleScript's text item delimiters to space
try
set theRecord to every text item of paragraph 1 of rawText
on error
my exitStrategy()
end try
if item 1 of theRecord is not "MFile" then
my exitStrategy()
else if item 2 of theRecord is "1" then
set visitWebsite to button returned of (display dialog "This appears to be a Type 1 MIDI file (ie: multi-track), which I can't cope with I'm afraid. " & ¬
"Would you like to try again?" with title dialogTitle with icon 0 buttons {"Type conversion website", "OK"} default button "OK")
if visitWebsite is "Type conversion website" then
open location "http://jjlee.com/midi/convert.php"
end if
set AppleScript's text item delimiters to currentTIDs
return
end if
-- Check for TimestampType=Delta
if rawText does not contain "TimestampType=Delta" then
set visitWebsite to button returned of (display dialog "I don't think this file was converted with the \"Delta\" option for TimestampType. " & ¬
"Would you like to try again?" with title dialogTitle with icon 0 buttons {"MIDI-to-text website", "OK"} default button "OK")
if visitWebsite is "MIDI-to-text website" then
open location "http://jjlee.com/midi/mid2txt.php"
end if
set AppleScript's text item delimiters to currentTIDs
return
end if
-- Find out about $9N@0 = $8N
set noteOffVelocitySentiment to button returned of (display dialog "How do you feel about Note Ons with a velocity of 0?
Display them as Note On, or \"Note Off\"? (The underlying MIDI data won't be changed.)" with title dialogTitle with icon 1 ¬
buttons {"Note On", "Note Off"} default button "Note Off")
-- Make a cue for every line?
set everyLineIsACue to button returned of ¬
(display dialog "Would you like me to create a Memo Cue for any lines in the text file that I don't understand " & ¬
"(along with all the lines that contain just metadata)?" with title dialogTitle with icon 1 buttons {"Yes", "No"} default button "No")
-- How long is this going to take? Quite a bit of faff just to make a grammatically-correct yet, irritatingly, flashing dialog...
set countText to count paragraphs of rawText
-- ###FIXME### Current estimate based on second order polynomial interpolation of 3 sample data points (!): get more data
set theETA to 49 - 0.535 * countText + 0.005 * (countText ^ 2)
if theETA is greater than 40 then -- Don't waste 10s telling you it's going to take 30s!
set timeString to my makeNiceT(theETA)
set spuriousPlurals to " seconds"
repeat with i from 10 to 1 by -1
if i is 1 then
set spuriousPlurals to " second"
end if
set goOnThen to button returned of (display dialog (("There are " & countText as string) & " lines in this text file.
Based on current tests, this may take about " & timeString & " to process, possibly longer.
You have " & i as string) & spuriousPlurals & " to hit \"Cancel\"..." with title dialogTitle with icon 0 ¬
buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 1)
if goOnThen is "OK" then
exit repeat
end if
end repeat
end if
-- Set the tempo & prepare bar|beat|tick information (my version of Jeremy Lee's modification to add event time to start of each q name)
set tempoFromFile to 500000 -- Use default of 120bpm (ie: 0.5s per quarter) if no tempo found in file
set timeSigFromFile to "4/4"
set tempoFound to false
set timeSigFound to false
try
set ticksPerQuarter to item 4 of theRecord
if ticksPerQuarter is less than 0 then
display dialog "I think this file is in SMPTE timing rather than clicks and I'm not clever enough to deal with that. Sorry." with title ¬
dialogTitle with icon 0 buttons {"OK"} default button "OK"
set AppleScript's text item delimiters to currentTIDs
return
end if
repeat with i from 2 to countText -- Get the first tempo & time signature events that appear in the file (skipping the file header)
set theRecord to every text item of paragraph i of rawText
if (count theRecord) is greater than 1 then
if item 1 of theRecord > 0 then -- "Initial" values can't come after a delta time has been encountered
exit repeat
end if
if item 2 of theRecord is "Tempo" then
set tempoFromFile to item 3 of theRecord
set tempoFound to true
else if item 2 of theRecord is "TimeSig" then
set timeSigFromFile to item 3 of theRecord
set timeSigFound to true
end if
if tempoFound is true and timeSigFound is true then
exit repeat
end if
end if
end repeat
on error
my exitStrategy()
end try
set theTempo to 60 * (1000000 / tempoFromFile) -- This is the tempo as bpm (tempo is expressed in SMFs as the duration of a quarter note in microseconds)
set tickToTime to (tempoFromFile / 1000000) * (1 / ticksPerQuarter) -- This is the duration of each tick
set {currentBar, currentBeat, currentTick} to {1, 0, 0}
set AppleScript's text item delimiters to "/" -- Extract the relevant info
set theNumerator to text item 1 of timeSigFromFile
set theDenominator to text item 2 of timeSigFromFile
set AppleScript's text item delimiters to space
set barModulus to theNumerator
set tickModulus to ticksPerQuarter * (4 / theDenominator)
-- Remove unnecessary text
set theText to rawText -- Keep a copy of the untouched text for entry into notes
repeat with eachString in removalStrings
set AppleScript's text item delimiters to eachString
set cleanText to text items of theText
set AppleScript's text item delimiters to ""
set theText to cleanText as text
end repeat
set AppleScript's text item delimiters to space
-- 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
-- Make a new Group Cue for the sequence
make type "Group"
set theGroupCue to last item of (selected as list)
set mode of theGroupCue to fire_first_go_to_next_cue
set q name of theGroupCue to "Imported from MIDI/text file: " & theName
-- Make the first cue in the group
make type "Memo"
set cueTheFirst to last item of (selected as list)
set continue mode of cueTheFirst to auto_continue
set q name of cueTheFirst to "Ticks per quarter: " & ticksPerQuarter
-- Move the Memo Cue inside the Group Cue
set cueTheFirstID to uniqueID of cueTheFirst
set cueTheFirstIsIn to the first cue whose (q type is "Group" and cues contains cueTheFirst)
set theGroupCueID to uniqueID of theGroupCue
move (the first cue whose uniqueID is cueTheFirstID) of cueTheFirstIsIn to end of (the first cue whose uniqueID is theGroupCueID)
-- Back to the main part
make type "Memo"
set newCue to last item of (selected as list)
set continue mode of newCue to auto_continue
set tempoString to my toThreePlaces(theTempo) -- Express tempo to 3 decimal places
if tempoFound is false then
set qualifyString to " (assumed)"
set notes of newCue to "Tempo not found at top of file"
else
set qualifyString to ""
end if
set q name of newCue to "Initial tempo: " & tempoString & "bpm" & qualifyString
make type "Memo"
set newCue to last item of (selected as list)
set continue mode of newCue to auto_continue
if timeSigFound is false then
set qualifyString to " (assumed)"
set notes of newCue to "Time signature not found at top of file"
else
set qualifyString to ""
end if
set q name of newCue to "Initial time signature: " & timeSigFromFile & qualifyString
repeat with i from 1 to countText
set theRecord to paragraph i of theText
set rawRecord to paragraph i of rawText
set dirtyFields to text items of theRecord
set theFields to my parseSpaceDelimitedTextWithEscapeCharacters(dirtyFields)
try -- This protects against empty/incomplete lines
set eventType to item 2 of theFields
if eventType is in acceptableStrings then
set combinedDeltaTick to (item 1 of theFields) + deltaTick
my bbtCounter()
set preWait to combinedDeltaTick * tickToTime
set deltaTick to 0
set theChannel to item 3 of theFields
set byteOne to item 4 of theFields
try -- Not all events have 5 fields
set byteTwo to item 5 of theFields
end try
make type "MIDI"
set newCue to last item of (selected as list)
if eventType is "On" then
set command of newCue to note_on
set nameString to "Note On | "
if noteOffVelocitySentiment is "Note Off" and byteTwo is "0" then
set nameString to "\"Note Off\" | "
end if
else if eventType is "Off" then
set command of newCue to note_off
set nameString to "Note Off | "
else if eventType is "PoPr" then
set command of newCue to key_pressure
set nameString to "Key Pressure | "
else if eventType is "ChPr" then
set command of newCue to channel_pressure
set nameString to "Channel Pressure | "
else if eventType is "Par" then
set command of newCue to control_change
set nameString to "Control Change | "
else if eventType is "Pb" then
set command of newCue to pitch_bend
set nameString to "Pitch Bend | "
else if eventType is "PrCh" then
set command of newCue to program_change
set nameString to "Program Change | "
end if
if eventType is not "Pb" then
set byte one of newCue to byteOne
set nameString to nameString & byteOne as string
if eventType is not "ChPr" and eventType is not "PrCh" then
set byte two of newCue to byteTwo
set nameString to nameString & " @ " & byteTwo as string -- ###FIXME### Is a Note On @ 0 actually a Note Off @ 0,
(* or a Note Off @ 64? Current output is the former *)
end if
else
set byte combo of newCue to byteOne
set nameString to nameString & (byteOne - 8192) as string -- Pitch bend of 0 will appear as 8192 in the file
end if
set channel of newCue to theChannel
set pre wait of newCue to preWait
set q name of newCue to (barsBeatsTicks & " - Channel " & theChannel as string) & " | " & nameString
set notes of newCue to "Line " & i & ": " & rawRecord -- Put the text from the file into the notes of the cue (Jeremy Lee)
set continue mode of newCue to auto_continue
if theChannel is not in channelsUsed then
copy theChannel to end of channelsUsed
end if
else if eventType is in exoticStrings then
set combinedDeltaTick to (item 1 of theFields) + deltaTick
my bbtCounter()
set preWait to combinedDeltaTick * tickToTime
set deltaTick to 0
if eventType is "Tempo" then
if notTheFirstTempo is false and tempoFound is true then
set notTheFirstTempo to true
if everyLineIsACue is "No" then
set skipTheProcessingAsNoCueWasMade to true
else
make type "Memo"
set newCue to last item of (selected as list)
set tempoString to my toThreePlaces(theTempo)
set nameString to "Initial tempo metadata: " & tempoString & "bpm"
end if
else
set tempoFromFile to item 3 of theFields
set theTempo to 60 * (1000000 / tempoFromFile)
set tickToTime to (tempoFromFile / 1000000) * (1 / ticksPerQuarter)
make type "Memo"
set newCue to last item of (selected as list)
set tempoString to my toThreePlaces(theTempo)
set nameString to "Tempo: " & tempoString & "bpm"
end if
end if
if eventType is "SysEx" then -- There is no error checking for this!
set sysExed to true
make type "MIDI SysEx"
set newCue to last item of (selected as list)
set howManyFields to count theFields
set theSysEx to {}
repeat with j from 4 to (howManyFields - 1)
copy item j of theFields to end of theSysEx
end repeat
set theSysExString to theSysEx as text
try
set sysex message of newCue to theSysExString
on error
my exitStrategy()
end try
set nameString to ("SysEx | " & (howManyFields - 4) as string) & " bytes |"
end if
if eventType is "KeySig" then
make type "Memo"
set newCue to last item of (selected as list)
try
set keySigNumber to item 3 of theFields as number
if keySigNumber is greater than 127 then
set keySigNumber to (keySigNumber - 256) -- MIDI-to-text converter not handling this item well
end if
set keySigNumber to keySigNumber + 8
if keySigNumber is greater than 0 then
set keySigString to item keySigNumber of keySignatures
else
set keySigString to "Invalid data..."
end if
on error
set keySigString to "Invalid data..."
end try
set nameString to "Key: " & keySigString & " " & item 4 of theFields
end if
if eventType is "TimeSig" then
if notTheFirstTimeSig is false and timeSigFound is true then
set notTheFirstTimeSig to true
if everyLineIsACue is "No" then
set skipTheProcessingAsNoCueWasMade to true
else
make type "Memo"
set newCue to last item of (selected as list)
set nameString to "Initial time signature metadata: " & timeSigFromFile
end if
else
set timeSig to item 3 of theFields
set AppleScript's text item delimiters to "/" -- Extract the relevant info
set theNumerator to text item 1 of timeSig
set theDenominator to text item 2 of timeSig
set AppleScript's text item delimiters to space
set barModulus to theNumerator
set tickModulus to ticksPerQuarter * (4 / theDenominator)
if (currentBar + currentBeat + currentTick) is not 1 then -- Force a new bar, but only if we've got past the very start of the file!
set currentBar to currentBar + 1
set {currentBeat, currentTick} to {0, 0}
my bbtCounter() -- Force the bar|beat|tick counter to display new information
end if
make type "Memo"
set newCue to last item of (selected as list)
set nameString to "Time signature: " & timeSig
end if
end if
if skipTheProcessingAsNoCueWasMade is false then
set pre wait of newCue to preWait
set q name of newCue to barsBeatsTicks & " - " & nameString
set notes of newCue to "Line " & i & ": " & rawRecord -- Put the text from the file into the notes of the cue (Jeremy Lee)
set continue mode of newCue to auto_continue
else
set skipTheProcessingAsNoCueWasMade to false
end if
else if eventType is "Meta" and item 3 of theFields is "TrkName" then
copy item 4 of theFields to end of trackNames
if everyLineIsACue is "No" then
set deltaTick to deltaTick + (item 1 of theFields)
else
set combinedDeltaTick to (item 1 of theFields) + deltaTick
my bbtCounter()
set preWait to combinedDeltaTick * tickToTime
set deltaTick to 0
make type "Memo"
set newCue to last item of (selected as list)
set pre wait of newCue to preWait
set q name of newCue to barsBeatsTicks & " - Track name: " & item 4 of theFields
set notes of newCue to "Line " & i & ": " & rawRecord -- Put the text from the file into the notes of the cue (Jeremy Lee)
set continue mode of newCue to auto_continue
end if
else if eventType is "Meta" and item 3 of theFields is "TrkEnd" then
if everyLineIsACue is "No" then
try
set deltaTick to deltaTick + (item 1 of theFields)
end try
else
set combinedDeltaTick to (item 1 of theFields) + deltaTick
my bbtCounter()
set preWait to combinedDeltaTick * tickToTime
set deltaTick to 0
make type "Memo"
set newCue to last item of (selected as list)
set pre wait of newCue to preWait
set q name of newCue to barsBeatsTicks & " - End of track"
set notes of newCue to "Line " & i & ": " & rawRecord -- Put the text from the file into the notes of the cue (Jeremy Lee)
set continue mode of newCue to auto_continue
end if
else
if everyLineIsACue is "No" then
try
set deltaTick to deltaTick + (item 1 of theFields)
end try
else
set combinedDeltaTick to (item 1 of theFields) + deltaTick
my bbtCounter()
set preWait to combinedDeltaTick * tickToTime
set deltaTick to 0
make type "Memo"
set newCue to last item of (selected as list)
set pre wait of newCue to preWait
set q name of newCue to barsBeatsTicks & " - Unprocessed line"
set notes of newCue to "Line " & i & ": " & rawRecord -- Put the text from the file into the notes of the cue (Jeremy Lee)
set continue mode of newCue to auto_continue
end if
end if
on error
if everyLineIsACue is "Yes" then
make type "Memo"
set newCue to last item of (selected as list)
try
set processCheck to item 1 of theFields
if processCheck is "MFile" then
set q name of newCue to "Start of file"
else if processCheck is "TrkEnd" then
set q name of newCue to "End of file"
else if processCheck is "0" then
set q name of newCue to "Unprocessed line (no event)"
else
set q name of newCue to "Unprocessed line (no time marker)"
end if
on error
set q name of newCue to "Unprocessed line (no time marker)"
end try
set notes of newCue to "Line " & i & ": " & rawRecord -- Put the text from the file into the notes of the cue (Jeremy Lee)
set continue mode of newCue to auto_continue
end if
try
set endCheck to item 1 of theFields
if endCheck is "TrkEnd" then -- Special case for end of file:
(* all notes off for all channels used - and always add a Memo to make sure final cue isn't an auto-continue! *)
set numberOfChannels to count channelsUsed
if numberOfChannels is not 0 then
set currentCue to last item of (selected as list)
set continue mode of currentCue to auto_continue
set sortedChannels to my simple_sort(channelsUsed)
repeat with j from 1 to numberOfChannels
set eachChannel to item j of sortedChannels
make type "MIDI"
set newCue to last item of (selected as list)
if j is 1 then
set pre wait of newCue to deltaTick * tickToTime
-- set hot key of newCue to "§" (###FIXME### Not currently scriptable: QLab 2.2.6)
end if
set command of newCue to control_change
set byte one of newCue to 123
set byte two of newCue to 0
set channel of newCue to eachChannel
set q name of newCue to barsBeatsTicks & " - All Notes Off: Channel " & eachChannel
set continue mode of newCue to auto_continue
end repeat
end if
make type "Memo"
set newCue to last item of (selected as list)
set q name of newCue to "Number of named tracks declared in file metadata: " & (count trackNames) as string
set AppleScript's text item delimiters to return
set allTracks to trackNames as text
set AppleScript's text item delimiters to "\""
set cleanTracks to text items of allTracks
set AppleScript's text item delimiters to ""
set allTracks to cleanTracks as text
set notes of newCue to allTracks
exit repeat -- Exit the overall repeat as no more cues to add after track end!
end if
end try
end try
if i mod 50 is 0 and (countText - i) > 25 then -- Countdown timer (and opportunity to escape)
set timeTaken to ((time of (current date)) - startTime) as integer
set timeString to my makeMMSS(timeTaken)
if application "QLab" is frontmost then
display dialog (("Time elapsed: " & timeString & " - " & i as string) & " of " & countText as string) & " lines done..." with title ¬
dialogTitle with icon 1 buttons {"Cancel", "OK"} default button "OK" cancel button "Cancel" giving up after 1
end if
end if
end repeat
end tell
if sysExed is true then
set sysExDisclaimer to "
I haven't checked those SysEx cues, so look out..."
else
set sysExDisclaimer to ""
end if
set timeTaken to ((time of (current date)) - startTime) as integer
set timeString to my makeNiceT(timeTaken)
activate
display dialog "Done.
(That took " & timeString & ".)" & sysExDisclaimer with title dialogTitle with icon 1 buttons {"OK"} default button "OK" giving up after 60
end tell
set AppleScript's text item delimiters to currentTIDs
on error number -128
set AppleScript's text item delimiters to currentTIDs
end try
-- Subroutines
on 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
error number -128
end exitStrategy
on parseSpaceDelimitedTextWithEscapeCharacters(theText) -- Deal with " as escape character
set tempTIDS to AppleScript's text item delimiters
set AppleScript's text item delimiters to space
set dirtyText to text items of theText
set AppleScript's text item delimiters to ""
set cleanTextItems to {}
set midClean to false
set cleanStore to ""
repeat with eachItem in dirtyText
if eachItem starts with "\"" then
set cleanStore to (rest of characters of eachItem as string)
set midClean to true
else
if midClean is true then
if eachItem does not end with "\"" then
set cleanStore to cleanStore & space & eachItem
else
set cleanStore to cleanStore & space & (characters 1 thru ((count eachItem) - 1) of eachItem as string)
copy cleanStore as string to end of cleanTextItems
set midClean to false
end if
else
copy eachItem as string to end of cleanTextItems
end if
end if
end repeat
set AppleScript's text item delimiters to tempTIDS
return cleanTextItems
end parseSpaceDelimitedTextWithEscapeCharacters
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 toThreePlaces(theNumber)
set theIntegral to theNumber div 1
set theFraction to theNumber mod 1
set theFraction to round (1000 * theFraction) rounding as taught in school
if theFraction > 99 then
set theFractionString to theFraction as string
else if theFraction > 9 then
set theFractionString to "0" & theFraction as string
else
set theFractionString to "00" & theFraction as string
end if
return (theIntegral as string) & "." & theFractionString
end toThreePlaces
on bbtCounter()
set tickCarry to (currentTick + combinedDeltaTick) div tickModulus
set currentTick to (currentTick + combinedDeltaTick) mod tickModulus as integer
set beatCarry to (currentBeat + tickCarry) div barModulus
set currentBeat to (currentBeat + tickCarry) mod barModulus as integer
set currentBar to currentBar + beatCarry as integer
set tickString to currentTick as string
repeat until length of tickString is 3
set tickString to "0" & tickString
end repeat
set barsBeatsTicks to ((currentBar as string) & " | " & currentBeat + 1 as string) & " | " & tickString
end bbtCounter
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
-- This subroutine was taken from http://www.macosxautomation.com/applescript/sbrt/sbrt-05.html
on simple_sort(my_list)
set the index_list to {}
set the sorted_list to {}
repeat (the number of items in my_list) times
set the low_item to ""
repeat with i from 1 to (number of items in my_list)
if i is not in the index_list then
set this_item to item i of my_list as text
if the low_item is "" then
set the low_item to this_item
set the low_item_index to i
else if this_item comes before the low_item then
set the low_item to this_item
set the low_item_index to i
end if
end if
end repeat
set the end of sorted_list to the low_item
set the end of the index_list to the low_item_index
end repeat
return the sorted_list
end simple_sort
(* END: Bodging a MIDI player *)