MIDI to System Events: Can I force a single instance of a Script?

Hello everyone,
I’m new, as of yesterday to Applescripts, though I know AutoHotKey’s language fairly well and have used Macs for years.

My current script is an Applescript triggered by a MIDI message.
I’m using MidiPipe to both listen for MIDI and execute.

on runme(message)
tell application “System Events”
if (item 1 of message = 176)
and (item 2 of message = 17)
and (item 3 of message =65)
then key code 126 – (up arrow)
end if
end tell
end runme

This works fine.

Now what I’d like to do is introduce a delay after the keystroke:

on runme(message)
tell application “System Events”
if (item 1 of message = 176)
and (item 2 of message = 17)
and (item 3 of message =65)
then key code 126 – (up arrow)
delay 1
end if
end tell
end runme

The reason for this is to slow down the response of the rotary encoder.
This script works…

…but a problem:
When I let go the rotary encoder, there is a buffer of keystrokes that runs them all before stopping.

Question:
Can I force a single instance of the script?

There was a command for this in AutoHotkey. No script would run while #forceSingleInstance until the first one finished.

Is there a way to tell “System Events” to reject the instruction if another is running?
Maybe the logical test for the script can check to see before “then key code 126” if one is already running?

Thank you for any help.
Bobby

I have no idea how you run it, but what you can do in the script, is to test if a certain file exists before you execute that block, and only start the block if that file doesn’t exist. Then you will create that file, so that any other instance that may be started botches. Then as the last thing within the block, you’ll remove the file.

It is not 100% bullet proof, but it should work in 99.99% of the time.

To test for a file:

set it_exists to (do shell script " test -e ~/Desktop/.DS_Store && echo \"true\"" as boolean)

To create a file:

do shell script "touch myfile"

to remove a file:

do shell script "rm myfile"

I don’t know what you know or don’t know, if you are comfortable with testing for file existance with System Events, then nothing is better than than that.

if exists file "posix/path/to/file"

This is not hindering other instances, but it should make them botch. I hope this helps, and welcome to MacScripters.

:slight_smile:

Hi Bobby. Welcome to MacScripter.

Your post makes little sense at all to anyone unfamiliar with MidiPipe. I gather from having just downloaded and looked at it that AppleScripts are run by a “tool” called “AppleScript Trigger”, that the AppleScript code is included in the tool rather than being compiled and saved to disk by an editor such as “AppleScript Editor”, and that the form of the “script” is actually a handler called ‘runme()’ which receives a list of numbers, presumably MIDI byte values. I can’t find any reference to a “rotary encoder” or what pressing the up arrow does.

A possible interpretation of your query is that you want the script to execute not more than once a second, regardless of the flow of data through the pipe:

property delayEnd : missing value

on runme(message)
	if (message begins with {176, 17, 65}) then
		set now to (current date)
		if ((delayEnd is missing value) or (now ≥ delayEnd)) then -- Only execute if this is the first call or the non-execution period's expired.
			tell application "System Events" to key code 126 -- (up arrow)
			set delayEnd to now + 1 -- Set the expiry time for a one-second non-execution period beginning now.
		end if
	end if
end runme

By the way, MacScripter fora support [applescript] and [/applescript] tags, which you can put round code when you post it so that it appears with a clickable link as above. You can either write them in yourself or use the AppleScript button just above the posting window.

Hi guys.:slight_smile:

Nigel, thanks for doing the legwork with MidiPipe.

The “rotary encoder” is the physical knob device I’m using. Sends Midi messages because another app is translating and sending them forward.

The “up arrow” is what I’m trying to simulate in the Finder and applications.

Inside MidiPipe there is a “compiled/not compiled” status that compiles automatically when it first receives the proper Midi message. I have no idea where it keeps this script. MidiPipe suggests testing the script in Applescript Editor first and pasting in.

Having used first Applescript Editor to get familiar with sending keystrokes, the “runme(message)” confused me at first. I am assuming it is not Applescript command but internal MidiPipe language.

From the two suggestions, I will mess with both and give you feedback.

Bobby

Oh, and the (176,17,65)…

The three parts of the Midi Message are:

“176” says I am a CC message on Midi channel 1 (176+0, channel 2 would be 176+1=177)

“17” says I am CC number 17 (out of 127, and CC is abbreviation for “Control Change”)

“65” says I am a value of 65 (out of 127)

I think it should be:

set it_exists to (do shell script " test -e ~/Desktop/.DS_Store && echo yes || echo no") as boolean

Now when the file doesn’t exists it won’t throw an error. Also as boolean coercion should in this case be outside the parentheses.

Hello.

Adding || echo "false" is important! I’m sorry for omitting that, and thanks for the reminder. Where you put the coercion to boolean, works both ways, within, and outside the parenthesis, at least on my machine.

At least the OP can use System Events, for testing the existance of the file, by the fragment longer down, which I recommend, since System Events is single-threaded, and will handle operations in the order received, this will help, secluding the job to one instance of the script, shouldn’t Nigel’s solution work, which I hope it does, since it is a much more cleaner solution.

It’s the way in to a ‘handler’, which is the AppleScript equivalent of a subroutine or function or process in other languages. Basically, it’s a piece of code which can be invoked from other points in the script or program without having to be written out in full at all those points.

It looks as if an “AppleScript Trigger” script is built into the tool and its ‘run’ handler (not shown in the code we see) contains only the instructions necessary to pass an AppleScript list of MIDI-message code values to a handler called ‘runme’. It’s this handler which the user has to write. If you want to test it in AppleScript Editor, you have to write your own run handler with some test values:

-- on run
set MIDImessage to {176, 17, 65}
runme(MIDImessage)
-- end run

property delayEnd : missing value

on runme(message)
	if (message begins with {176, 17, 65}) then
		set now to (current date)
		if ((delayEnd is missing value) or (now ≥ delayEnd)) then -- Only execute if this is the first call or the non-execution period's expired.
			tell application "System Events" to key code 126 -- (up arrow)
			set delayEnd to now + 1 -- Set the expiry time for a one-second non-execution period beginning now.
		end if
	end if
end runme

I’ve realised that the way my code in post #3 was originally written, it actually gave a non-execution period of two seconds rather than one. I’ve now changed ‘>’ to ‘≥’ both there and here to correct this.

You’re welcome :slight_smile:

Just for the record: On 10.6 and 10.8 (I don’t have 10.7) it does matter where the coercion is made. Inside the parenthesis it will return always true no matter if the file exists or not. It will return the proper value when the coercion is made outside the parenthesis.

Inside or outside the parenthesis is with do shell script an difference. The as keyword in do shell script is not an standard coercion according to the standard addition dictionary; it makes sense why they behave differently.

Hi,

If anyones interested, I was working on this as a compiled script storing the date last run property in the script’s comment:

on run the_msg
	runme(the_msg)
end run
-- script's comment contains date last run or dummy date
-- your stuff only runs 1 minute after last run
on runme(message)
	set my_ref to (path to me)
	set cur_date to (current date)
	tell application "Finder"
		if comment of my_ref is "" then
			-- set comment to dummy date
			set comment of my_ref to ((cur_date - 2 * minutes) as string)
		end if
		set my_comment to comment of my_ref
	end tell
	if cur_date > ((date my_comment) + 1 * minutes) then
		tell application "Finder"
			set comment of my_ref to (cur_date as string)
		end tell
		-- your stuff here
		display dialog "running"
	else -- do nothing
		display dialog "not running"
	end if
	return
end runme

Probably needs tweaking. I just finished it.

Unfortunately, as I pointed out in post #3, there is no script file. The script in question is built into an instance of a “tool” in a “pipe” in the application MidiPipe. (path to me) returns the path to the application. All discussion of files and coercions of booleans in shell scripts is irrelevant to the query.

Hi,

One thing I noticed about reading the post is that if you want to simulate keystrokes in a certain application, then you need to bring the process to the frontmost. For instance, If you want to keystroke in the Finder, do:

Tell application “Finder” to activate

Then, do the ‘key code’. You can also set Finder process frontmost to true.

gl,
kel

:o Indeed! I am so sorry, usually, when things works for one case, you can deduce that it will work for the other, and it worked for true. Sheakes head at my self, for being naïve.

Have a nice evening.

Here is my version of the script, it can han handle the midisequences quite fast. if it is too fast, just increase the delay. You must change the filename too .

property pxFilename : "/Users/mcusr/Desktop/runmelock.file"
on runme(message)
	if (message begins with {176, 17, 65}) then
		tell application "System Events"
			if not (exists file pxFilename) then
				do shell script "touch " & pxFilename
				key code 126 -- (up arrow)
				delay 0.12
				do shell script "rm " & pxFilename
			end if
		end tell
	end if
end runme

McUsrII,

I just tried your script and the effect is the same as just setting the “delay 1”. (without the file creation, as in my original script)

I can see the runmelock.file being created and deleted at the delay intervals.
However, all the Midi messages waiting in line still run themselves out as keystrokes.

So, the messages are not ignored but buffered.

Going to try the Nigel approach next.
Thanks for all the help!

Hi wilcofan,

You could have skipped to Nigel’s script. He downloaded your app.

Edited: I mean the app you’re using.

gl,
kel

Hello.

I’m looking around for MidiPipe as I write this, or shortly after. If it is so, that the messages is queued up, until the rest of the script is available again, then I wonder if there is something in a dictionary, to either drain the message queue, or some way to close it.

Edit
The dictionary doesn’t make me much wiser, as I am totally unfamiliar with midi-commands, but if there isn’t anything to close the port you are listening on with, then maybe, you’ll close down the app in the system event block, and restart it after the file is deleted again?

I’d also write a letter to the Developer if I were you, asking for advice, because this may be something that he hasn’t anticipated.

I would think that the midi-events got digested each and every time the runme handler was called, but maybe the script lacks something that makes them be digested?

maybe adding a test for the contents of the midibuffer, without doing anything?

Below is an attempt, hoping message is a defined property or global.

# this is just a poor attempt, for illustrating a possible
# way of fixing it, I have no idea if this will work.
# if message is a variable/property name, I have a little hope.
pproperty pxFilename : "/Users/mcusr/Desktop/runmelock.file"
on runme(message)
	if (message begins with {176, 17, 65}) then
		tell application "System Events"
			if not (exists file pxFilename) then
				do shell script "touch " & pxFilename
				key code 126 -- (up arrow)
				delay 0.12
				do shell script "rm " & pxFilename
			else
				set my message to {}
			end if
		end tell
	else
		set my message to {}
	end if
end runme

Hi Kel,

I did try Nigel’s first. When I got nothing I turned to McUsrII’s.

Then going back I realized I had the Midi message in Nigel’s wrong for the knob I was turning. :smiley:
So I have Nigel’s working. :slight_smile:

Now I can’t seem to get a smooth reliable turn of keystrokes on anything lower than 1 second. (which is too long)
Can this approach, the “set now to current date” work with milliseconds?
I tried 0.1, 0.5, etc. and the script seems to breakdown. (turns like it had no delay).
Then I set it to 0.9 and got sporadic results, sometimes quick, sometimes delayed.

Hello.

Try this, you’ll have to adjust the delay, and add the proper port numbers/ use the right knob.
:smiley: I’d love to try scrolling, by having a knob to turn. Seems like great fun!

on runme(message)
	if (message begins with {176, 17, 65}) then
		tell application "System Events"
			delay 0.12
			key code 126 -- (up arrow)
		end tell
	end if
end runme

This is where my mind is looking too. I tried breaking your code into two different scripts, triggered by the same message, one which would look for and create the file, and another which would do nothing if it did exist.

property pxFilename : "/Users/Robert/Desktop/runmelock.file"
on runme(message)
   if (message begins with {176, 16, 1}) then
      tell application "System Events"
         if not (exists file pxFilename) then
            do shell script "touch " & pxFilename
            delay 1
            do shell script "rm " & pxFilename
         end if
      end tell
   end if
end runme
property pxFilename : "/Users/Robert/Desktop/runmelock.file"
on runme(message)
   if (message begins with {176, 16, 1}) then
      tell application "System Events"
         if not (exists file pxFilename) then
            key code 125
         end if
      end tell
   end if
end runme

But this works the same as yours. So I’ll try your other one next.

This has the same effect. Delay works, then a run-out of buffered keystrokes.
I’ll be trying your previous one next.

BTW, I have two kinds of knobs on my device, one clicked and one freely spinning (the one we’re working with here). My music recording software responds to both key commands and hovering/scrolling over some parameters. This way I can have one knob do 15 functions depending on the modifiers. (which are also buttons on same device → Midi message → “System Events”) Either scrolling OR keystrokes from the same knob. A breakthrough in workflow since I’m limited by what the recording software will do by keystroke.

Key Maestro handles the rest when I need the MIDI->keystrokes turned into mouse emulation. (and yes, I tried to get Key Maestro to ignore some of the keystrokes for a slowdown, no avail)

Still in the setup phase but it is great fun!