1The BeOS R5 Midi Kit protocol
2=============================
3
4In the course of writing the OpenBeOS Midi Kit, I spent some time
5looking at how BeOS R5's libmidi2.so and midi_server communicate. Not
6out of a compulsion to clone this protocol, but to learn from it. After
7all, the Be engineers spent a lot of time thinking about this already,
8and it would be foolish not to build on their experience. Here is what I
9have found out.
10
11Two kinds of communication happen: administrative tasks and MIDI events.
12The housekeeping stuff is done by sending BMessages between the
13BMidiRoster and the midi_server. MIDI events are sent between producers
14and consumers using ports, without intervention from the server.
15
16This document describes the BMessage protocol. The protocol appears to
17be asynchronous, which means that when BMidiRoster sends a message to
18the midi_server, it does not wait around for a reply, even though the
19midi_server replies to all messages. The libmidi2 functions *do* block
20until the reply is received, though, so client code does not have to
21worry about any of this.
22
23Both BMidiRoster and the midi_server can initiate messages. BMidiRoster
24typically sends a message when client code calls one of the functions
25from a libmidi2 class. When the midi_server sends messages, it is to
26keep BMidiRoster up-to-date about changes in the roster. BMidiRoster
27never replies to messages from the server. The encoding of the BMessage
28'what' codes indicates their direction. The 'Mxxx' messages are sent
29from libmidi2 to the midi_server. The 'mXXX' messages go the other way
30around: from the server to a client.
31
32--------------
33
34Who does what?
35--------------
36
37The players here are the midi_server, which is a normal BApplication,
38and all the client apps, also BApplications. The client apps have loaded
39a copy of libmidi2 into their own address space. The main class from
40libmidi2 is BMidiRoster. The BMidiRoster has a BLooper that communicates
41with the midi_server's BLooper.
42
43The midi_server keeps a list of *all* endpoints in the system, even
44local, nonpublished, ones. Each BMidiRoster instance keeps its own list
45of remote published endpoints, and all endpoints local to this
46application. It does not know about remote endpoints that are not
47published yet.
48
49Whenever you make a change to one of your own endpoints, your
50BMidiRoster notifies the midi_server. If your endpoint is published, the
51midi_server then notifies all of the other BMidiRosters, so they can
52update their local rosters. It does *not* notify your own app!
53(Sometimes, however, the midi_server also notifies everyone else even if
54your local endpoint is *not* published. The reason for this escapes me,
55because the other BMidiRosters have no access to those endpoints
56anyway.)
57
58By the way, "notification" here means the internal communications
59between server and libmidi, not the B_MIDI_EVENT messages you receive
60when you call BMidiRoster::StartWatching().
61
62--------------
63
64BMidiRoster::MidiRoster()
65-------------------------
66
67The first time it is called, this function creates the one-and-only
68instance of BMidiRoster. Even if you don't explicitly call it yourself,
69it is used behind-the-scenes anyway by any of the other BMidiRoster
70functions. MidiRoster() constructs a BLooper and gets it running. Then
71it sends a BMessenger with the looper's address to the midi_server:
72
73::
74
75   OUT BMessage: what = Mapp (0x4d617070, or 1298231408)
76       entry       be:msngr, type='MSNG', c=1, size=24,         
77
78The server now responds with mOBJ messages for all *remote* *published*
79producers and consumers. (Obviously, this list only contains remote
80objects because by now you can't have created any local endpoints yet.)
81
82For a consumer this message looks like:
83
84::
85
86   IN  BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
87       entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x1 (1, '')
88       entry     be:latency, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
89       entry        be:port, type='LONG', c=1, size= 4, data[0]: 0x1dab (7595, '')
90       entry        be:name, type='CSTR', c=1, size=16, data[0]: "/dev/midi/vbus0"
91
92(Oddness: why is be:latency a LONG and not a LLNG? Since latency is
93expressed in microseconds using a 64-bit bigtime_t, you'd expect the
94midi_server to send all 64 of those bits... In the 'Mnew' message, on
95the other hand, be:latency *is* a LLGN.)
96
97And for a producer:
98
99::
100
101   IN  BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
102       entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x2 (2, '')
103       entry        be:name, type='CSTR', c=1, size=16, data[0]: "/dev/midi/vbus0"
104
105Note that the be:name field is not present if the endpoint has no name.
106That is, if the endpoint was constructed by passing a NULL name into the
107BMidiLocalConsumer() or BMidiLocalProducer() constructor.
108
109Next up are notifications for *all* connections, even those between
110endpoints that are not registered:
111
112::
113
114   IN  BMessage: what = mCON (0x6d434f4e, or 1833127758)
115       entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
116       entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
117
118These messages are followed by an Msyn message:
119
120::
121
122   IN  BMessage: what = Msyn (0x4d73796e, or 1299413358)
123
124And finally the (asynchronous) reply:
125
126::
127
128   IN  BMessage: what =  (0x0, or 0)
129       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
130       entry     _previous_, ...
131
132Only after this reply is received, MidiRoster() returns.
133
134The purpose of the Msyn message is not entirely clear. (Without it, Be's
135libmidi2 blocks in the MidiRoster() call.) Does it signify the end of
136the list of endpoints? Why doesn't libmidi2 simply wait for the final
137reply?
138
139--------------
140
141BMidiLocalProducer constructor
142------------------------------
143
144BMidiRoster, on behalf of the constructor, sends the following to the
145midi_server:
146
147::
148
149   OUT BMessage: what = Mnew (0x4d6e6577, or 1299080567)
150       entry        be:type, type='CSTR', c=1, size=9, data[0]: "producer"
151       entry        be:name, type='CSTR', c=1, size=21, data[0]: "MIDI Keyboard output"
152
153The be:name field is optional.
154
155The reply includes the ID for the new endpoint. This means that the
156midi_server assigns the IDs, and any endpoint gets an ID whether it is
157published or not.
158
159::
160
161   IN  BMessage: what =  (0x0, or 0)
162       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x11 (17, '')
163       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
164       entry     _previous_, ...
165
166Unlike many other Be API classes, BMidiLocalProducer and
167BMidiLocalConsumer don't have an InitCheck() method. But under certain
168odd circumstances (such as the midi_server not running), creating the
169endpoint might fail. How does client code check for that? Well, it turns
170out that upon failure, the endpoint is assigned ID 0, so you can check
171for that. In that case, the endpoint's refcount is 0 and you should not
172Release() it. (That is stupid, actually, because Release() is the only
173way that you can destroy the object. Our implementation should bump the
174endpoint to 1 even on failure!)
175
176If another app creates a new endpoint, your BMidiRoster is not notified.
177The remote endpoint is not published yet, so your app is not supposed to
178see it.
179
180--------------
181
182BMidiLocalConsumer constructor
183------------------------------
184
185This is similar to the BMidiLocalProducer constructor, although the
186contents of the message differ slightly. Again, be:name is optional.
187
188::
189
190   OUT BMessage: what = Mnew (0x4d6e6577, or 1299080567)
191       entry        be:type, type='CSTR', c=1, size=9, data[0]: "consumer"
192       entry     be:latency, type='LLNG', c=1, size= 8, data[0]: 0x0 (0, '')
193       entry        be:port, type='LONG', c=1, size= 4, data[0]: 0x4c0 (1216, '')
194       entry        be:name, type='CSTR', c=1, size=13, data[0]: "InternalMIDI"  
195
196And the reply:
197
198::
199
200   IN  BMessage: what =  (0x0, or 0)
201       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x11 (17, '')
202       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
203       entry     _previous_, ...
204
205Before it sends the message to the server, the constructor creates a new
206port with the name "MidiEventPort" and a queue length (capacity) of 1.
207
208--------------
209
210BMidiEndpoint::Register()
211BMidiRoster::Register()
212-------------------------
213
214Sends the same message for producers and consumers:
215
216::
217
218   OUT BMessage: what = Mreg (0x4d726567, or 1299342695)
219       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')     
220
221The reply:
222
223::
224
225   IN  BMessage: what =  (0x0, or 0)
226       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
227       entry     _previous_, ...
228
229If you try to Register() an endpoint that is already registered,
230libmidi2 still sends the message. (Which could mean that BMidiRoster
231does not keep track of this registered state.) The midi_server simply
232ignores that request, and sends back error code 0 (B_OK). So the API
233does not flag this as an error.
234
235If you send an invalid be:id, the midi_server returns error code -1
236(General OS Error, B_ERROR). If you try to Register() a remote endpoint,
237libmidi2 immediately returns error code -1, and does not send a message
238to the server.
239
240If another app Register()'s a producer, your BMidiRoster receives:
241
242::
243
244   IN  BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
245       entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x17 (23, '')
246       entry        be:name, type='CSTR', c=1, size=7, data[0]: "a name"
247
248If the other app registers a consumer, your BMidiRoster receives:
249
250::
251
252   IN  BMessage: what = mOBJ (0x6d4f424a, or 1833910858)
253       entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x19 (25, '')
254       entry     be:latency, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
255       entry        be:port, type='LONG', c=1, size= 4, data[0]: 0xde9 (3561, '')
256       entry        be:name, type='CSTR', c=1, size=7, data[0]: "a name"
257
258These are the same messages you get when your BMidiRoster instance is
259constructed. In both messages, the be:name field is optional again.
260
261If the other app Register()'s the endpoint more than once, you still get
262only one notification. So the midi_server simply ignores that second
263publish request.
264
265--------------
266
267BMidiEndpoint::Unregister()
268BMidiRoster::Unregister()
269---------------------------
270
271Sends the same message for producers and consumers:
272
273::
274
275   OUT BMessage: what = Munr (0x4d756e72, or 1299541618)
276       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')       
277
278The reply:
279
280::
281
282   IN  BMessage: what =  (0x0, or 0)
283       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
284       entry     _previous_, ...
285
286If you try to Unregister() and endpoint that is already unregistered,
287libmidi2 still sends the message. The midi_server simply ignores that
288request, and sends back error code 0 (B_OK). So the API does not flag
289this as an error. If you try to Unregister() a remote endpoint, libmidi2
290immediately returns error code -1, and does not send a message to the
291server.
292
293When another app Unregister()'s one of its own endpoints, your
294BMidiRoster receives:
295
296::
297
298   IN  BMessage: what = mDEL (0x6d44454c, or 1833190732)
299       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17 (23, '')             
300
301When the other app deletes that endpoint (refcount is now 0) and it is
302not unregistered yet, your BMidiRoster also receives that mDEL message.
303Multiple Unregisters() are ignored again by the midi_server.
304
305If an app quits without properly cleaning up, i.e. it does not
306Unregister() and Release() its endpoints, then the midi_server's roster
307contains a stale endpoint. As soon as the midi_server recognizes this
308(for example, when an application tries to connect that endpoint), it
309sends all BMidiRosters an mDEL message for this endpoint. (This message
310is sent whenever the midi_server feels like it, so libmidi2 can receive
311this message while it is still waiting for a reply to some other
312message.) If the stale endpoint is still on the roster and you (re)start
313your app, then you receive an mOBJ message for this endpoint during the
314startup handshake. A little later you will receive the mDEL.
315
316--------------
317
318BMidiEndpoint::Release()
319------------------------
320
321Only sends a message if the refcount of local objects (published or not)
322becomes 0:
323
324::
325
326   OUT BMessage: what = Mdel (0x4d64656c, or 1298425196)
327       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
328
329The corresponding reply:
330
331::
332
333   IN  BMessage: what =  (0x0, or 0)
334       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
335       entry     _previous_, ...
336
337If you did not Unregister() a published endpoint before you Release()'d
338it, no 'Munr' message is sent. Of course, the midi_server is smart
339enough to realize that this endpoint should be wiped from the roster
340now. Likewise, if this endpoint is connected to another endpoint,
341Release() will not send a separate 'Mdis' message, but the server *will*
342disconnect them. (This, of course, only happens when you Release() local
343objects. Releasing a proxy has no impact on the connection with the real
344endpoint.)
345
346When you Release() a proxy (a remote endpoint) and its refcount becomes
3470, libmidi2 does not send an 'Mdel' message to the server. After all,
348the object is not deleted, just your proxy. If the remote endpoint still
349exists (i.e. IsValid() returns true), the BMidiRoster actually keeps a
350cached copy of the proxy object around, just in case you need it again.
351This means you can do this: endp = NextEndpoint(); endp->Release(); (now
352refcount is 0) endp- >Acquire(); (now refcount is 1 again). But I advice
353against that since it doesn't work for all objects; local and dead
354remote endpoints *will* be deleted when their refcount reaches zero.
355
356In Be's implementation, if you Release() a local endpoint that already
357has a zero refcount, libmidi still sends out the 'Mdel' message. It also
358drops you into the debugger. (I think it should return an error code
359instead, it already has a status_t.) However, if you Release() proxies a
360few times too many, your app does not jump into the debugger. (Again, I
361think the return result should be an error code here -- for OpenBeOS R1
362I think we should jump into the debugger just like with local objects).
363Hmm, actually, whether you end up in the debugger depends on the
364contents of memory after the object is deleted, because you perform the
365extra Release() on a dead object. Don't do that.
366
367--------------
368
369BMidiEndpoint::SetName()
370------------------------
371
372For local endpoints, both unpublished and published, libmidi2 sends:
373
374::
375
376   OUT BMessage: what = Mnam (0x4d6e616d, or 1299079533)
377       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
378       entry        be:name, type='CSTR', c=1, size=7, data[0]: "b name"
379
380And receives:
381
382::
383
384   IN BMessage: what =  (0x0, or 0)
385       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
386       entry     _previous_, ...
387
388You cannot rename remote endpoints. If you try, libmidi2 will simply
389ignore your request. It does not send a message to the midi_server.
390
391If another application renames one of its own endpoints, all other
392BMidiRosters receive:
393
394::
395
396   IN  BMessage: what = mREN (0x6d52454e, or 1834108238)
397       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x5 (5, '')
398       entry        be:name, type='CSTR', c=1, size=7, data[0]: "b name"
399
400You receive this message even if the other app did not publish its
401endpoint. This seems rather strange, because your BMidiRoster has no
402knowledge of this particular endpoint yet, so what is it to do with this
403message? Ignore it, I guess.
404
405--------------
406
407BMidiEndpoint::GetProperties()
408------------------------------
409
410For *any* kind of endpoint (local non-published, local published,
411remote) libmidi2 sends the following message to the server:
412
413::
414
415   OUT BMessage: what = Mgpr (0x4d677072, or 1298624626)
416       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x2b2 (690, '')
417       entry       be:props, type='MSGG', c=1, size= 0,
418
419(Why this "get properties" request includes a BMessage is a mistery to
420me. The midi_server does not appear to copy its contents into the reply,
421which would have made at least some sense. The BMessage from the client
422is completely overwritten with the endpoint's properties.)
423
424::
425
426   IN  BMessage: what =  (0x0, or 0)
427       entry       be:props, type='MSGG', c=1, size= 0,
428       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
429       entry     _previous_, ...
430
431This means that endpoint properties are stored in the server only, not
432inside the BMidiEndpoints, and not by the local BMidiRosters.
433
434--------------
435
436BMidiEndpoint::SetProperties()
437------------------------------
438
439For local endpoints, published or not, libmidi2 sends the following
440message to the server:
441
442::
443
444   OUT BMessage: what = Mspr (0x4d737072, or 1299411058)
445       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
446       entry       be:props, type='MSGG', c=1, size= 0,
447
448And expects this back:
449
450::
451
452   IN  BMessage: what =  (0x0, or 0)
453       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
454       entry     _previous_, ...
455
456You cannot change the properties of remote endpoints. If you try,
457libmidi2 will ignore your request. It does not send a message to the
458midi_server, and it returns the -1 error code (B_ERROR).
459
460If another application changes the properties of one of its own
461endpoints, all other BMidiRosters receive:
462
463::
464
465   IN  BMessage: what = mPRP (0x6d505250, or 1833980496)
466       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
467       entry  be:properties, type='MSGG', c=1, size= 0,
468
469You receive this message even if the other app did not publish its
470endpoint.
471
472--------------
473
474BMidiLocalConsumer::SetLatency()
475--------------------------------
476
477For local endpoints, published or not, libmidi2 sends the following
478message to the server:
479
480::
481
482   OUT BMessage: what = Mlat (0x4d6c6174, or 1298948468)
483       entry     be:latency, type='LLNG', c=1, size= 8, data[0]: 0x3e8 (1000, '')
484       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x14f (335, '')
485
486And receives:
487
488::
489
490   IN  BMessage: what =  (0x0, or 0)
491       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
492       entry     _previous_, ...
493
494If another application changes the latency of one of its own consumers,
495all other BMidiRosters receive:
496
497::
498
499   IN  BMessage: what = mLAT (0x6d4c4154, or 1833714004)
500       entry          be:id, type='LONG', c=1, size= 4, data[0]: 0x15 (21, '')
501       entry     be:latency, type='LLNG', c=1, size= 8, data[0]: 0x3e8 (1000, '')
502
503You receive this message even if the other app did not publish its
504endpoint.
505
506--------------
507
508BMidiProducer::Connect()
509------------------------
510
511The message:
512
513::
514
515   OUT BMessage: what = Mcon (0x4d636f6e, or 1298362222)
516       entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x17f (383, '')
517       entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x376 (886, '')
518
519The answer:
520
521::
522
523   IN  BMessage: what =  (0x0, or 0)
524       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
525       entry     _previous_, ...
526
527The server sends back a B_ERROR result if you specify wrong ID's. When
528you try to connect a producer and consumer that are already connected to
529each other, libmidi2 still sends the 'Mcon' message to the server (even
530though it could have known these endpoints are already connected). In
531that case, the server responds with a B_ERROR code as well.
532
533When another app makes the connection, your BMidiRoster receives:
534
535::
536
537   IN  BMessage: what = mCON (0x6d434f4e, or 1833127758)
538       entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
539       entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
540
541Note: your BMidiRoster receives this notification even if the producer
542or the consumer (or both) are not registered endpoints.
543
544--------------
545
546BMidiProducer::Disconnect()
547---------------------------
548
549The message:
550
551::
552
553   OUT BMessage: what = Mdis (0x4d646973, or 1298426227)
554       entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x309 (777, '')
555       entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x393 (915, '')
556
557The answer:
558
559::
560
561   IN  BMessage: what =  (0x0, or 0)
562       entry      be:result, type='LONG', c=1, size= 4, data[0]: 0x0 (0, '')
563       entry     _previous_, ...
564
565The server sends back a B_ERROR result if you specify wrong ID's. When
566you try to disconnect a producer and consumer that are not connected to
567each other, libmidi2 still sends the 'Mdis' message to the server (even
568though it could have known these endpoints are not connected). In that
569case, the server responds with a B_ERROR code as well.
570
571When another app breaks the connection, your BMidiRoster receives:
572
573::
574
575   IN  BMessage: what = mDIS (0x6d444953, or 1833191763)
576       entry    be:producer, type='LONG', c=1, size= 4, data[0]: 0x13 (19, '')
577       entry    be:consumer, type='LONG', c=1, size= 4, data[0]: 0x14 (20, '')
578
579Note: your BMidiRoster receives this notification even if the producer
580or the consumer (or both) are not registered endpoints.
581
582--------------
583
584Watchin'
585--------
586
587BMidiRoster::StartWatching() and StopWatching() do not send messages to
588the midi_server. This means that the BMidiRoster itself, and not the
589midi_server, sends the notifications to the messenger. It does this
590whenever it receives a message from the midi_server.
591
592The relationship between midi_server messages and B_MIDI_EVENT
593notifications is as follows:
594
595   +---------+---------------------------+
596   | message | notification              |
597   +=========+===========================+
598   | mOBJ    | B_MIDI_REGISTERED         |
599   +---------+---------------------------+
600   | mDEL    | B_MIDI_UNREGISTERED       |
601   +---------+---------------------------+
602   | mCON    | B_MIDI_CONNECTED          |
603   +---------+---------------------------+
604   | mDIS    | B_MIDI_DISCONNECTED       |
605   +---------+---------------------------+
606   | mREN    | B_MIDI_CHANGED_NAME       |
607   +---------+---------------------------+
608   | mLAT    | B_MIDI_CHANGED_LATENCY    |
609   +---------+---------------------------+
610   | mPRP    | B_MIDI_CHANGED_PROPERTIES |
611   +---------+---------------------------+
612
613For each message on the left, the watcher will receive the corresponding
614notification on the right.
615
616--------------
617
618Other observations
619------------------
620
621Operations that do not send messages to the midi_server:
622
623-  BMidiEndpoint::Acquire(). This means reference counting is done
624   locally by BMidiRoster. Release() doesn't send a message either,
625   unless the refcount becomes 0 and the object is deleted. (Which
626   suggests that it is actually the destructor and not Release() that
627   sends the message.)
628
629-  BMidiRoster::NextEndpoint(), NextProducer(), NextConsumer(),
630   FindEndpoint(), FindProducer(), FindConsumer(). None of these
631   functions send messages to the midi_server. This means that each
632   BMidiRoster instance keeps its own list of available endpoints. This
633   is why it receives 'mOBJ' messages during the startup handshake, and
634   whenever a new remote endpoint is registered, and 'mDEL' messages for
635   every endpoint that disappears. Even though the NextXXX() functions
636   do not return locally created objects, this "local roster" *does*
637   keep track of them, since FindXXX() *do* return local endpoints.
638
639-  BMidiEndpoint::Name(), ID(), IsProducer(), IsConsumer(), IsRemote(),
640   IsLocal() IsPersistent(). BMidiConsumer::Latency().
641   BMidiLocalConsumer::GetProducerID(), SetTimeout(). These all appear
642   to consult BMidiRoster's local roster.
643
644-  BMidiEndpoint::IsValid(). This function simply looks at BMidiRoster's
645   local roster to see whether the remote endpoint is still visible,
646   i.e. not unregistered. It does not determine whether the endpoint's
647   application is still alive, or "ping" the endpoint or anything fancy
648   like that.
649
650-  BMidiProducer::IsConnected(), Connections(). This means that
651   BMidiRoster's local roster, or maybe the BMidiProducers themselves
652   (including the proxies) keep track of the various connections.
653
654-  BMidiLocalProducer::Connected(), Disconnected(). These methods are
655   invoked when any app (including your own) makes or breaks a
656   connection on one of your local producers. These hooks are invoked
657   before the B_MIDI_EVENT messages are sent to any watchers.
658
659-  Quitting your app. Even though the BMidiRoster instance is deleted
660   when the app quits, it does not let the midi_server know that the
661   application in question is now gone. Any endpoints you have
662   registered are not automatically unregistered. This means that the
663   midi_server is left with some stale information. Undoubtedly, there
664   is a mechanism in place to clean this up. The same mechanism would be
665   used to clean up apps that did not exit cleanly, or that crashed.
666
667Other stuff:
668
669-  libmidi2.so exports an int32 symbol called "midi_debug_level". If you
670   set it to a non-zero value, libmidi2 will dump a lot of interesting
671   debug info on stdout. To do this, declare the variable in your app
672   with "extern int32 midi_debug_level;", and then set it to some high
673   value later: "midi_debug_level = 0x7FFFFFFF;" Now run your app from a
674   Terminal and watch libmidi2 do its thing.
675
676-  libmidi2.so also exports an int32 symbol called
677   "midi_dispatcher_priority". This is the runtime priority of the
678   thread that fields MIDI events to consumers.
679