AppleScript Trigger in Midipipe

Hello,

I’ve looked everywhere to find a means of scripting in Midipipe by Subtlesoft and there are bits here and there but nothing of doing midi tunings like the built in plugin MicroTuner.

I was hoping someone might know the syntax or where to learn the syntax for the midi protocols inside of the Midipipe program available through the AppleScript Trigger plugin.

My ultimate goal is to create much of the same functionality of the plugin MicroTuner but with more microtonal options. I need to be able to change the mapping of the keyboard and change the size of the octave and have a lot of flexibility to do whatever tunings I want to do.

Any help would be greatly appreciated. Thank you

The Most High said: “so that I may help you, help yourself.” Would you have at least some code snippets, any links, or at least something … Now I go at work. See you in the morning

The Most High never said such a thing but he said, “Apart from me you can do nothing.”

Now here are some code snippets as examples of the AppleScript Trigger plugin and some of the midi protocols. But I need to know how to use this syntax to code in tunings. I don’t fully understand how it is working.

tell application "MidiPipe"
	activate
	repeat with counter from 0 to 127
		MIDISend toPort "MidiPipe AppleScript Input" withData {192, counter}
		MIDISend toPort "MidiPipe AppleScript Input" withData {144, counter, 64, 144, 127 - counter, 64}
		delay 0.1
		MIDISend toPort "MidiPipe AppleScript Input" withData {128, counter, 0, 128, 127 - counter, 0}
	end repeat
end tell
-- this script filters out all Note On messages on channel "fromChannel" to "toChannel" with a velocity value less than "minvelocity"
property fromChannel : 1
property toChannel : 6
property minvelocity : 17
on runme(message)
	if ((item 1 of message ≥ (143 + fromChannel)) and (item 1 of message ≤ (143 + toChannel))) then
		if (item 3 of message > 0) then
			-- note on 
			if (item 3 of message ≥ minvelocity) then
				return message
			end if
		else
			-- note off 
			return message
		end if
	else
		-- message on different channel 
		return message
	end if
end runme

property channel : 1
property keymap : {{space, 0}, {"a", 0}, {"a", command down}, {"MidiPipe", 0}, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}

on runme(message)
	if (item 1 of message = (143 + channel)) then
		if (item 3 of message > 0) then
			-- note on 
			tell application "TextEdit"
				activate
			end tell
			tell application "System Events"
				tell process "TextEdit"
					keystroke item 1 of (item ((item 2 of message) + 1) of keymap) using item 2 of (item ((item 2 of message) + 1) of keymap)
				end tell
			end tell
		end if
	end if
end runme
-- this script transposes every note message on channel 1 up 12 keys if its on velocity is above 105 
property channel : 1 
property velocity : 105 
property transpose : 12 
property keydown : {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 
on runme(message) 
if (item 1 of message = (143 + channel)) then 
if (item 3 of message > 0) then 
-- note on 
if (item 3 of message > velocity) then 
-- remember key 
set item (item 2 of message) of keydown to 1 
-- transpose 
set item 2 of message to ((item 2 of message) + transpose) 
else 
-- safety for avoiding hanging notes 
if (item (item 2 of message) of keydown) = 1 then 
-- double note on (missing note off) -> transpose too 
set item 2 of message to ((item 2 of message) + transpose) 
end if 
end if 
return message 
else 
-- note off 
noteoff(message) 
return message 
end if 
else 
if (item 1 of message = (127 + channel)) then 
-- note off 
noteoff(message) 
return message 
else 
-- message on different channel 
return message 
end if 
end if 
end runme 
on noteoff(message) 
if (item (item 2 of message) of keydown) = 1 then 
-- forget key 
set item (item 2 of message) of keydown to 0 
-- transpose 
set item 2 of message to ((item 2 of message) + transpose) 
end if 
end noteoff 

These are all examples of what can be done in the AppleScript Trigger plugin but I wouldn’t know how to follow this syntax to create tunings since there is no example of that I can find. But maybe someone here knows how to code in a tuning through this.

Here is a link to the protocols for the Midi Messages which are being used in these snippets:
https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2

I’m not sure how to connect the syntax of this AppleScript Trigger plugin with the RPN Tuning messages at the bottom and how it would actually work.

And this is a quick tutorial on the basics of Midi messages but again I don’t understand how it would work with this:
http://www.music-software-development.com/midi-tutorial.html

Thanks for the links. This application interested me a lot.

So, let’s start to understand. Let’s start with the main thing: 1) In the MIDI application language, the word message is nothing more than the word command of the Applescript language. The only difference is that AppleSript commands have names, and in MIDI applications these names replace lists of numbers (that is, bytes). Moreover, these commands can be written in 3 forms: 1) as a decimal numbers 2) as a binary 3) as hexadecimal.

Second: the MidiPipe application itself has a minimal scripting interface. Only one command (MIDISend), except the standard ones. This means that to extend the functionality of the application you will have to use UI scripting. But as I see it, you can reduce UI scripting to a minimum by coding functionality in the form of on runme (message) subroutines that respond to MIDI messages that you yourself can create. Each such subroutine becomes a new custom command of MidiPipe, created by you yourself.

You can read the scripts above. For example, the item 2 of message represents the second byte of MIDI command (see protocols). Other example: lists of 128 digits 0 in the properties responds to maximum available value of something in MIDI tool (what concretely, I don’t know yet. Most probably, maximum of 128 available different notes)

To fully understand what is happening, we need to first examine The_MIDI_Specification.pdf and
MIDI tutorial will help you to understand how you can use the MIDI language to control any device that uses the MIDI protocol.


So, what is doing that script: MIDISend toPort “MidiPipe AppleScript Input” withData {192, counter} ???

OK, decimal 192 is hexadecimal 0xC0, - is a command SELECT SOUND IN THE CURRENT SOUND BANK
A synthesizer may contain one or more sound banks, each containing 128 sounds.

MIDISend toPort “MidiPipe AppleScript Input” says, that this command is sent to computer’s MidiPipe input device (see the picture, this is a synthesizer)

Second byte of Data, counter, is a number of current selected song (current in loop) of bank of synthesizer (songs in bank all together are 128)


Similar way, in the following code line, decimal 144 is hexadecimal 0x90, which is command START PLAY NOTE, following byte is note’s code. Third byte is a pitch code. This code line plays 2 notes together.

Similar way, in the following code line, decimal 128 is hexadecimal 0x80, which is command END PLAY NOTE, following byte is note’s code. Third byte is a pitch code.


Hm, I’m already starting to regret that I don’t have a MIDI instrument for connecting to a computer now.

Hi. Welcome to MacScripter.

There have been one or two queries here in the past about scripting MidiPipe. I don’t know if any of these may be useful to you:

https://macscripter.net/viewtopic.php?id=41100
https://macscripter.net/viewtopic.php?id=45832
https://macscripter.net/viewtopic.php?id=45870

The threads date from before MacScripter changed its text encoding, so some of the characters in the scripts, such as “≥” and “≤”, may not appear as originally intended. :frowning:

I can’t remember much about Midipipe now. As KniazidisR has observed, it only has one AppleScript command of its own, which outputs MIDI data. Its also possible to set up instances of its AppleScript Trigger tool, each containing a runme(message) handler which will run any suitable AppleScript code you write into it. The message parameter receives MIDI messages from the pipe and the handler has to look out for the one(s) of interest to it. I believe it can either modify them or do something else entirely.

Hope this helps.

Thanks guys. Yes the runme (message) is taking in whatever Midi message is generated through the Midi controller. It’s possible to make your computer keyboard a Midi controller of it’s own with other software if you don’t have a midi controller. I don’t know how to do it myself but I’ve seen it done online.

I should be able to take whatever message as a trigger for the code I put inside of the runme() and do whatever operations on it I want.

What I need to know is how to code up the Tuning Messages for this. It might be Midipipe specific but I wasn’t sure. It seems “item 1 of message” is an AppleScript terminology of the array that comes in?

So maybe there is a terminology for the Tuning Messages?

My goal is to write a microtonal tuning inside of the runme(). When you utilize the MicroTuner plugin it does 12 pitch bend messages, one for each key but no more. So I know that micro tunings will be done through utilizing the pitch bend message but since it only does 1 for each key and it only does it once for that key (effectively redefining the key for the keyboard) then I know that on the inside it is using the Midi protocol for the Tuning Request. The problem is, I don’t know how Midipipe is doing that in order to do my own custom tuning.

Yes. As far as I can remember, MidiPipe passes the MIDI stream to each “tool” in the pipe a MIDI message at a time. In the case of the AppleScript Trigger tool, the handler receives an array (called a ‘list’ in AppleScript) of two or more integers representing the two or more bytes in the message. “item 1” is the first integer in that list. The code in the handler then has to do the math on the integers to see if the message is one to which it needs to react:

First integer div 16: type of message
First integer mod 16 + 1: MIDI channel
Remaining integers: note or controller concerned, value to apply, anything else.

https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message
https://www.midi.org/specifications-old/item/table-2-expanded-messages-list-status-bytes
https://www.midi.org/specifications-old/item/table-3-control-change-messages-data-bytes-2

Hope this gets you somewhere. :slight_smile:

That does help. So I’m guessing that all of the tuning is done entirely with pitch bend messages and all I need to do is send the right pitch bend messages to get my tunings going? I was tricked by the “Tune Request” message. It appears it is not the message to do microtonal music but it is some other thing.

I’ve tried working through all of this and I think I’m closer to a solution but if anyone can write an example tuning in the AppleScript Trigger plugin then I could extrapolate to whatever tunings I’m using.

If I realize the solution I’ll post it here as well.

OK, I have figured out the pitch bend message it’s just an array with {c, l, m} where “c” is the channels 1-16 as midi values of 224-239; “l” is the LSB with midi values of 0-127 and “m” is the MSB with midi values of 0-127. The LSB and MSB combine to form a single value of the magnitude of the pitch bend.

For instance, to “bend” a note of a semitone higher, you must send a value of 0x3000, which will be sent as:

0xE0 - 0x00 - 0x60

Indeed, the value 0x3000 must be split into two 7-bit values, giving 0x60 and 0x00 for the MSB and LSB parts.

So here is my code:

property channel : 1
property pitchBend : {223 + channel, 63, 63}
on runme(message)
	if (item 1 of message = (143 + channel)) then
		if (item 2 of message = 60) then
--Id like to be able to create an extra message where I send the pitch bend along with the message
-- but this code doesn't work and even if it did is still not truly "tuning" the key but only playing the
-- key and a pitch bend at the same time
			#tell application "MidiPipe"
			#MIDISend toPort "MidiPipe AppleScript Input" withData pitchBend
			#end tell
			return pitchBend --here I lose the original message
		end if
	end if
	return message
end runme

My problem is that I need to know how to code in an actual tuning for that key individually instead of just sending a pitch bend message. But also, I am going to need to be able to pass the original message through this trigger so that not only do I do the pitch bend or tuning of the key but also continue to play the key which triggered the event.

Right now it either sends a pitch bend message or it will play the key not both.

I contacted the guy who did the Midi Tutorial from previous posts and he said that the tuning is done with System Exclusive Messages (Sysex). Here is the link for that but again, I cannot understand how to put that into this trigger. My assumption is that it will also be an array/list object but I’m not sure.

https://www.midi.org/specifications-old/item/table-4-universal-system-exclusive-messages

“c” is actually the Pitch Bend Change flag 224 (0xE0) plus the MIDI channel from 0 to 15 (ie. 1 less than when numbering from 1 to 16).

Based on theory, but unfortunately not currently testable by me, a Pitch Bend Change script might look something like this:

property channel : 1 -- Numbering channels as 1-16
property triggerNote : 60
property pitchBendAmount : 96 -- Not more than 8191 or less than -8191.

property channelValue : channel - 1 -- Channels are numbered 0-15 in the MIDI stream.
property NoteOnFlag : 9 * 16 -- 0x90, binary 10010000
property NoteOffFlag : 8 * 16 -- 0x80, binary 10000000
property PitchBendChangeFlag : 14 * 16 -- 0xE0, binary 11100000
-- The Pitch Bend centre position value is 8192 (0x2000), to which is added or subtracted the amount of the pitch change. The result is split into its lowest and next highest seven bits.
property pitchBendEffectMessage : {PitchBendChangeFlag + channelValue, (8192 + pitchBendAmount) mod 128, (8192 + pitchBendAmount) div 128 mod 128}
property pitchBendCentreMessage : {PitchBendChangeFlag + channelValue, 0, 64}

on runme(message)
	-- Get the status byte.
	set b1 to beginning of message
	-- If it's a Note On on the watched channel, and the note is the trigger one, output a Pitch Bend Change message and the Note On.
	-- Otherwise if it's a Note Off for the same channel and note, output the Note Off followed by a centring Pitch Bend Change.
	if (b1 = NoteOnFlag + channelValue) then
		if (item 2 of message = triggerNote) then set message to pitchBendEffectMessage & message
	else if (b1 = NoteOffFlag + channelValue) then
		if (item 2 of message = triggerNote) then set message to message & pitchBendCentreMessage
	end if
	
	return message
end runme

Note though that the channel would remain “bent” until the Note Off for the trigger note!

I’d guess that “pass through” has to be left unchecked in the AppleScript Trigger tool so that only the output from the script goes to the next stage in the pipe.

Depending on how you want to implement your microtonality, MidiPipe also has a MicroTuner tool, which allows you define the distance between the individual semitones in an octave.

I tested that script and it works! Well sort of. It does pass the pitch bend and note on/off messages. But the pitch bend is undone before the note off message so you can hear the pitch bend inside of a single key stroke.

Honestly, all the extra math is unnecessary so long as I know the decimal values. It works exactly as a definition would:

Note On : 143 + (channel sent value)
Note Off : 127 + (channel sent value)
Pitch Bend : 223 + (channel sent value)

So I can send those messages that way and it works every time. You’d have to memorize two things: the CC number and to multiply by 16 rather than just a single number that I exchange for the CC number. On top of that I don’t have to do the -1 to channel bit since in this format I can work directly with the channel that I’m using in Midi.

So by translation the only change to your script from mine would be

property channel : 1
property noteOn : (143 + channel)
property noteOff : (127 + channel)
property pBL: 0
property pBM : 127
property pitchBend : {223 + channel, pBL, pBM}
property noPitchBend : {223 + channel, 0, 64}
on runme(message)
	if (item 1 of message = noteOn) then
		if (item 2 of message = 60) then
			set message to pitchBend & message --the only real change
			return message
		end if
	end if
--also accounting for removing the pitch bend
	if (item 1 of message = noteOff) then
		if (item 2 of message = 60) then
			set message to message & noPitchBend
			return message
		end if
	end if
	return message
end runme

Edit: I see now that the pitch bend has to turn off since it is being applied to every key on the keyboard. And this script above also works but in the same way as yours. You can hear the pitch bend inside of a single key stroke. I found by testing it that the pitch bend magnitude is mostly controlled by the MSB and the LSB appears to change it by a very small amount.

The problem now, is I need two different things: 1 - I need to change the pitch bend sensitivity to a full 12 semitones instead of the default +/- 2 semitones, 2 - I then need a means of changing the key as in a tuning change. I guess I have to turn the pitch bend message into a Sysex message that asks to change the tuning of one note; just like in the MicroTuner plugin. Would you know how to do that?

I certainly need to be able to play chords. So as it is, it would pitch bend everything while the trigger note is pressed. So the only solution to this is doing a Sysex message I believe.

NOW I understand what is going on here. MidiPipe’s plugin MicroTuner is setting every key of the same note into a separate channel and then applying the appropriate pitch bend for that channel to create the tuning. Now I understand this:

Utilizing this information, that would mean I would have to put every key on the keyboard that shares the same note into a separate channel. Then, I can apply the pitch bend to that channel as a whole and I won’t have to apply a pitch bend neutral and then I will hear my tunings.

I haven’t yet figured out how to do that.

I don’t want this to be a solution but it is a work around. If I put the MicroTuner plugin in the pipe and set it to Equal Temperament then this plugin will do the splitting of the keys for me. All I have to do is apply the correct pitch bends to the correct channels and I should be able to apply whatever tunings. The problem with this approach is that it limits me to 16 notes an octave since each channel available represents a unique note and there are only 16 midi channels to work with. But here is my work around so long as the MicroTuner is in the pipe at Equal Temperament.

-- these are the MicroTuner Pythagorean Tuning; the 0 pair is the skipped channel by MidiPipe
property tuningPBL : {32, 2, 96, 66, 32, 0, 102, 65, 38, 0, 0, 96, 64}
property tuningPBM : {60, 54, 62, 56, 65, 59, 52, 61, 55, 0, 64, 57, 66}
on runme(message)
	repeat with c from 1 to 13
		-- we have to skip 10 because MidiPipe skips it
		if (c is 10) then set c to (c + 1)
		-- now we set all midi values with respect to the channel we are in
		set noteOn to (143 + c)
		if (item 1 of message is noteOn) then -- we don't need to set all of them until this is true
			set pBL to item c of tuningPBL
			set pBM to item c of tuningPBM
			set pitchBend to {223 + c, pBL, pBM}
			set message to pitchBend & message
			return message
		end if
	end repeat
	return message
end runme

And this works! But I don’t like the fact that I have to constantly resend the same pitch bend message. Is there a way to send it once and only send a new pitch bend message when I need to?

Also if anyone is able to figure out how to send Sysex that would be far better than this so I can do more than 16 notes in an octave. But by using the MicroTuner plugin I am actually limited to just the 12 notes to an octave. Since I still don’t know how it is mapping all those keys to those different channels.

I found out that setting a flag outside will be remembered. So now this is a good solution for setting whatever tunings once without sending pitch bend messages over and over.


-- these are the MicroTuner Pythagorean Tuning; the 0 pair is the skipped channel by MidiPipe
property tuningPBL : {32, 2, 96, 66, 32, 0, 102, 65, 38, 0, 0, 96, 64}
property tuningPBM : {60, 54, 62, 56, 65, 59, 52, 61, 55, 0, 64, 57, 66}
property pitchBent : false
on runme(message)
	if (pitchBent is false) then
		set pitchBent to true
		repeat with c from 1 to 13
			if (c is 10) then set c to (c + 1)--MidiPipe skips channel 10 for some reason so we skip it here
			set pBL to item c of primaryPBL
			set pBM to item c of primaryPBM
			if (c is 1) then set pitchBend1 to {223 + c, pBL, pBM}
			if (c is 2) then set pitchBend2 to {223 + c, pBL, pBM}
			if (c is 3) then set pitchBend3 to {223 + c, pBL, pBM}
			if (c is 4) then set pitchBend4 to {223 + c, pBL, pBM}
			if (c is 5) then set pitchBend5 to {223 + c, pBL, pBM}
			if (c is 6) then set pitchBend6 to {223 + c, pBL, pBM}
			if (c is 7) then set pitchBend7 to {223 + c, pBL, pBM}
			if (c is 8) then set pitchBend8 to {223 + c, pBL, pBM}
			if (c is 9) then set pitchBend9 to {223 + c, pBL, pBM}
			if (c is 11) then set pitchBend11 to {223 + c, pBL, pBM}
			if (c is 12) then set pitchBend12 to {223 + c, pBL, pBM}
			if (c is 13) then set pitchBend13 to {223 + c, pBL, pBM}
		end repeat
		set message to pitchBend1 & pitchBend2 & pitchBend3 & pitchBend4 & pitchBend5 & pitchBend6 & pitchBend7 & pitchBend8 & pitchBend9 & pitchBend11 & pitchBend12 & pitchBend13 & message
end if
		return message
end runme

Very good. Only the code can be improved:


-- MicroTuner Pythagorean Tuning; the 0 pair is the skipped channel by MidiPipe
property tuningPBL : {32, 2, 96, 66, 32, 0, 102, 65, 38, 0, 0, 96, 64}
property tuningPBM : {60, 54, 62, 56, 65, 59, 52, 61, 55, 0, 64, 57, 66}
property pitchBent : false

on runme(message)
	if pitchBent = false then
		set {message, pitchBent} to {{}, true}
		repeat with c from 1 to 13
			if c ≠ 10 then set end of message to {223 + c, item c of tuningPBL, item c of tuningPBM}
		end repeat
		return message
	end if
end runme

That’s a great improvement. I was wondering how to simplify all those “if” statements.

In the MidiPipe app the message comes in as a list already so there is no need to define it as an empty list.

I have this problem however

-- account for the skipped channel in creating the saved previous message
				set previousMessage to {}
				set previousMessage to previousMessage & item 1 of message & item 2 of message & item 3 of message
				if (item 1 of message is (143 + 11) or item 1 of message is (143 + 12) or item 1 of message is (143 + 13)) then
					set item 1 of previousMessage to ((item 1 of previousMessage) - 1)
				end if

If I set the “previousMessage” to “message” directly then when I change it’s values it also changes the original message’s values. I don’t want that. So I had to set each individual item of the original “message” to the end of the “previousMessage” and this seems like a pain.

But I have to do this somehow because I need the channels to aligned in one context but then I need to send the original message back after I change it with the previousMessage.

To achieve not changing of original message, instead of set command use copy command:

copy message to previousMessage

The difference with set and copy commands I explained already HERE. To understand what is explained, the main difference is that:

  1. the set command creates second reference to the first object in the memory, and
  2. the copy command creates second object in the memory, which you can control and change without automatically changes the first object’s bytes (that is, its values)

Excellent. I implemented these changes. For the life of me I couldn’t find what the dereference method was. I had no idea what to call the problem so I did that work around. Pretty much all of this is just “tell me the password and I can get in” type of thing.

Hi there I’m new here!
Because an app named ControllerMate doesn’t work anymore on Mac m1 silicon
I try to find another midi application ho do the same job.
Midipipe let me trigger midi messages from my Xtouch mini- midi controller.
Can someone pleas write a code after the midi message was trigger to be able to move the pointer
at a certain screen position with left click hold down when I press the controller pad?
It would be so helpful.Thanks!