1Midi Kit design
2===============
3
4The Midi Kit consists of the midi_server and two shared libraries,
5libmidi2.so and libmidi.so. The latter is the "old" pre-R5 Midi Kit and
6has been re-implemented using the facilities from libmidi2, which makes
7it fully compatible with the new kit. This document describes the design
8and implementation of the OpenBeOS midi_server and libmidi2.so.
9
10The midi_server has two jobs: it keeps track of the endpoints that the
11client apps have created, and it publishes endpoints for the devices
12from /dev/midi. (This last task could have been done by any other app,
13but it was just as convenient to make the midi_server do that.) The
14libmidi2.so library also has two jobs: it assists the midi_server with
15the housekeeping stuff, and it allows endpoints to send and receive MIDI
16events. (That's right, the midi_server has nothing to do with the actual
17MIDI data.)
18
19--------------
20
21Ooh, pictures
22-------------
23
24The following image shows the center of Midi Kit activity, the
25midi_server, and its data structures:
26
27   |image0|
28
29And here is the picture for libmidi2.so:
30
31   |image1|
32
33Note that these diagrams give only a conceptual overview of who is
34responsible for which bits of data. The actual implementation details of
35the kit may differ.
36
37--------------
38
39Housekeeping
40------------
41
42-  The design for our implementation of the midi2 "housekeeping"
43   protocol roughly follows `what Be did <oldprotocol.html>`__, although
44   there are some differences. In Be's implementation, the BMidiRosters
45   only have BMidiEndpoints for remote endpoints if they are registered.
46   In our implementation, the BMidiRosters have BMidiEndpoint objects
47   for *all* endpoints, including remote endpoints that aren't published
48   at all. If there are many unpublished endpoints in the system, our
49   approach is less optimal. However, it made the implementation of the
50   Midi Kit much easier ;-)
51
52-  Be's libmidi2.so exports the symbols "midi_debug_level" and
53   "midi_dispatcher_priority", both int32's. Our libmidi2 does not use
54   either of these. But even though these symbols are not present in the
55   headers, some apps may use them nonetheless. That's why our libmidi2
56   exports those symbols as well.
57
58-  The name of the message fields in Be's implementation of the protocol
59   had the "be:" prefix. Our fields have a "midi:" prefix instead.
60   Except for the fields in the B_MIDI_EVENT notification messages,
61   because that would break compatibility with existing apps.
62
63Initialization
64~~~~~~~~~~~~~~
65
66-  The first time an app uses a midi2 class, the
67   BMidiRoster::MidiRoster() method sends an 'Mapp' message to the
68   midi_server, and blocks (on a semaphore). This message includes a
69   messenger to the app's BMidiRosterLooper object. The server adds the
70   app to its list of registered apps. Then the server asynchronously
71   sends back a series of 'mNEW' message notifications for all endpoints
72   on the roster, and 'mCON' messages for all existing connections. The
73   BMidiRosterLooper creates BMidiEndpoint objects for these endpoints
74   and adds them to its local roster; if the app is watching, it also
75   sends out corresponding B_MIDI_EVENT notifications. Finally, the
76   midi_server sends an 'mAPP' message to notify the app that it has
77   been successfully registered. Upon receipt, BMidiRoster::MidiRoster()
78   unblocks and returns control to the client code. This handshake is
79   the only asynchronous message exchange; all the other requests have a
80   synchronous reply.
81
82-  If the server detects an error during any of this (incorrect message
83   format, delivery failure, etc.) it simply ignores the request and
84   does not try to send anything back to the client (which is most
85   likely impossible anyway). If the app detects an error (server sends
86   back meaningless info, cannot connect to server), it pretends that
87   everything is hunkey dorey. (The API has no way of letting the client
88   know that the initialization succeeded.) Next time the app tries
89   something, the server either still does not respond, or it ignores
90   the request (because this app isn't properly registered). However, if
91   the app does not receive the 'mAPP' message, it will not unblock, and
92   remains frozen for all eternity.
93
94-  BMidiRoster's MidiRoster() method creates the one and only
95   BMidiRoster instance on the heap the first time it is called. This
96   instance is automatically destroyed when the app quits.
97
98Error handling
99~~~~~~~~~~~~~~
100
101-  If some error occurs, then the reply message is only guaranteed to
102   contain the "midi:result" field with some non- zero error code.
103   libmidi2 can only assume that the reply contains other data on
104   success (i.e. when "midi:result" is B_OK).
105
106-  The timeout for delivering and responding to a message is about 2
107   seconds. If the client receives no reply within that time, it assumes
108   the request failed. If the server cannot deliver a message within 2
109   seconds, it assumes the client is dead and removes it (and its
110   endpoints) from the roster. Of course, these assumptions may be
111   false. If the client wasn't dead and tries to send another request to
112   the server, then the server will now ignore it, since the client app
113   is no longer registered.
114
115-  Because we work with timeouts, we must be careful to avoid
116   misunderstandings between the midi_server and the client app. Both
117   sides must recognize the timeout, so they both can ignore the
118   operation. If, however, the server thinks that everything went okay,
119   but the client flags an error, then the server and the client will
120   have two different ideas of the current state of the roster. Of
121   course, those situations must be avoided.
122
123-  Although apps register themselves with the midi_server, there is no
124   corresponding "unregister" message. The only way the server
125   recognizes that an app and its endpoints are no longer available is
126   when it fails to deliver a message to that app. In that case, we
127   remove the app and all its endpoints from the roster. To do this, the
128   server sends "purge endpoint" messages to itself for all of the app's
129   endpoints. This means we don't immediately throw the app away, but we
130   schedule that for some time in the future. That makes the whole event
131   handling mechanism much cleaner. There is no reply to the purge
132   request. (Actually, we *do* immediately throw away the app_t object,
133   since that doesn't really interfere with anything.) (If there are
134   other events pending in the queue which also cause notifications,
135   then the server may send multiple purge messages for the same
136   endpoints. That's no biggie, because a purge message will be ignored
137   if its endpoint no longer exists.)
138
139-  As mentioned above, the midi_server ignores messages that do not come
140   from a registered app, although it does send back an error reply. In
141   the case of the "purge endpoint" message, the server makes sure the
142   message was local (i.e. sent by the midi_server itself).
143
144-  Note: BMessage's SendReply() apparently succeeds even if you kill the
145   app that the reply is intended for. This is rather strange, and it
146   means that you can't test delivery error handling for replies by
147   killing the app. (You *can* kill the app for testing the error
148   handling on notifications, however.)
149
150Creating and deleting endpoints
151~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
152
153-  When client code creates a new BMidiLocalProducer or
154   BMidiLocalConsumer endpoint, we send an 'Mnew' message to the server.
155   Unlike Be's implementation, the "name" field is always present, even
156   if the name is empty. After adding the endpoint to the roster, the
157   server sends 'mNEW' notifications to all other applications. Upon
158   receipt of this notification, the BMidiRosterLoopers of these apps
159   create a new BMidiEndpoint for the endpoint and add it to their
160   internal list of endpoints. The app that made the request receives a
161   reply with a single "midi:result" field.
162
163-  When you "new" an endpoint, its refcount is 1, even if the creation
164   failed. (For example, if the midi_server does not run.) When you
165   Acquire(), the refcount is bumped. When you Release(), it is
166   decremented. When refcount drops to 0, the endpoint object "deletes"
167   itself. (So client code should never use an endpoint after having
168   Release()'d it, because the object may have just been killed.) When
169   creation succeeds, IsValid() returns true and ID() returns a valid ID
170   (> 0). Upon failure, IsValid() is false and ID() returns 0.
171
172-  After the last Release() of a local endpoint, we send 'Mdel' to let
173   the midi_server know the endpoint is now deleted. We don't expect a
174   reply back. If something goes wrong, the endpoint is deleted
175   regardless. We do not send separate "unregistered" notifications,
176   because deleting an endpoint implies that it is removed from the
177   roster. For the same reason, we also don't send separate
178   "disconnected" notifications.
179
180-  The 'mDEL' notification triggers a BMidiRosterLooper to remove the
181   corresponding BMidiEndpoint from its internal list. This object is
182   always a proxy for a remote endpoint. The remote endpoint is gone,
183   but whether we can also delete the proxy depends on its reference
184   count. If no one is still using the object, its refcount is zero, and
185   we can safely delete the object. Otherwise, we must defer destruction
186   until the client Release()'s the object.
187
188-  If you "delete" an endpoint, your app drops into the debugger.
189
190-  If you Release() an endpoint too many times, your app *could* drop
191   into the debugger. It might also crash, because you are now using a
192   dead object. It depends on whether the memory that was previously
193   occupied by your endpoint object was overwritten in the mean time.
194
195-  You are allowed to pass NULL into the constructors of
196   BMidiLocalConsumer and BMidiLocalProducer, in which case the
197   endpoint's name is simply an empty string.
198
199Changing endpoint attributes
200~~~~~~~~~~~~~~~~~~~~~~~~~~~~
201
202-  An endpoint can be "invalid". In the case of a proxy this means that
203   the remote endpoint is unregistered or even deleted. Local endpoints
204   can only be invalid if something went wrong during their creation (no
205   connection to server, for example). You can get the attributes of
206   invalid objects, but you cannot set them. Any attempts to do so will
207   return an error code.
208
209-  For changing the name, latency, or properties of an endpoint,
210   libmidi2 sends an 'Mchg' message with the fields that should be
211   changed, "midi:name", "midi:latency", or "midi:properties".
212   Registering or unregistering an endpoint also sends such an 'Mchg'
213   message, because we consider the "registered" state also an
214   attribute, in "midi:registered". The message obviously also includes
215   the ID of the endpoint in question. Properties are sent using a
216   different message, because the properties are not stored inside the
217   BMidiEndpoints.
218
219-  After handling the 'Mchg' request, the midi_server broadcasts an
220   'mCHG' notification to all the other apps. This message has the same
221   contents as the original request.
222
223-  If the 'Mchg' message contains an invalid "midi:id" (i.e. no such
224   endpoint exists or it does not belong to the app that sent the
225   request), the midi_server returns an error code, and it does not
226   notify the other apps.
227
228-  If you try to Register() an endpoint that is already registered,
229   libmidi2 does not send a message to the midi_server but simply
230   returns B_OK. (Be's implementation *did* send a message, but our
231   libmidi2 also keeps track whether an endpoint is registered or not.)
232   Although registering an endpoint more than once doesn't make much
233   sense, it is not considered an error. Likewise for Unregister()ing an
234   endpoint that is not registered.
235
236-  If you try to Register() or Unregister() a remote endpoint, libmidi2
237   immediately returns an error code, and does not send a message to the
238   server. Likewise for a local endpoints that are invalid (i.e. whose
239   IsValid() function returns false).
240
241-  BMidiRoster::Register() and Unregister() do the same thing as
242   BMidiEndpoint::Register() and Unregister(). If you pass NULL into
243   these functions, they return B_BAD_VALUE.
244
245-  SetName() ignores NULL names. When you call it on a remote endpoint,
246   SetName() does nothing. SetName() does not send a message if the new
247   name is the same as the current name.
248
249-  SetLatency() ignores negative values. SetLatency() does not send a
250   message if the new latency is the same as the current latency. (Since
251   SetLatency() lives in BMidiLocalConsumer, you can never use it on
252   remote endpoints.)
253
254-  We store a copy of the endpoint properties in each BMidiEndpoint. The
255   properties of new endpoints are empty. GetProperties() copies this
256   BMessage into the client's BMessage. GetProperties() returns NULL if
257   the message parameter is NULL.
258
259-  SetProperties() returns NULL if the message parameter is NULL. It
260   returns an error code if the endpoint is remote or invalid.
261   SetProperties() does *not* compare the contents of the new BMessage
262   to the old, so it will always send out the change request.
263
264Connections
265~~~~~~~~~~~
266
267-  BMidiProducer::Connect() sends an 'Mcon' request to the midi_server.
268   This request contains the IDs of the producer and the consumer you
269   want to connect. The server sends back a reply with a result code. If
270   it is possible to make this connection, the server broadcasts an
271   'mCON' notification to all other apps. In one of these apps the
272   producer is local, so that app's libmidi2 calls the
273   BMidiLocalProducer::Connected() hook.
274
275-  You are not allowed to connect the same producer and consumer more
276   than once. The midi_server checks for this. It also returns an error
277   code if you try to disconnect two endpoints that were not connected.
278
279-  Disconnect() sends an 'Mdis' request to the server, which contains
280   the IDs of the producer and consumer that you want to disconnect. The
281   server replies with a result code. If the connection could be broken,
282   it also sends an 'mDIS' notification to the other apps. libmidi2
283   calls the local producer's BMidiLocalProducer::Disconnected() hook.
284
285-  Connect() and Disconnect() immediately return an error code if you
286   pass a NULL argument, or if the producer or consumer is invalid.
287
288-  When you Release() a local consumer that is connected, all apps will
289   go through their producers, and throw away this consumer from their
290   connection lists. If one of these producers is local, we call its
291   Disconnected() hook. If you release a local producer, this is not
292   necessary.
293
294Watching
295~~~~~~~~
296
297-  When you call StartWatching(), the BMidiRosterLooper remembers the
298   BMessenger, and sends it B_MIDI_EVENT notifications for all
299   registered remote endpoints, and the current connections between
300   them. It does not let you know about local endpoints. When you call
301   StartWatching() a second time with the same BMessenger, you'll
302   receive the whole bunch of notifications again. StartWatching(NULL)
303   is not allowed, and will be ignored (so it is not the same as
304   StopWatching()).
305
306Thread safety
307~~~~~~~~~~~~~
308
309-  Within libmidi2 there are several possible race conditions, because
310   we are dealing with two threads: the one from BMidiRosterLooper and a
311   thread from the client app, most likely the BApplication's main
312   thread. Both can access the same data: BMidiEndpoint objects. To
313   synchronize these threads, we lock the BMidiRosterLooper, which is a
314   normal BLooper. Anything happening in BMidiRosterLooper's message
315   handlers is safe, because BLoopers are automatically locked when
316   handling a message. Any other operations (which run from a different
317   thread) must first lock the looper if they access the list of
318   endpoints or certain BMidiEndpoint attributes (name, properties,
319   etc).
320
321-  What if you obtain a BMidiEndpoint object from FindEndpoint() and at
322   the same time the BMidiRosterLooper receives an 'mDEL' request to
323   delete that endpoint? FindEndpoint() locks the looper, and bumps the
324   endpoint object before giving it to you. Now the looper sees that the
325   endpoint's refcount is larger than 0, so it won't delete it (although
326   it will remove the endpoint from its internal list). What if you
327   Acquire() or Release() a remote endpoint while it is being deleted by
328   the looper? That also won't happen, because if you have a pointer to
329   that endpoint, its refcount is at least 1 and the looper won't delete
330   it.
331
332-  It is not safe to use a BMidiEndpoint and/or the BMidiRoster from
333   more than one client thread at a time; if you want to do that, you
334   should synchronize access to these objects yourself. The only
335   exception is the Spray() functions from BMidiLocalProducer, since
336   most producers have a separate thread to spray their MIDI events.
337   This is fine, as long as that thread isn't used for anything else,
338   and it is the only one that does the spraying.
339
340-  BMidiProducer objects keep a list of consumers they are connected to.
341   This list can be accessed by several threads at a time: the client's
342   thread, the BMidiRosterLooper thread, and possibly a separate thread
343   that is spraying MIDI events. We could have locked the producer using
344   BMidiRosterLooper's lock, but that would freeze everything else while
345   the producer is spraying events. Conversely, it would freeze all
346   producers while the looper is talking to the midi_server. To lock
347   with a finer granularity, each BMidiProducer has its own BLocker,
348   which is used only to lock the list of connected consumers.
349
350Misc remarks
351~~~~~~~~~~~~
352
353-  BMidiEndpoint keeps track of its local/remote state with an "isLocal"
354   variable, and whether it is a producer/consumer with "isConsumer". It
355   also has an "isRegistered" field to remember whether this endpoint is
356   registered or not. Why not lump all these different states together
357   into one "flags" bitmask? The reason is that isLocal only makes sense
358   to this application, not to others. Also, the values of isLocal and
359   isConsumer never change, but isRegistered does. It made more sense
360   (and clearer code) to separate them out. Finally, isRegistered does
361   not need to be protected by a lock, even though it can be accessed by
362   multiple threads at a time. Reading and writing a bool is atomic, so
363   this can't get messed up.
364
365The messages
366~~~~~~~~~~~~
367
368   ::
369
370      Message: Mapp (MSG_REGISTER_APP)
371          BMessenger midi:messenger
372      Reply: 
373          (no reply)
374
375      Message: mAPP (MSG_APP_REGISTERED)
376          (no fields)
377
378      Message: Mnew (MSG_CREATE_ENDPOINT)
379          bool     midi:consumer
380          bool     midi:registered
381          char[]   midi:name
382          BMessage midi:properties
383          int32    midi:port       (consumer only)
384          int64    midi:latency    (consumer only)
385      Reply:
386          int32    midi:result
387          int32    midi:id
388
389      Message: mNEW (MSG_ENPOINT_CREATED)
390          int32    midi:id
391          bool     midi:consumer
392          bool     midi:registered
393          char[]   midi:name
394          BMessage midi:properties
395          int32    midi:port       (consumer only)
396          int64    midi:latency    (consumer only)
397
398      Message: Mdel (MSG_DELETE_ENDPOINT)
399          int32 midi:id
400      Reply: 
401          (no reply)
402
403      Message: Mdie (MSG_PURGE_ENDPOINT)
404          int32 midi:id
405      Reply: 
406          (no reply)
407
408      Message: mDEL (MSG_ENDPOINT_DELETED)
409          int32 midi:id
410
411      Message: Mchg (MSG_CHANGE_ENDPOINT)
412          int32    midi:id
413          int32    midi:registered (optional)
414          char[]   midi:name       (optional)
415          int64    midi:latency    (optional)
416          BMessage midi:properties (optional)
417      Reply:
418          int32 midi:result
419
420      Message: mCHG (MSG_ENDPOINT_CHANGED)
421          int32 midi:id
422          int32    midi:registered (optional)
423          char[]   midi:name       (optional)
424          int64    midi:latency    (optional)
425          BMessage midi:properties (optional)
426
427--------------
428
429MIDI events
430-----------
431
432-  MIDI events are always sent from a BMidiLocalProducer to a
433   BMidiLocalConsumer. Proxy endpoint objects have nothing to do with
434   this. During its construction, the local consumer creates a kernel
435   port. The ID of this port is published, so everyone knows what it is.
436   When a producer sprays an event, it creates a message that it sends
437   to the ports of all connected consumers.
438
439-  This means that the Midi Kit considers MIDI messages as discrete
440   events. Hardware drivers chop the stream of incoming MIDI data into
441   separate events that they send out to one or more kernel ports.
442   Consumers never have to worry about parsing a stream of MIDI data,
443   just about handling a bunch of separate events.
444
445-  Each BMidiLocalConsumer has a (realtime priority) thread associated
446   with it that waits for data to arrive at the port. As soon as a new
447   MIDI message comes in, the thread examines it and feeds it to the
448   Data() hook. The Data() hook ignores the message if the "atomic" flag
449   is false, or passes it on to one of the other hook functions
450   otherwise. Incoming messages are also ignored if their contents are
451   not valid; for example, if they have too few or too many bytes for a
452   certain type of MIDI event.
453
454-  Unlike the consumer, BMidiLocalProducer has no thread of its own. As
455   a result, spraying MIDI events always happens in the thread of the
456   caller. Because the consumer port's queue is only 1 message deep,
457   spray functions will block if the consumer thread is already busy
458   handling another MIDI event. (For this reason, the Midi Kit does not
459   support interleaving of real time messages with lower priority
460   messages such as sysex dumps, except at the driver level.)
461
462-  The producer does not just send MIDI event data to the consumer, it
463   also sends a 20-byte header describing the event. The total message
464   looks like this:
465
466      +---------+------------------------------+
467      | 4 bytes | ID of the producer           |
468      +---------+------------------------------+
469      | 4 bytes | ID of the consumer           |
470      +---------+------------------------------+
471      | 8 bytes | performance time             |
472      +---------+------------------------------+
473      | 1 byte  | atomic (1 = true, 0 = false) |
474      +---------+------------------------------+
475      | 3 bytes | padding (0)                  |
476      +---------+------------------------------+
477      | x bytes | MIDI event data              |
478      +---------+------------------------------+
479
480-  In the case of a sysex event, the SystemExclusive() hook is only
481   called if the first byte of the message is 0xF0. The sysex end marker
482   (0xF7) is optional; only if the last byte is 0xF7 we strip it off.
483   This is unlike Be's implementation, which all always strips the last
484   byte even when it is not 0xF7. According to the MIDI spec, 0xF7 is
485   not really required; any non-realtime status byte ends a sysex
486   message.
487
488-  SprayTempoChange() sends 0xFF5103tttttt, where tttttt is
489   60,000,000/bpm. This feature is not really part of the MIDI spec, but
490   an extension from the SMF (Standard MIDI File) format. Of course, the
491   TempoChange() hook is called in response to this message.
492
493-  The MIDI spec allows for a number of shortcuts. A Note On event with
494   velocity 0 is supposed to be interpreted as a Note Off, for example.
495   The Midi Kit does not concern itself with these shortcuts. In this
496   case, it still calls the NoteOn() hook with a velocity parameter of
497   0.
498
499-  The purpose of BMidiLocalConsumer's AllNotesOff() function is not
500   entirely clear. All Notes Off is a so-called "channel mode message"
501   and is generated by doing a SprayControlChange(channel,
502   B_ALL_NOTES_OFF, 0). BMidi has an AllNotesOff() function that sends
503   an All Notes Off event to all channels, and possible Note Off events
504   to all keys on all channels as well. I suspect someone at Be was
505   confused by AllNotesOff() being declared "virtual", and thought it
506   was a hook function. Only that would explain it being in
507   BMidiLocalConsumer as opposed to BMidiLocalProducer, where it would
508   have made sense. The disassembly for Be's libmidi2.so shows that
509   AllNotesOff() is empty, so to cut a long story short, our
510   AllNotesOff() simply does nothing and is never invoked either.
511
512-  There are several types of System Common events, each of which takes
513   a different number of data bytes (0, 1, or 2). But
514   SpraySystemCommon() and the SystemCommon() hook are always given 2
515   data parameters. The Midi Kit simply ignores the extra data bytes; in
516   fact, in our implementation it doesn't even send them. (The Be
517   implementation always sends 2 data bytes, but that will confuse the
518   Midi Kit if the client does a SprayData() of a common event instead.
519   In our case, that will still invoke the SystemCommon() hook, because
520   we are not as easily fooled.)
521
522-  Handling of timeouts is fairly straightforward. When reading from the
523   port, we specify an absolute timeout. When the port function returns
524   with a B_TIMED_OUT error code, we call the Timeout() hook. Then we
525   reset the timeout value to -1, which means that timeouts are disabled
526   (until the client calls SetTimeout() again). This design means that a
527   call to SetTimeout() only takes effect the next time we read from the
528   port, i.e. after at least one new MIDI event is received (or the
529   previous timeout is triggered). Even though BMidiLocalConsumer's
530   timeout and timeoutData values are accessed by two different threads,
531   I did not bother to protect this. Both values are int32's and
532   reading/writing them should be an atomic operation on most processors
533   anyway.
534
535.. |image0| image:: midi_server.png
536.. |image1| image:: libmidi2.png
537
538