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