How to write MIDI messages in JACK

Programming applications for making music on Linux.

Moderators: MattKingUSA, khz

Post Reply
f00bar
Established Member
Posts: 83
Joined: Sun May 12, 2013 7:40 pm

How to write MIDI messages in JACK

Post by f00bar »

When writing MIDI messages I use the following routines:

Code: Select all

void MuStudio::MIDI::OutputExported::messageWritePrepare(size_t n_frames)
	{
	buffer=jack_port_get_buffer((jack_port_t*)port,n_frames);
	jack_midi_clear_buffer(buffer);
	}

void MuStudio::MIDI::OutputExported::messageWrite(const Event& e)
	{
	jack_midi_data_t* outbuff = jack_midi_event_reserve( buffer, 0, 3);
	outbuff[0]=e.data.bytes[0];
	outbuff[1]=e.data.bytes[1];
	outbuff[2]=e.data.bytes[2];
	}
Called from a loop like this

Code: Select all

		int onProcess(size_t n_frames)
			{
			MuStudio::MIDI::Event event_in;
			bool event_has=midi_in.eventFirstGet(event_in,n_frames);

			midi_out.messageWritePrepare(n_frames);
			size_t now=0;
			while(n_frames)
				{
				if(event_has && event_in.time>=now)
					{
					if((event_in.data.bytes[0]&0xf0)==0x80
						|| (event_in.data.bytes[0]&0xf0)==0x90)
						{
						int key=event_in.data.bytes[1];
						key+=m_amount;
						event_in.data.bytes[1]=std::max(0,std::min(key,127));
						}
					midi_out.messageWrite(event_in);
					event_has=midi_in.eventNextGet(event_in);
					}
				--n_frames;
				++now;
				}
			return 0;
			}
I have two questions:

1. Should I pass "now" to MuStudio::MIDI::OutputExported::messageWrite in order to get perfect timing? Not really a problem since in my case the buffer is small anyway.

2. The Program change consists of two bytes only. Does this mean that I need special treatments for shorter messages to avoid problems. I have one device that seems to interpret a trailing zero byte as changing the program back to "Grand piano".
j_e_f_f_g
Established Member
Posts: 2032
Joined: Fri Aug 10, 2012 10:48 pm
Been thanked: 357 times

Re: How to write MIDI messages in JACK

Post by j_e_f_f_g »

f00bar wrote:one device that seems to interpret a trailing zero byte as changing the program back to "Grand piano".
That's because, although the message is 2 bytes, you're telling jack to send 3 bytes with the following line:

Code: Select all

outbuff = jack_midi_event_reserve( buffer, 0, 3);
Therefore the device regards that extra 0 byte as a second message -- another program change with "running status".

Here's the correct code:

Code: Select all

void MuStudio::MIDI::OutputExported::messageWrite(const Event& e)
{
   int length;

   if (e.data.bytes[0] < 0xF0)
      length = (e.data.bytes[0] >= 0xC0 &&  e.data.bytes[0] <= 0xDF) ? 2 : 3;
   else if (e.data.bytes[0] >= 0xF8)
      length = 1;
   else switch (e.data.bytes[0])
   {
      case 0xF1:
      case 0xF3:
         length = 2;
         break;
      case 0xF2:
         length = 3;
         break;
      case 0xF6:
         length = 1;
         break;
      default:
         return;
   }

   jack_midi_data_t* outbuff = jack_midi_event_reserve( buffer, 0, length);
   while (length--) outbuff[length]=e.data.bytes[length];
}

Author of BackupBand at https://sourceforge.net/projects/backupband/files/
My fans show their support by mentioning my name in their signature.

f00bar
Established Member
Posts: 83
Joined: Sun May 12, 2013 7:40 pm

Re: How to write MIDI messages in JACK

Post by f00bar »

This changed did it. Now what about the event time?
ssj71
Established Member
Posts: 1294
Joined: Tue Sep 25, 2012 6:36 pm
Has thanked: 1 time

Re: How to write MIDI messages in JACK

Post by ssj71 »

Code: Select all

	ev.time = jack_frame_time(jack_client); 
I used the jack keyboard app as my JACK implementation example. Perhaps there are better ones. Its all at
http://sourceforge.net/p/jack-keyboard/ ... keyboard.c
_ssj71

music: https://soundcloud.com/ssj71
My plugins are Infamous! http://ssj71.github.io/infamousPlugins
I just want to get back to making music!
tramp
Established Member
Posts: 2335
Joined: Mon Jul 01, 2013 8:13 am
Has thanked: 9 times
Been thanked: 454 times

Re: How to write MIDI messages in JACK

Post by tramp »

f00bar wrote:1. Should I pass "now" to MuStudio::MIDI::OutputExported::messageWrite in order to get perfect timing? Not really a problem since in my case the buffer is small anyway
No. That isn't needed in jack-midi.
Her is a old, but still true quota.
In ALSA you can timestamp events, but (as far as I know) there is no way
to map the timestamps to the JACK frame time without some jitter. In
JACK MIDI all events are given a timestamp which is the frame offset
from the start of the current JACK period. This means that JACK MIDI
always is perfectly synchronised with JACK audio, even when
freewheeling, and you also get all the MIDI events given to you in an
input port buffer in the process() callback, (almost) just like audio -
no need to read from a file descriptor in a separate thread, like you do
with ALSA, with all the synchronisation and programming problems that
adds.
http://lists.gnu.org/archive/html/om-sy ... 00033.html
On the road again.
Drumfix
Established Member
Posts: 299
Joined: Mon Jan 26, 2009 5:15 pm
Been thanked: 11 times

Re: How to write MIDI messages in JACK

Post by Drumfix »

1. The event time has unfortunately to be relative to the start of the current processing cycle, so the line for time = now should read:

Code: Select all

ev.time = jack_frames_since_cycle_start(jack_client);
And the rule is 0 <= ev.time < nframes, which means that one cannot schedule midi events ahead for output in a later processing cycle to compensate for the latency of the audio output.

2. Thus the statement of Lars Luthman is only partially true.

While the design of jack_midi is perfect for driving other jack clients, it is almost a complete fail for driving external midi equipment. The reason is that no matter what frame you set for a midi event, the events will be output to jack's alsa midi thread
after all jack clients have finished their current processing cycle.
Example: Suppose you use a jack buffer length of 1024 and want to send midi events at frames 0, 500 and 1000. Now if your jack clients use up about 80% of the processing cycle, the earliest a midi event will be output to alsa is 1024 * 0.8 = frame 819.

The consequence is that jack midi can only be used for driving external equipment, if the jack buffer size is set as small as possible (IMO no more than 64 frames). And then setting the ev.time to anything other than 0 is more or less unnecessary.
Post Reply