<HTML> 
<BODY> 

<H1>The BeOS R5 Midi Kit protocol</H1>

<P>In the course of writing the OpenBeOS Midi Kit, I spent some time looking at 
how BeOS R5's libmidi2.so and midi_server communicate. Not out of a compulsion 
to clone this protocol, but to learn from it. After all, the Be engineers spent 
a lot of time thinking about this already, and it would be foolish not to build 
on their experience. Here is what I have found out.</P>

<P>Two kinds of communication happen: administrative tasks and MIDI events. The 
housekeeping stuff is done by sending BMessages between the BMidiRoster and the 
midi_server. MIDI events are sent between producers and consumers using ports, 
without intervention from the server.</P>

<P>This document describes the BMessage protocol. The protocol appears to be 
asynchronous, which means that when BMidiRoster sends a message to the 
midi_server, it does not wait around for a reply, even though the midi_server 
replies to all messages. The libmidi2 functions <I>do</I> block until the reply 
is received, though, so client code does not have to worry about any of 
this.</P>

<P>Both BMidiRoster and the midi_server can initiate messages. BMidiRoster 
typically sends a message when client code calls one of the functions from a 
libmidi2 class. When the midi_server sends messages, it is to keep BMidiRoster 
up-to-date about changes in the roster. BMidiRoster never replies to messages 
from the server. The encoding of the BMessage 'what' codes indicates their 
direction. The 'Mxxx' messages are sent from libmidi2 to the midi_server. The 
'mXXX' messages go the other way around: from the server to a client.</P>

<HR SIZE="1">

<H2>Who does what?</H2>

<P>The players here are the midi_server, which is a normal BApplication, and 
all the client apps, also BApplications. The client apps have loaded a copy of 
libmidi2 into their own address space. The main class from libmidi2 is 
BMidiRoster. The BMidiRoster has a BLooper that communicates with the 
midi_server's BLooper.</P>

<P>The midi_server keeps a list of <I>all</I> endpoints in the system, even 
local, nonpublished, ones. Each BMidiRoster instance keeps its own list of 
remote published endpoints, and all endpoints local to this application. It 
does not know about remote endpoints that are not published yet.</P>

<P>Whenever you make a change to one of your own endpoints, your BMidiRoster 
notifies the midi_server. If your endpoint is published, the midi_server then 
notifies all of the other BMidiRosters, so they can update their local rosters. 
It does <I>not</I> notify your own app! (Sometimes, however, the midi_server 
also notifies everyone else even if your local endpoint is <I>not</I> 
published. The reason for this escapes me, because the other BMidiRosters have 
no access to those endpoints anyway.)</P>

<P>By the way, "notification" here means the internal communications between 
server and libmidi, not the B_MIDI_EVENT messages you receive when you call 
BMidiRoster::StartWatching().</P>

<HR SIZE="1">

<H2>BMidiRoster::MidiRoster()</H2>

<P>The first time it is called, this function creates the one-and-only instance 
of BMidiRoster. Even if you don't explicitly call it yourself, it is used 
behind-the-scenes anyway by any of the other BMidiRoster functions. 
MidiRoster() constructs a BLooper and gets it running. Then it sends a 
BMessenger with the looper's address to the midi_server:</P>

<PRE><SMALL>
OUT BMessage: what = Mapp (0x4d617070, or 1298231408)
    entry       be:msngr, type='MSNG', c=1, size=24,         
</SMALL></PRE>

<P>The server now responds with mOBJ messages for all <I>remote</I> 
<I>published</I> producers and consumers. (Obviously, this list only contains 
remote objects because by now you can't have created any local endpoints 
yet.)</P>

<P>For a consumer this message looks like:</P>

<PRE><SMALL>
IN  BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
    entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x1 (1, '')
    entry     be:latency, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry        be:port, type='LONG', c=1, size= 4, data[0]: 0x1dab (7595, '')
    entry        be:name, type='CSTR', c=1, size=16, data[0]: "/dev/midi/vbus0"
</SMALL></PRE>

<P>(Oddness: why is be:latency a LONG and not a LLNG? Since latency is 
expressed in microseconds using a 64-bit bigtime_t, you'd expect the 
midi_server to send all 64 of those bits... In the 'Mnew' message, on the other 
hand, be:latency <I>is</I> a LLGN.)</P>

<P>And for a producer:</P>

<PRE><SMALL>
IN  BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
    entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x2 (2, '')
    entry        be:name, type='CSTR', c=1, size=16, data[0]: "/dev/midi/vbus0"
</SMALL></PRE>

<P>Note that the be:name field is not present if the endpoint has no name. That 
is, if the endpoint was constructed by passing a NULL name into the 
BMidiLocalConsumer() or BMidiLocalProducer() constructor.</P>

<P>Next up are notifications for <I>all</I> connections, even those between 
endpoints that are not registered:</P>

<PRE><SMALL>
IN  BMessage: what = mCON (0x6d434f4e, or 1833127758)
    entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
    entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
</SMALL></PRE>

<P>These messages are followed by an Msyn message:</P>

<PRE><SMALL>
IN  BMessage: what = Msyn (0x4d73796e, or 1299413358)
</SMALL></PRE>

<P>And finally the (asynchronous) reply:</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>Only after this reply is received, MidiRoster() returns.</P>

<P>The purpose of the Msyn message is not entirely clear. (Without it, Be's 
libmidi2 blocks in the MidiRoster() call.) Does it signify the end of the list 
of endpoints? Why doesn't libmidi2 simply wait for the final reply?</P>

<HR SIZE="1">

<H2>BMidiLocalProducer constructor</H2>

<P>BMidiRoster, on behalf of the constructor, sends the following to the 
midi_server:</P>

<PRE><SMALL>
OUT BMessage: what = Mnew (0x4d6e6577, or 1299080567)
    entry        be:type, type='CSTR', c=1, size=9, data[0]: "producer"
    entry        be:name, type='CSTR', c=1, size=21, data[0]: "MIDI Keyboard output"
</SMALL></PRE>

<P>The be:name field is optional.</P>

<P>The reply includes the ID for the new endpoint. This means that the 
midi_server assigns the IDs, and any endpoint gets an ID whether it is 
published or not.</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x11 (17, '')
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>Unlike many other Be API classes, BMidiLocalProducer and BMidiLocalConsumer 
don't have an InitCheck() method. But under certain odd circumstances (such as 
the midi_server not running), creating the endpoint might fail. How does client 
code check for that? Well, it turns out that upon failure, the endpoint is 
assigned ID 0, so you can check for that. In that case, the endpoint's refcount 
is 0 and you should not Release() it. (That is stupid, actually, because 
Release() is the only way that you can destroy the object. Our implementation 
should bump the endpoint to 1 even on failure!)</P>

<P>If another app creates a new endpoint, your BMidiRoster is not notified. The 
remote endpoint is not published yet, so your app is not supposed to see 
it.</P>

<HR SIZE="1">

<H2>BMidiLocalConsumer constructor</H2>

<P>This is similar to the BMidiLocalProducer constructor, although the contents 
of the message differ slightly. Again, be:name is optional.</P>

<PRE><SMALL>
OUT BMessage: what = Mnew (0x4d6e6577, or 1299080567)
    entry        be:type, type='CSTR', c=1, size=9, data[0]: "consumer"
    entry     be:latency, type='LLNG', c=1, size= 8, data[0]: 0x0 (0, '')
    entry        be:port, type='LONG', c=1, size= 4, data[0]: 0x4c0 (1216, '')
    entry        be:name, type='CSTR', c=1, size=13, data[0]: "InternalMIDI"  
</SMALL></PRE>

<P>And the reply:</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x11 (17, '')
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>Before it sends the message to the server, the constructor creates a new 
port with the name "MidiEventPort" and a queue length (capacity) of 1.</P>

<HR SIZE="1">

<H2>BMidiEndpoint::Register()<BR>
BMidiRoster::Register()</H2>

<P>Sends the same message for producers and consumers:</P>

<PRE><SMALL>
OUT BMessage: what = Mreg (0x4d726567, or 1299342695)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')     
</SMALL></PRE>

<P>The reply:</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>If you try to Register() an endpoint that is already registered, libmidi2 
still sends the message. (Which could mean that BMidiRoster does not keep track 
of this registered state.) The midi_server simply ignores that request, and 
sends back error code 0 (B_OK). So the API does not flag this as an error.</P>

<P>If you send an invalid be:id, the midi_server returns error code -1 (General 
OS Error, B_ERROR). If you try to Register() a remote endpoint, libmidi2 
immediately returns error code -1, and does not send a message to the 
server.</P>

<P>If another app Register()'s a producer, your BMidiRoster receives:</P>

<PRE><SMALL>
IN  BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
    entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x17 (23, '')
    entry        be:name, type='CSTR', c=1, size=7, data[0]: "a name"
</SMALL></PRE>
                                                                            
<P>If the other app registers a consumer, your BMidiRoster 
receives:</P>

<PRE><SMALL>
IN  BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
    entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x19 (25, '')
    entry     be:latency, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry        be:port, type='LONG', c=1, size= 4, data[0]: 0xde9 (3561, '')
    entry        be:name, type='CSTR', c=1, size=7, data[0]: "a name"
</SMALL></PRE>

<P>These are the same messages you get when your BMidiRoster instance is 
constructed. In both messages, the be:name field is optional again.</P>

<P>If the other app Register()'s the endpoint more than once, you still get 
only one notification. So the midi_server simply ignores that second publish 
request.</P>

<HR SIZE="1">

<H2>BMidiEndpoint::Unregister()<BR>
BMidiRoster::Unregister()</H2>

<P>Sends the same message for producers and consumers:</P>

<PRE><SMALL>
OUT BMessage: what = Munr (0x4d756e72, or 1299541618)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')       
</SMALL></PRE>

<P>The reply:</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>If you try to Unregister() and endpoint that is already unregistered, 
libmidi2 still sends the message. The midi_server simply ignores that request, 
and sends back error code 0 (B_OK). So the API does not flag this as an error. 
If you try to Unregister() a remote endpoint, libmidi2 immediately returns 
error code -1, and does not send a message to the server.</P>

<P>When another app Unregister()'s one of its own endpoints, your BMidiRoster 
receives:</P>

<PRE><SMALL>
IN  BMessage: what = mDEL (0x6d44454c, or 1833190732)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17 (23, '')             
</SMALL></PRE>

<P>When the other app deletes that endpoint (refcount is now 0) and it is not 
unregistered yet, your BMidiRoster also receives that mDEL message. Multiple 
Unregisters() are ignored again by the midi_server.</P>

<P>If an app quits without properly cleaning up, i.e. it does not Unregister() 
and Release() its endpoints, then the midi_server's roster contains a stale 
endpoint. As soon as the midi_server recognizes this (for example, when an 
application tries to connect that endpoint), it sends all BMidiRosters an mDEL 
message for this endpoint. (This message is sent whenever the midi_server feels 
like it, so libmidi2 can receive this message while it is still waiting for a 
reply to some other message.) If the stale endpoint is still on the roster and 
you (re)start your app, then you receive an mOBJ message for this endpoint 
during the startup handshake. A little later you will receive the mDEL.</P>

<HR SIZE="1">

<H2>BMidiEndpoint::Release()</H2>

<P>Only sends a message if the refcount of local objects (published or not) 
becomes 0:</P>

<PRE><SMALL>
OUT BMessage: what = Mdel (0x4d64656c, or 1298425196)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
</SMALL></PRE>

<P>The corresponding reply:</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>If you did not Unregister() a published endpoint before you Release()'d it, 
no 'Munr' message is sent. Of course, the midi_server is smart enough to 
realize that this endpoint should be wiped from the roster now. Likewise, if 
this endpoint is connected to another endpoint, Release() will not send a 
separate 'Mdis' message, but the server <I>will</I> disconnect them. (This, of 
course, only happens when you Release() local objects. Releasing a proxy has no 
impact on the connection with the real endpoint.)</P>

<P>When you Release() a proxy (a remote endpoint) and its refcount becomes 0, 
libmidi2 does not send an 'Mdel' message to the server. After all, the object 
is not deleted, just your proxy. If the remote endpoint still exists (i.e. 
IsValid() returns true), the BMidiRoster actually keeps a cached copy of the 
proxy object around, just in case you need it again. This means you can do 
this: endp = NextEndpoint(); endp->Release(); (now refcount is 0) endp- 
>Acquire(); (now refcount is 1 again). But I advice against that since it 
doesn't work for all objects; local and dead remote endpoints <I>will</I> be 
deleted when their refcount reaches zero.</P>

<P>In Be's implementation, if you Release() a local endpoint that already has a 
zero refcount, libmidi still sends out the 'Mdel' message. It also drops you 
into the debugger. (I think it should return an error code instead, it already 
has a status_t.) However, if you Release() proxies a few times too many, your 
app does not jump into the debugger. (Again, I think the return result should 
be an error code here -- for OpenBeOS R1 I think we should jump into the 
debugger just like with local objects). Hmm, actually, whether you end up in 
the debugger depends on the contents of memory after the object is deleted, 
because you perform the extra Release() on a dead object. Don't do that.</P>

<HR SIZE="1">

<H2>BMidiEndpoint::SetName()</H2>

<P>For local endpoints, both unpublished and published, libmidi2 sends:</P>

<PRE><SMALL>
OUT BMessage: what = Mnam (0x4d6e616d, or 1299079533)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
    entry        be:name, type='CSTR', c=1, size=7, data[0]: "b name"
</SMALL></PRE>

<P>And receives:</P>

<PRE><SMALL>
IN BMessage: what =  (0x0, or 0)
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>You cannot rename remote endpoints. If you try, libmidi2 will simply ignore 
your request. It does not send a message to the midi_server.</P>

<P>If another application renames one of its own endpoints, all other 
BMidiRosters receive:</P>

<PRE><SMALL>
IN  BMessage: what = mREN (0x6d52454e, or 1834108238)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x5 (5, '')
    entry        be:name, type='CSTR', c=1, size=7, data[0]: "b name"
</SMALL></PRE>

<P>You receive this message even if the other app did not publish its endpoint. 
This seems rather strange, because your BMidiRoster has no knowledge of this 
particular endpoint yet, so what is it to do with this message? Ignore it, I 
guess.</P>

<HR SIZE="1">

<H2>BMidiEndpoint::GetProperties()</H2>

<P>For <I>any</I> kind of endpoint (local non-published, local published, 
remote) libmidi2 sends the following message to the server:</P>

<PRE><SMALL>
OUT BMessage: what = Mgpr (0x4d677072, or 1298624626)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x2b2 (690, '')
    entry       be:props, type='MSGG', c=1, size= 0,
</SMALL></PRE>

<P>(Why this "get properties" request includes a BMessage is a mistery to me. 
The midi_server does not appear to copy its contents into the reply, which 
would have made at least some sense. The BMessage from the client is completely 
overwritten with the endpoint's properties.)</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry       be:props, type='MSGG', c=1, size= 0,
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>This means that endpoint properties are stored in the server only, not 
inside the BMidiEndpoints, and not by the local BMidiRosters.</P>

<HR SIZE="1">

<H2>BMidiEndpoint::SetProperties()</H2>

<P>For local endpoints, published or not, libmidi2 sends the following message 
to the server:</P>

<PRE><SMALL>
OUT BMessage: what = Mspr (0x4d737072, or 1299411058)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
    entry       be:props, type='MSGG', c=1, size= 0,
</SMALL></PRE>

<P>And expects this back:</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>You cannot change the properties of remote endpoints. If you try, libmidi2 
will ignore your request. It does not send a message to the midi_server, and it 
returns the -1 error code (B_ERROR).</P>

<P>If another application changes the properties of one of its own endpoints, 
all other BMidiRosters receive:</P>

<PRE><SMALL>
IN  BMessage: what = mPRP (0x6d505250, or 1833980496)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
    entry  be:properties, type='MSGG', c=1, size= 0,
</SMALL></PRE>

<P>You receive this message even if the other app did not publish its 
endpoint.</P>

<HR SIZE="1">

<H2>BMidiLocalConsumer::SetLatency()</H2>

<P>For local endpoints, published or not, libmidi2 sends the following message 
to the server:</P>

<PRE><SMALL>
OUT BMessage: what = Mlat (0x4d6c6174, or 1298948468)
    entry     be:latency, type='LLNG', c=1, size= 8, data[0]: 0x3e8 (1000, '')
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x14f (335, '')
</SMALL></PRE>

<P>And receives:</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>If another application changes the latency of one of its own consumers, all 
other BMidiRosters receive:</P>

<PRE><SMALL>
IN  BMessage: what = mLAT (0x6d4c4154, or 1833714004)
    entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x15 (21, '')
    entry     be:latency, type='LLNG', c=1, size= 8, data[0]: 0x3e8 (1000, '')
</SMALL></PRE>

<P>You receive this message even if the other app did not publish its 
endpoint.</P>

<HR SIZE="1">

<H2>BMidiProducer::Connect()</H2>

<P>The message:</P>

<PRE><SMALL>
OUT BMessage: what = Mcon (0x4d636f6e, or 1298362222)
    entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
    entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x376 (886, '')
</SMALL></PRE>

<P>The answer:</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>The server sends back a B_ERROR result if you specify wrong ID's. When you 
try to connect a producer and consumer that are already connected to each 
other, libmidi2 still sends the 'Mcon' message to the server (even though it 
could have known these endpoints are already connected). In that case, the 
server responds with a B_ERROR code as well.</P>

<P>When another app makes the connection, your BMidiRoster receives:</P>
                                                          
<PRE><SMALL>
IN  BMessage: what = mCON (0x6d434f4e, or 1833127758)
    entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
    entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
</SMALL></PRE>

<P>Note: your BMidiRoster receives this notification even if the producer or 
the consumer (or both) are not registered endpoints.</P>

<HR SIZE="1">

<H2>BMidiProducer::Disconnect()</H2>

<P>The message:</P>

<PRE><SMALL>
OUT BMessage: what = Mdis (0x4d646973, or 1298426227)
    entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x309 (777, '')
    entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x393 (915, '')
</SMALL></PRE>

<P>The answer:</P>

<PRE><SMALL>
IN  BMessage: what =  (0x0, or 0)
    entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
    entry     _previous_, ...
</SMALL></PRE>

<P>The server sends back a B_ERROR result if you specify wrong ID's. When you 
try to disconnect a producer and consumer that are not connected to each other, 
libmidi2 still sends the 'Mdis' message to the server (even though it could 
have known these endpoints are not connected). In that case, the server 
responds with a B_ERROR code as well.</P>

<P>When another app breaks the connection, your BMidiRoster receives:</P>

<PRE><SMALL>
IN  BMessage: what = mDIS (0x6d444953, or 1833191763)
    entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
    entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
</SMALL></PRE>

<P>Note: your BMidiRoster receives this notification even if the producer or 
the consumer (or both) are not registered endpoints.</P>

<HR SIZE="1">

<H2>Watchin'</H2>

<P>BMidiRoster::StartWatching() and StopWatching() do not send messages to the 
midi_server. This means that the BMidiRoster itself, and not the midi_server, 
sends the notifications to the messenger. It does this whenever it receives a 
message from the midi_server.</P>

<P>The relationship between midi_server messages and B_MIDI_EVENT notifications 
is as follows:</P>

<BLOCKQUOTE>
<TABLE BORDER="1">
<TR><TH>message</TH><TH>notification</TH></TR>
<TR><TD>mOBJ</TD><TD>B_MIDI_REGISTERED</TD></TR>
<TR><TD>mDEL</TD><TD> B_MIDI_UNREGISTERED </TD></TR>
<TR><TD>mCON</TD><TD>B_MIDI_CONNECTED</TD></TR>
<TR><TD>mDIS</TD><TD>B_MIDI_DISCONNECTED</TD></TR>
<TR><TD>mREN</TD><TD>B_MIDI_CHANGED_NAME</TD></TR>
<TR><TD>mLAT</TD><TD>B_MIDI_CHANGED_LATENCY</TD></TR>
<TR><TD>mPRP</TD><TD>B_MIDI_CHANGED_PROPERTIES</TD></TR>
</TABLE>
</BLOCKQUOTE>

<P>For each message on the left, the watcher will receive the corresponding 
notification on the right.</P>

<HR SIZE="1">

<H2>Other observations</H2>

<P>Operations that do not send messages to the midi_server:</P>

<UL>

<LI><P>BMidiEndpoint::Acquire(). This means reference counting is done locally 
by BMidiRoster. Release() doesn't send a message either, unless the refcount 
becomes 0 and the object is deleted. (Which suggests that it is actually the 
destructor and not Release() that sends the message.)</P></LI>

<LI><P>BMidiRoster::NextEndpoint(), NextProducer(), NextConsumer(), 
FindEndpoint(), FindProducer(), FindConsumer(). None of these functions send 
messages to the midi_server. This means that each BMidiRoster instance keeps 
its own list of available endpoints. This is why it receives 'mOBJ' messages 
during the startup handshake, and whenever a new remote endpoint is registered, 
and 'mDEL' messages for every endpoint that disappears. Even though the 
NextXXX() functions do not return locally created objects, this "local roster" 
<I>does</I> keep track of them, since FindXXX() <I>do</I> return local 
endpoints.</P></LI>

<LI><P>BMidiEndpoint::Name(), ID(), IsProducer(), IsConsumer(), IsRemote(), 
IsLocal() IsPersistent(). BMidiConsumer::Latency(). 
BMidiLocalConsumer::GetProducerID(), SetTimeout(). These all appear to consult 
BMidiRoster's local roster.</P></LI>

<LI><P>BMidiEndpoint::IsValid(). This function simply looks at BMidiRoster's 
local roster to see whether the remote endpoint is still visible, i.e. not 
unregistered. It does not determine whether the endpoint's application is still 
alive, or "ping" the endpoint or anything fancy like that.</P></LI>

<LI><P>BMidiProducer::IsConnected(), Connections(). This means that 
BMidiRoster's local roster, or maybe the BMidiProducers themselves (including 
the proxies) keep track of the various connections.</P></LI>

<LI><P>BMidiLocalProducer::Connected(), Disconnected(). These methods are 
invoked when any app (including your own) makes or breaks a connection on one 
of your local producers. These hooks are invoked before the B_MIDI_EVENT 
messages are sent to any watchers.</P></LI>

<LI><P>Quitting your app. Even though the BMidiRoster instance is deleted when 
the app quits, it does not let the midi_server know that the application in 
question is now gone. Any endpoints you have registered are not automatically 
unregistered. This means that the midi_server is left with some stale 
information. Undoubtedly, there is a mechanism in place to clean this up. The 
same mechanism would be used to clean up apps that did not exit cleanly, or 
that crashed.</P></LI>

</UL>

<P>Other stuff:</P>

<UL>

<LI><P>libmidi2.so exports an int32 symbol called "midi_debug_level". If you 
set it to a non-zero value, libmidi2 will dump a lot of interesting debug info 
on stdout. To do this, declare the variable  in your app with "extern int32 
midi_debug_level;", and then set it to some high value later: "midi_debug_level 
= 0x7FFFFFFF;" Now run your app from a Terminal and watch libmidi2 do its 
thing.</P></LI>

<LI><P>libmidi2.so also exports an int32 symbol called 
"midi_dispatcher_priority". This is the runtime priority of the thread that 
fields MIDI events to consumers.</P></LI>

</UL>

</BODY>
</HTML>