New design docs

git-svn-id: file:///srv/svn/repos/haiku/trunk/current@2880 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
mahlzeit
2003-03-10 19:23:14 +00:00
parent 92185c23c0
commit 3b101c8633
6 changed files with 1764 additions and 6 deletions
-6
View File
@@ -1,6 +0,0 @@
All of the Midi Kit documentation currently resides
on the MIDI team website:
<http://home.concepts.nl/~hollies/midi>
+538
View File
@@ -0,0 +1,538 @@
<HTML>
<BODY>
<H1>Midi Kit design</H1>
<P>The Midi Kit consists of the midi_server and two shared libraries,
libmidi2.so and libmidi.so. The latter is the "old" pre-R5 Midi Kit and has
been re-implemented using the facilities from libmidi2, which makes it fully
compatible with the new kit. This document describes the design and
implementation of the OpenBeOS midi_server and libmidi2.so.</P>
<P>The midi_server has two jobs: it keeps track of the endpoints that the
client apps have created, and it publishes endpoints for the devices from
/dev/midi. (This last task could have been done by any other app, but it was
just as convenient to make the midi_server do that.) The libmidi2.so library
also has two jobs: it assists the midi_server with the housekeeping stuff, and
it allows endpoints to send and receive MIDI events. (That's right, the
midi_server has nothing to do with the actual MIDI data.)</P>
<HR SIZE="1">
<H2>Ooh, pictures</H2>
<P>The following image shows the center of Midi Kit activity, the midi_server,
and its data structures:</P>
<BLOCKQUOTE><IMG ALT="" SRC="midi_server.png"></BLOCKQUOTE>
<P>And here is the picture for libmidi2.so:</P>
<BLOCKQUOTE><IMG ALT="" SRC="libmidi2.png"></BLOCKQUOTE>
<P>Note that these diagrams give only a conceptual overview of who is
responsible for which bits of data. The actual implementation details of the
kit may differ.</P>
<HR SIZE="1">
<H2>Housekeeping</H2>
<UL>
<LI><P>The design for our implementation of the midi2 "housekeeping" protocol
roughly follows <A HREF="oldprotocol.html">what Be did</A>, although there are
some differences. In Be's implementation, the BMidiRosters only have
BMidiEndpoints for remote endpoints if they are registered. In our
implementation, the BMidiRosters have BMidiEndpoint objects for <I>all</I>
endpoints, including remote endpoints that aren't published at all. If there
are many unpublished endpoints in the system, our approach is less optimal.
However, it made the implementation of the Midi Kit much easier ;-)</P></LI>
<LI><P>Be's libmidi2.so exports the symbols "midi_debug_level" and
"midi_dispatcher_priority", both int32's. Our libmidi2 does not use either of
these. But even though these symbols are not present in the headers, some apps
may use them nonetheless. That's why our libmidi2 exports those symbols as
well.</P></LI>
<LI><P>The name of the message fields in Be's implementation of the protocol
had the "be:" prefix. Our fields have a "midi:" prefix instead. Except for the
fields in the B_MIDI_EVENT notification messages, because that would break
compatibility with existing apps.</P></LI>
</UL>
<H3>Initialization</H3>
<UL>
<LI><P>The first time an app uses a midi2 class, the BMidiRoster::MidiRoster()
method sends an 'Mapp' message to the midi_server, and blocks (on a semaphore).
This message includes a messenger to the app's BMidiRosterLooper object. The
server adds the app to its list of registered apps. Then the server
asynchronously sends back a series of 'mNEW' message notifications for all
endpoints on the roster, and 'mCON' messages for all existing connections. The
BMidiRosterLooper creates BMidiEndpoint objects for these endpoints and adds
them to its local roster; if the app is watching, it also sends out
corresponding B_MIDI_EVENT notifications. Finally, the midi_server sends an
'mAPP' message to notify the app that it has been successfully registered. Upon
receipt, BMidiRoster::MidiRoster() unblocks and returns control to the client
code. This handshake is the only asynchronous message exchange; all the other
requests have a synchronous reply.</P></LI>
<LI><P>If the server detects an error during any of this (incorrect message
format, delivery failure, etc.) it simply ignores the request and does not try
to send anything back to the client (which is most likely impossible anyway).
If the app detects an error (server sends back meaningless info, cannot connect
to server), it pretends that everything is hunkey dorey. (The API has no way of
letting the client know that the initialization succeeded.) Next time the app
tries something, the server either still does not respond, or it ignores the
request (because this app isn't properly registered). However, if the app does
not receive the 'mAPP' message, it will not unblock, and remains frozen for all
eternity.</P></LI>
<LI><P>BMidiRoster's MidiRoster() method creates the one and only BMidiRoster
instance on the heap the first time it is called. This instance is
automatically destroyed when the app quits.</P></LI>
</UL>
<H3>Error handling</H3>
<UL>
<LI><P>If some error occurs, then the reply message is only guaranteed to
contain the "midi:result" field with some non- zero error code. libmidi2 can
only assume that the reply contains other data on success (i.e. when
"midi:result" is B_OK).</P></LI>
<LI><P>The timeout for delivering and responding to a message is about 2
seconds. If the client receives no reply within that time, it assumes the
request failed. If the server cannot deliver a message within 2 seconds, it
assumes the client is dead and removes it (and its endpoints) from the roster.
Of course, these assumptions may be false. If the client wasn't dead and tries
to send another request to the server, then the server will now ignore it,
since the client app is no longer registered.</P></LI>
<LI><P>Because we work with timeouts, we must be careful to avoid
misunderstandings between the midi_server and the client app. Both sides must
recognize the timeout, so they both can ignore the operation. If, however, the
server thinks that everything went okay, but the client flags an error, then
the server and the client will have two different ideas of the current state of
the roster. Of course, those situations must be avoided.</P></LI>
<LI><P>Although apps register themselves with the midi_server, there is no
corresponding "unregister" message. The only way the server recognizes that an
app and its endpoints are no longer available is when it fails to deliver a
message to that app. In that case, we remove the app and all its endpoints from
the roster. To do this, the server sends "purge endpoint" messages to itself
for all of the app's endpoints. This means we don't immediately throw the app
away, but we schedule that for some time in the future. That makes the whole
event handling mechanism much cleaner. There is no reply to the purge request.
(Actually, we <I>do</I> immediately throw away the app_t object, since that
doesn't really interfere with anything.) (If there are other events pending in
the queue which also cause notifications, then the server may send multiple
purge messages for the same endpoints. That's no biggie, because a purge
message will be ignored if its endpoint no longer exists.)</P></LI>
<LI><P>As mentioned above, the midi_server ignores messages that do not come
from a registered app, although it does send back an error reply. In the case
of the "purge endpoint" message, the server makes sure the message was local
(i.e. sent by the midi_server itself).</P></LI>
<LI><P>Note: BMessage's SendReply() apparently succeeds even if you kill the
app that the reply is intended for. This is rather strange, and it means that
you can't test delivery error handling for replies by killing the app. (You
<I>can</I> kill the app for testing the error handling on notifications,
however.)</P></LI>
</UL>
<H3>Creating and deleting endpoints</H3>
<UL>
<LI><P>When client code creates a new BMidiLocalProducer or BMidiLocalConsumer
endpoint, we send an 'Mnew' message to the server. Unlike Be's implementation,
the "name" field is always present, even if the name is empty. After adding the
endpoint to the roster, the server sends 'mNEW' notifications to all other
applications. Upon receipt of this notification, the BMidiRosterLoopers of
these apps create a new BMidiEndpoint for the endpoint and add it to their
internal list of endpoints. The app that made the request receives a reply with
a single "midi:result" field.</P></LI>
<LI><P>When you "new" an endpoint, its refcount is 1, even if the creation
failed. (For example, if the midi_server does not run.) When you Acquire(), the
refcount is bumped. When you Release(), it is decremented. When refcount drops
to 0, the endpoint object "deletes" itself. (So client code should never use an
endpoint after having Release()'d it, because the object may have just been
killed.) When creation succeeds, IsValid() returns true and ID() returns a
valid ID (> 0). Upon failure, IsValid() is false and ID() returns 0.</P></LI>
<LI><P>After the last Release() of a local endpoint, we send 'Mdel' to let the
midi_server know the endpoint is now deleted. We don't expect a reply back. If
something goes wrong, the endpoint is deleted regardless. We do not send
separate "unregistered" notifications, because deleting an endpoint implies
that it is removed from the roster. For the same reason, we also don't send
separate "disconnected" notifications.</P></LI>
<LI><P>The 'mDEL' notification triggers a BMidiRosterLooper to remove the
corresponding BMidiEndpoint from its internal list. This object is always a
proxy for a remote endpoint. The remote endpoint is gone, but whether we can
also delete the proxy depends on its reference count. If no one is still using
the object, its refcount is zero, and we can safely delete the object.
Otherwise, we must defer destruction until the client Release()'s the
object.</P></LI>
<LI><P>If you "delete" an endpoint, your app drops into the debugger.</P></LI>
<LI><P>If you Release() an endpoint too many times, your app <I>could</I> drop
into the debugger. It might also crash, because you are now using a dead
object. It depends on whether the memory that was previously occupied by your
endpoint object was overwritten in the mean time.</P></LI>
<LI><P>You are allowed to pass NULL into the constructors of BMidiLocalConsumer
and BMidiLocalProducer, in which case the endpoint's name is simply an empty
string.</P></LI>
</UL>
<H3>Changing endpoint attributes</H3>
<UL>
<LI><P>An endpoint can be "invalid". In the case of a proxy this means that the
remote endpoint is unregistered or even deleted. Local endpoints can only be
invalid if something went wrong during their creation (no connection to server,
for example). You can get the attributes of invalid objects, but you cannot set
them. Any attempts to do so will return an error code.</P></LI>
<LI><P>For changing the name, latency, or properties of an endpoint, libmidi2
sends an 'Mchg' message with the fields that should be changed, "midi:name",
"midi:latency", or "midi:properties". Registering or unregistering an endpoint
also sends such an 'Mchg' message, because we consider the "registered" state
also an attribute, in "midi:registered". The message obviously also includes
the ID of the endpoint in question. Properties are sent using a different
message, because the properties are not stored inside the
BMidiEndpoints.</P></LI>
<LI><P>After handling the 'Mchg' request, the midi_server broadcasts an 'mCHG'
notification to all the other apps. This message has the same contents as the
original request.</P></LI>
<LI><P>If the 'Mchg' message contains an invalid "midi:id" (i.e. no such
endpoint exists or it does not belong to the app that sent the request), the
midi_server returns an error code, and it does not notify the other
apps.</P></LI>
<LI><P>If you try to Register() an endpoint that is already registered,
libmidi2 does not send a message to the midi_server but simply returns B_OK.
(Be's implementation <I>did</I> send a message, but our libmidi2 also keeps
track whether an endpoint is registered or not.) Although registering an
endpoint more than once doesn't make much sense, it is not considered an error.
Likewise for Unregister()ing an endpoint that is not registered.</P></LI>
<LI><P>If you try to Register() or Unregister() a remote endpoint, libmidi2
immediately returns an error code, and does not send a message to the server.
Likewise for a local endpoints that are invalid (i.e. whose IsValid() function
returns false).</P></LI>
<LI><P>BMidiRoster::Register() and Unregister() do the same thing as
BMidiEndpoint::Register() and Unregister(). If you pass NULL into these
functions, they return B_BAD_VALUE.</P></LI>
<LI><P>SetName() ignores NULL names. When you call it on a remote endpoint,
SetName() does nothing. SetName() does not send a message if the new name is
the same as the current name.</P></LI>
<LI><P>SetLatency() ignores negative values. SetLatency() does not send a
message if the new latency is the same as the current latency. (Since
SetLatency() lives in BMidiLocalConsumer, you can never use it on remote
endpoints.)</P></LI>
<LI><P>We store a copy of the endpoint properties in each BMidiEndpoint. The
properties of new endpoints are empty. GetProperties() copies this BMessage
into the client's BMessage. GetProperties() returns NULL if the message
parameter is NULL.</P></LI>
<LI><P>SetProperties() returns NULL if the message parameter is NULL. It
returns an error code if the endpoint is remote or invalid. SetProperties()
does <I>not</I> compare the contents of the new BMessage to the old, so it will
always send out the change request.</P></LI>
</UL>
<H3>Connections</H3>
<UL>
<LI><P>BMidiProducer::Connect() sends an 'Mcon' request to the midi_server.
This request contains the IDs of the producer and the consumer you want to
connect. The server sends back a reply with a result code. If it is possible to
make this connection, the server broadcasts an 'mCON' notification to all other
apps. In one of these apps the producer is local, so that app's libmidi2 calls
the BMidiLocalProducer::Connected() hook.</P></LI>
<LI><P>You are not allowed to connect the same producer and consumer more than
once. The midi_server checks for this. It also returns an error code if you try
to disconnect two endpoints that were not connected.</P></LI>
<LI><P>Disconnect() sends an 'Mdis' request to the server, which contains the
IDs of the producer and consumer that you want to disconnect. The server
replies with a result code. If the connection could be broken, it also sends an
'mDIS' notification to the other apps. libmidi2 calls the local producer's
BMidiLocalProducer::Disconnected() hook.</P></LI>
<LI><P>Connect() and Disconnect() immediately return an error code if you pass
a NULL argument, or if the producer or consumer is invalid.</P></LI>
<LI><P>When you Release() a local consumer that is connected, all apps will go
through their producers, and throw away this consumer from their connection
lists. If one of these producers is local, we call its Disconnected() hook. If
you release a local producer, this is not necessary.</P></LI>
</UL>
<H3>Watching</H3>
<UL>
<LI><P>When you call StartWatching(), the BMidiRosterLooper remembers the
BMessenger, and sends it B_MIDI_EVENT notifications for all registered remote
endpoints, and the current connections between them. It does not let you know
about local endpoints. When you call StartWatching() a second time with the
same BMessenger, you'll receive the whole bunch of notifications again.
StartWatching(NULL) is not allowed, and will be ignored (so it is not the same
as StopWatching()).</P></LI>
</UL>
<H3>Thread safety</H3>
<UL>
<LI><P>Within libmidi2 there are several possible race conditions, because we
are dealing with two threads: the one from BMidiRosterLooper and a thread from
the client app, most likely the BApplication's main thread. Both can access the
same data: BMidiEndpoint objects. To synchronize these threads, we lock the
BMidiRosterLooper, which is a normal BLooper. Anything happening in
BMidiRosterLooper's message handlers is safe, because BLoopers are
automatically locked when handling a message. Any other operations (which run
from a different thread) must first lock the looper if they access the list of
endpoints or certain BMidiEndpoint attributes (name, properties, etc).</P></LI>
<LI><P>What if you obtain a BMidiEndpoint object from FindEndpoint() and at the
same time the BMidiRosterLooper receives an 'mDEL' request to delete that
endpoint? FindEndpoint() locks the looper, and bumps the endpoint object before
giving it to you. Now the looper sees that the endpoint's refcount is larger
than 0, so it won't delete it (although it will remove the endpoint from its
internal list). What if you Acquire() or Release() a remote endpoint while it
is being deleted by the looper? That also won't happen, because if you have a
pointer to that endpoint, its refcount is at least 1 and the looper won't
delete it.</P></LI>
<LI><P>It is not safe to use a BMidiEndpoint and/or the BMidiRoster from more
than one client thread at a time; if you want to do that, you should
synchronize access to these objects yourself. The only exception is the Spray()
functions from BMidiLocalProducer, since most producers have a separate thread
to spray their MIDI events. This is fine, as long as that thread isn't used for
anything else, and it is the only one that does the spraying.</P></LI>
<LI><P>BMidiProducer objects keep a list of consumers they are connected to.
This list can be accessed by several threads at a time: the client's thread,
the BMidiRosterLooper thread, and possibly a separate thread that is spraying
MIDI events. We could have locked the producer using BMidiRosterLooper's lock,
but that would freeze everything else while the producer is spraying events.
Conversely, it would freeze all producers while the looper is talking to the
midi_server. To lock with a finer granularity, each BMidiProducer has its own
BLocker, which is used only to lock the list of connected consumers.</P></LI>
</UL>
<H3>Misc remarks</H3>
<UL>
<LI><P>BMidiEndpoint keeps track of its local/remote state with an "isLocal"
variable, and whether it is a producer/consumer with "isConsumer". It also has
an "isRegistered" field to remember whether this endpoint is registered or not.
Why not lump all these different states together into one "flags" bitmask? The
reason is that isLocal only makes sense to this application, not to others.
Also, the values of isLocal and isConsumer never change, but isRegistered does.
It made more sense (and clearer code) to separate them out. Finally,
isRegistered does not need to be protected by a lock, even though it can be
accessed by multiple threads at a time. Reading and writing a bool is atomic,
so this can't get messed up.</P></LI>
</UL>
<H3>The messages</H3>
<BLOCKQUOTE><PRE>
Message: Mapp (MSG_REGISTER_APP)
BMessenger midi:messenger
Reply:
(no reply)
Message: mAPP (MSG_APP_REGISTERED)
(no fields)
Message: Mnew (MSG_CREATE_ENDPOINT)
bool midi:consumer
bool midi:registered
char[] midi:name
BMessage midi:properties
int32 midi:port (consumer only)
int64 midi:latency (consumer only)
Reply:
int32 midi:result
int32 midi:id
Message: mNEW (MSG_ENPOINT_CREATED)
int32 midi:id
bool midi:consumer
bool midi:registered
char[] midi:name
BMessage midi:properties
int32 midi:port (consumer only)
int64 midi:latency (consumer only)
Message: Mdel (MSG_DELETE_ENDPOINT)
int32 midi:id
Reply:
(no reply)
Message: Mdie (MSG_PURGE_ENDPOINT)
int32 midi:id
Reply:
(no reply)
Message: mDEL (MSG_ENDPOINT_DELETED)
int32 midi:id
Message: Mchg (MSG_CHANGE_ENDPOINT)
int32 midi:id
int32 midi:registered (optional)
char[] midi:name (optional)
int64 midi:latency (optional)
BMessage midi:properties (optional)
Reply:
int32 midi:result
Message: mCHG (MSG_ENDPOINT_CHANGED)
int32 midi:id
int32 midi:registered (optional)
char[] midi:name (optional)
int64 midi:latency (optional)
BMessage midi:properties (optional)
</PRE></BLOCKQUOTE>
<HR SIZE="1">
<H2>MIDI events</H2>
<UL>
<LI><P>MIDI events are always sent from a BMidiLocalProducer to a
BMidiLocalConsumer. Proxy endpoint objects have nothing to do with this. During
its construction, the local consumer creates a kernel port. The ID of this port
is published, so everyone knows what it is. When a producer sprays an event, it
creates a message that it sends to the ports of all connected consumers.</P></LI>
<LI><P>This means that the Midi Kit considers MIDI messages as discrete events.
Hardware drivers chop the stream of incoming MIDI data into separate events
that they send out to one or more kernel ports. Consumers never have to worry
about parsing a stream of MIDI data, just about handling a bunch of separate
events.</P></LI>
<LI><P>Each BMidiLocalConsumer has a (realtime priority) thread associated with
it that waits for data to arrive at the port. As soon as a new MIDI message
comes in, the thread examines it and feeds it to the Data() hook. The Data()
hook ignores the message if the "atomic" flag is false, or passes it on to one
of the other hook functions otherwise. Incoming messages are also ignored if
their contents are not valid; for example, if they have too few or too many
bytes for a certain type of MIDI event.</P></LI>
<LI><P>Unlike the consumer, BMidiLocalProducer has no thread of its own. As a
result, spraying MIDI events always happens in the thread of the caller.
Because the consumer port's queue is only 1 message deep, spray functions will
block if the consumer thread is already busy handling another MIDI event. (For
this reason, the Midi Kit does not support interleaving of real time messages
with lower priority messages such as sysex dumps, except at the driver
level.)</P></LI>
<LI><P>The producer does not just send MIDI event data to the consumer, it also
sends a 20-byte header describing the event. The total message looks like
this:</P>
<BLOCKQUOTE><TABLE BORDER="1">
<TR><TD>4 bytes</TD><TD>ID of the producer</TD></TR>
<TR><TD>4 bytes</TD><TD>ID of the consumer</TD></TR>
<TR><TD>8 bytes</TD><TD>performance time</TD></TR>
<TR><TD>1 byte</TD><TD>atomic (1 = true, 0 = false)</TD></TR>
<TR><TD>3 bytes</TD><TD>padding (0)</TD></TR>
<TR><TD>x bytes</TD><TD>MIDI event data</TD></TR>
</TABLE></BLOCKQUOTE></LI>
<LI><P>In the case of a sysex event, the SystemExclusive() hook is only called
if the first byte of the message is 0xF0. The sysex end marker (0xF7) is
optional; only if the last byte is 0xF7 we strip it off. This is unlike Be's
implementation, which all always strips the last byte even when it is not 0xF7.
According to the MIDI spec, 0xF7 is not really required; any non-realtime
status byte ends a sysex message.</P></LI>
<LI><P>SprayTempoChange() does nothing and TempoChange() is never called, just
like with the BeOS R5 Midi Kit. The Midi2Defs.h header defines the symbol
B_TEMPO_CHANGE, which is equal to "general purpose controller 6". In theory,
SprayTempoChange() would send a Control Change event for the B_TEMPO_CHANGE
controller, and TempoChange() would be invoked in response of such an event.
But the R5 Midi Kit doesn't do this, and neither do we.</P></LI>
<LI><P>The MIDI spec allows for a number of shortcuts. A Note On event with
velocity 0 is to interpreted as a Note Off, for example. The Midi Kit does not
concern itself with these shortcuts. In this case, it still calls the NoteOn()
hook with a velocity parameter of 0.</P></LI>
<LI><P>The purpose of BMidiLocalConsumer's AllNotesOff() function is not
entirely clear. All Notes Off is a so-called "channel mode message" and is
generated by doing a SprayControlChange(channel, B_ALL_NOTES_OFF, 0). One would
assume that the AllNotesOff() hook is called in response to the receipt of such
an event, but no, it is not. What's odd is that the documentation for the midi1
kit's BMidi class does not describe this AllNotesOff() as a hook at all, but as
a spray function that sends an All Notes Off event to all 16 channels, and
optionally sends Note Off events for all notes too. Even stranger, BMidi's
AllNotesOff() is declared "virtual", just like all other hooks, and unlike any
of the other spray functions. Since AllNotesOff() is also part of
BMidiLocalConsumer, it really seems to be a hook function. The disassembly for
Be's libmidi2.so shows that AllNotesOff() is empty, which only supports this
hypothesis. Somebody at Be must have been very confused when they designed the
midi2 kit. To cut a long story short, AllNotesOff(), like TempoChange() does
nothing and is never invoked in our implementation.</P></LI>
<LI><P>There are several types of System Common events, each of which takes a
different number of data bytes (0, 1, or 2). But SpraySystemCommon() and the
SystemCommon() hook are always given 2 data parameters. The Midi Kit simply
ignores the extra data bytes; in fact, in our implementation it doesn't even
send them. (The Be implementation always sends 2 data bytes, but that will
confuse the Midi Kit if the client does a SprayData() of a common event
instead. In our case, that will still invoke the SystemCommon() hook, because
we are not as easily fooled.)</P></LI>
<LI><P>Handling of timeouts is fairly straightforward. When reading from the
port, we specify an absolute timeout. When the port function returns with a
B_TIMED_OUT error code, we call the Timeout() hook. Then we reset the timeout
value to -1, which means that timeouts are disabled (until the client calls
SetTimeout() again). This design means that a call to SetTimeout() only takes
effect the next time we read from the port, i.e. after at least one new MIDI
event is received (or the previous timeout is triggered). Even though
BMidiLocalConsumer's timeout and timeoutData values are accessed by two
different threads, I did not bother to protect this. Both values are int32's
and reading/writing them should be an atomic operation on most processors
anyway.</P></LI>
</UL>
</BODY>
</HTML>
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+660
View File
@@ -0,0 +1,660 @@
<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>
+566
View File
@@ -0,0 +1,566 @@
<HTML>
<BODY>
<H1>Testing the Midi Kit</H1>
<P>Most of the OpenBeOS source code has unit tests in the current/src/tests
directory. I looked into building CppUnit tests for the midi2 kit, but decided
that it doesn't really make much sense. Unit tests work best if you can test
something in isolation, but in the case of the midi2 kit this is very hard to
achieve. Because the classes from libmidi2.so always need to talk to the
midi_server, the tests depend on too many external factors. The available
endpoints, for example, will differ from system to system. The spray and hook
functions are difficult to test this way, too.</P>
<P>So instead of a CppUnit test suite, here is a list of manual tests that I
performed when developing the midi2 kit:</P>
<HR SIZE="1">
<H2>Registering the application</H2>
<P><I>Required:</I> Client app that calls BMidiRoster::MidiRoster()</P>
<UL>
<LI><P>When a client app starts, it should first receive mNEW notifications for
all endpoints in the system (even unregistered remotes), followed by mCON
notifications for all connections in the system (even those between two
unregistered local endpoints from another app).</P></LI>
<LI><P>Send invalid Mapp message (without messenger). The midi_server ignores
the request, and the client app blocks forever.</P></LI>
<LI><P>Fake a delivery error for the mNEW notifications and the mAPP reply.
(Add a snooze() in the midi_server's OnRegisterApplication(). While it is
snoozing, Ctrl-C the client app. Now the server can't deliver the message and
will unregister the application again.)</P></LI>
<LI><P>Kill the server. Start the client app. It should realize that the server
is not running, and return from MidiRoster(); it does not block
forever.</P></LI>
<LI><P>Note: The server does not protect against sending two or more Mapp
messages; it will add a new app_t object to the roster and it will also send
out the mNEW and mCON notifications again.</P></LI>
<LI><P>Verify that when the client app quits, the BMidiRoster instance is
destroyed by the BMidiRosterKiller. The BMidiRosterLooper is also destroyed,
along with any endpoint objects from its list. We don't destroy endpoints with
a refcount > 0, but print a warning message on stderr instead.</P></LI>
<LI><P>When the app quits before it has created a BMidiRoster instance, the
BMidiRosterKiller should do nothing.</P></LI>
</UL>
<HR SIZE="1">
<H2>Creating endpoints</H2>
<P><I>Required:</I> Client app that creates a new BMidiLocalProducer and/or
BMidiLocalConsumer</P>
<UL>
<LI><P>Send invalid Mnew message (missing fields). The server will return an
error code.</P></LI>
<LI><P>Don't send reply from midi_server. The client receives a B_NO_REPLY
error.</P></LI>
<LI><P>If something goes wrong creating a new local endpoint, you still get a
new BMidiEndpoint object (but it is not added to BMidiRosterLooper's internal
list of endpoints). Verify that its ID() function returns 0, and IsValid()
returns false. Verify that you can Release() it without crashing into the
debugger (i.e. the reference count of the new object should be 1).</P></LI>
<LI><P>Snooze in midi_server's OnCreateEndpoint() before sending reply to
client to simulate heavy processor load. Client should timeout. When done
snoozing, server fails to deliver the reply because the client is no longer
listening, and it unregisters the app.</P></LI>
<LI><P>Note: if you kill the client app with Ctrl-C before the server has sent
its reply, SendReply() still returns okay, and the midi_server adds the
endpoint, even though the corresponding app is dead. There is not much we can
do to prevent that (but it is not really a big deal).</P></LI>
<LI><P>Start the test app from two different Terminals. Verify that the new
local endpoint of app1 is added to the BMidiRosterLooper's list of endpoints,
and that its "isLocal" flag is true. Verify that when you start the second app,
it immediately receives mNEW notifications for the first app's endpoints. It
should also create BMidiEndpoint proxy objects for these endpoints with
"isLocal" set to false, and add them its own list. Vice versa for the endpoints
that app2 creates. Verify that the "registered" field in the mNEW notification
is false, because newly created endpoints are not registered yet. The
"properties" field should contain an empty message.</P></LI>
<LI><P>Start server. Start client app. The app makes new endpoints and the
server adds them to the roster. Ctrl-C the app. Start client app again. The new
client first receives mNEW notifications for the old app's endpoints. When the
new app tries to create its own endpoints, the server realizes that the old app
is dead, and sends mDEL notifications for the now-defunct endpoints.</P></LI>
<LI><P>The test app should now create 2 endpoints. Let the midi_server snooze
during the second create message, so the app times out. The server now
unregisters the app and purges its first endpoint (which was successfully
created).</P></LI>
<LI><P>The test app should now create 3 endpoints. Let the midi_server snooze
during the second create message, so the app times out. (It also times out when
sending the create request for the 3rd endpoint, because the server is still
snoozing.) Because it cannot send a reply for the 2nd create message, the
server now unregisters the app and purges its first endpoint (which was
successfully created). Then it processes the create request for the 3rd
endpoint, but ignores it because the app is now no longer registered with the
server.</P></LI>
<LI><P>Purging endpoints. The test app should now create 2 endpoints. Let the
midi_server snooze during the _fourth_ create message. Run the server. Run the
test app. Run the test app again in a second Terminal. The server times out,
and unregisters the second app. The first app should receive an mDEL
notification. Repeat, but now the test app should make 3 endpoints and the
server fails on the _sixth_ endpoint. The first app now receives 2 mDEL
notifications.</P></LI>
<LI><P>You should be allowed to pass NULL into the BMidiLocalProducer and
BMidiLocalConsumer constructor.</P></LI>
<LI><P>Let the midi_server assign random IDs to new endpoints; the
BMidiRosterLooper should sort the endpoints by their IDs when it adds them to
its internal list.</P></LI>
</UL>
<HR SIZE="1">
<H2>Deleting endpoints</H2>
<P><I>Required:</I> client app that creates one or more endpoints and
Release()'s them</P>
<UL>
<LI><P>Verify that Acquire() increments the endpoint's refcount and Release()
decrements it. When you Release() a local endpoint so its refcount becomes
zero, the client sends an Mdel request to the server. When you Release() a
local endpoint too many times, your app jumps into the debugger.</P></LI>
<LI><P>Send an Mdel request with an invalid ID to the server. Examples of
invalid IDs: -1, 0, 1000 (or any other large number).</P></LI>
<LI><P>Start the test app from two different Terminals. Note that when one of
the apps Release()'s its endpoints, the other receives corresponding mDEL
notifications.</P></LI>
<LI><P>Snooze in midi_server's OnCreateEndpoint() before sending reply to
"create endpoint" request. The client will timeout and the server will
unregister the app. Now have the client Release() the endpoint. This sends a
"delete endpoint" request to the server, which ignores the request because the
app is no longer registered.</P></LI>
<LI><P>Override BMidiLocalProducer and BMidiLocalConsumer, and provide a public
destructor. Call "delete prod; delete cons;" from your code, instead of using
Release(). Your app should drop into the debugger.</P></LI>
<LI><P>Start the client app and let it make its endpoints. Kill the server.
Release() the endpoints. The server doesn't run, so the Mdel request never
arrives, but the BMidiEndpoint objects should be deleted regardless.</P></LI>
<LI><P>Start the test app from two different Terminals, and let them make their
endpoints. Quit the apps (using the Deskbar's "Quit Application" menu item).
Verify that both clean up and exit correctly. App1 removes its own endpoint
from the BMidiRosterLooper's list of endpoints and sends an 'mDEL' message to
the server, which passes it on to app2. In response, app2 removes the proxy
object from its own list and deletes it. Again, vice versa for the endpoint
from app2.</P></LI>
<LI><P>Start both apps again and wait until they have notified each other about
the endpoints. Ctrl-C app1, and restart it. Verify that app1 receives the
'mNEW' messages and creates proxies for these remote endpoints. Both apps
should receive an 'mDEL' message for app1's old endpoint (because the
midi_server realizes it no longer exists and purges it), and remove it from
their lists accordingly.</P></LI>
</UL>
<HR SIZE="1">
<H2>Changing attributes</H2>
<P><I>Required:</I> Client app that creates an endpoint and calls Register(),
Unregister(), SetName(), and SetLatency()</P>
<UL>
<LI><P>Send an Mchg request with an invalid ID to the server.</P></LI>
<LI><P>Register() a local endpoint that is already registered. This does not
send a message to the server and always returns B_OK. Likewise for
Unregister()ing a local endpoint that is not registered.</P></LI>
<LI><P>Register() or Unregister() a remote endpoint, or an invalid local
endpoint. That should immediately return an error code.</P></LI>
<LI><P>Verify that BMidiRoster::Register() does the same thing as
BMidiEndpoint::Register(). Also for BMidiRoster::Unregister() and
BMidiEndpoint::Unregister().</P></LI>
<LI><P>If you pass NULL into BMidiRoster::Register() or Unregister(), the
functions immediately return with an error code.</P></LI>
<LI><P>SetName() should ignore NULL names. When you call it on a remote
endpoint, SetName() should do nothing. SetName() does not send a message if the
new name is the same as the current name.</P></LI>
<LI><P>SetLatency() should ignore negative values. SetLatency() does not send a
message if the new latency is the same as the current latency. (Since
SetLatency() lives in BMidiLocalConsumer, you can never use it on remote
endpoints.)</P></LI>
<LI><P>Kill the server after making the new endpoint, and call Register(). The
client app should return an error code. Also for Unregister(), SetName(),
SetLatency(), and SetProperties().</P></LI>
<LI><P>Snooze in the midi_server's OnChangeEndpoint() before sending the reply
to the client. Both sides will flag an error. No mCHG notifications will be
sent. The server unregisters the app and purges its endpoints.</P></LI>
<LI><P>Verify that other apps will receive mCHG notifications when the test app
successfully calls Register(), Unregister(), SetName(), and SetLatency(), and
that they modify the corresponding BMidiEndpoint objects accordingly. Since
clients are never notified when they change their own endpoints, they should
ignore the notifications that concern local endpoints. Latency changes should
be ignored if the endpoint is not a consumer.</P></LI>
<LI><P>Send an Mchg request with only the "midi:id" field, so no "midi:name",
"midi:registered", "midi:latency", or "midi:properties". The server will still
notify the other apps, although they will obviously ignore the notification,
because it doesn't contain any useful data.</P></LI>
<LI><P>The Mchg request is overloaded to change several attributes. Verify that
changing one of these attributes, such as the latency, does not overwrite/wipe
out the others.</P></LI>
<LI><P>Start app1. Wait until it has created and registered its endpoint. Start
app2. During the initial handshake, app2 should receive an 'mNEW' message for
app1's endpoint. Verify that the "refistered" field in this message is already
true, and that this is passed on correctly to the new BMidiEndpoint proxy
object.</P></LI>
<LI><P>GetProperties() should return NULL if the message parameter is
NULL.</P></LI>
<LI><P>The properties of new endpoints are empty. Create a new endpoint and
call GetProperties(). The BMessage that you receive should contain no
fields.</P></LI>
<LI><P>SetProperties() should return NULL if the message parameter is NULL. It
should return an error code if the endpoint is remote or invalid. It should
work fine on local endpoints, registered or not. SetProperties() does not
compare the contents of the new BMessage to the old, so it will always send out
the change request.</P></LI>
<LI><P>If you Unregister() an endpoint that is connected, the connection should
not be broken.</P></LI>
</UL>
<HR SIZE="1">
<H2>Consulting the roster</H2>
<P><I>Required:</I> Client app that creates several endpoints, and registers
some of them (not all), and uses the BMidiRoster::FindEndpoint() etc functions
to examine the roster.</P>
<UL>
<LI><P>Verify that FindEndpoint() returns NULL if you pass it:</P>
<UL>
<LI>invalid ID (localOnly = false)</LI>
<LI>invalid ID (localOnly = true)</LI>
<LI>remote non-registered endpoint (localOnly = false)</LI>
<LI>remote non-registered endpoint (localOnly = true)</LI>
<LI>remote registered endpoint (localOnly = true)</LI>
</UL><BR>
<P>Verify that FindEndpoint() returns a valid BMidiEndpoint object if you pass
it:</P>
<UL>
<LI>local non-registered endpoint (localOnly = false)</LI>
<LI>local non-registered endpoint (localOnly = true)</LI>
<LI>local registered endpoint (localOnly = false)</LI>
<LI>local registered endpoint (localOnly = true)</LI>
<LI>remote registered endpoint (localOnly = false)</LI>
</UL><BR>
</LI>
<LI><P>Verify that FindConsumer() works just like FindEndpoint(), but that it
also returns NULL if the endpoint with the specified ID is not a consumer.
Likewise for FindProducer().</P></LI>
<LI><P>Verify that NextEndpoint() returns NULL if you pass it NULL. It also
returns NULL if no more endpoints exist. Otherwise, it returns a BMidiEndpoint
object, bumps the endpoint's reference count, and sets the "id" parameter to
the ID of the endpoint. NextEndpoint() should never return local endpoints
(registered or not), nor unregistered remote endpoints. Verify that negative
"id" values also work.</P></LI>
<LI><P>Verify that you can safely call the Find and Next functions without
having somehow initialized the BMidiRoster first (by making a new endpoint, for
example). The functions themselves should call MidiRoster() and do the
handshake with the server.</P></LI>
<LI><P>The Find and Next functions should bump the reference count of the
BMidiEndpoint object that they return. However, they should not (inadvertently)
modify the refcounts of any other endpoint objects.</P></LI>
<LI><P>Get a BMidiEndpoint proxy for a remote published endpoint. Release().
Now it should not be removed from the endpoint list or even be deleted, even
though its reference count dropped to zero.</P></LI>
<LI><P>Start app1. Start app2. App2 gets a BMidiEndpoint proxy for a remote
endpoint from app1. Ctrl-C app1. Start app1 again. Now app2 receives an mDEL
message for app1's old endpoint. Verify that the endpoint is removed from the
endpoint list, but not deleted because its reference count isn't zero. If app2
now Release()s the endpoint, the BMidiEndpoint object should be deleted. Try
again, but now Release() the endpoint before you Ctrl-C; now it should be
deleted and removed from the list when you start app1 again.</P></LI>
</UL>
<HR SIZE="1">
<H2>Making/breaking connections</H2>
<P><I>Required:</I> Client app that creates a producer and consumer endpoint,
optionally registers them, consults the roster for remote endpoints, and makes
various kinds of connections.</P>
<UL>
<LI><P>Test the following for BMidiProducer::Connect():</P>
<UL>
<LI>Connect(NULL)</LI>
<LI>Connect(invalid consumer)</LI>
<LI>Connect() using an invalid producer</LI>
<LI>Send Mcon request with invalid IDs</LI>
<LI>Kill the midi_server just before you Connect()</LI>
<LI>Let the midi_server snooze, so the connect request times out</LI>
<LI>Have the midi_server return an error result code</LI>
<LI>On successful connect, verify that the consumer is added to the producer's
list of endpoints</LI>
<LI>Verify that you can make connections between 2 local endpoints, a local
producer and a remote consumer, a remote producer and a local consumer, and two
2 remote endpoints. Test the local endpoints both registered and
unregistered.</LI>
<LI>2x Connect() on same consumer should give an error</LI>
<LI>The other applications should receive an mCON notification, and adjust
their own local rosters accordingly</LI>
<LI>If you are calling Connect() on a local producer, its Connected() hook
should be called. If you are calling Connect() on a remote producer, then its
own application should call the Connected() hook.</LI>
</UL><BR></LI>
<LI><P>Test the following for BMidiProducer::Disconnect():</P>
<UL>
<LI>Disconnect(NULL)</LI>
<LI>Disconnect(invalid consumer)</LI>
<LI>Disconnect() using an invalid producer</LI>
<LI>Send Mdis request with invalid IDs</LI>
<LI>Kill the midi_server just before you Disconnect()</LI>
<LI>Let the midi_server snooze, so the disconnect request times out</LI>
<LI>Have the midi_server return an error result code</LI>
<LI>On successful disconnect, verify that the consumer is removed from the
producer's list of endpoints</LI>
<LI>Verify that you can break connections between 2 local endpoints, a local
producer and a remote consumer, a remote producer and a local consumer, and two
2 remote endpoints. Test the local endpoints both registered and
unregistered.</LI>
<LI>Disconnecting 2 endpoints that were not connected should give an error</LI>
<LI>The other applications should receive an mDIS notification, and adjust
their own local rosters accordingly</LI>
<LI>If you are calling Disconnect() on a local producer, its Disconnected()
hook should be called. If you are calling Disconnect() on a remote producer,
then its own application should call the Disconnected() hook.</LI>
</UL><BR></LI>
<LI><P>Make a connection on a local producer. Release() the producer. The other
app should only receive an mDEL notification. Likewise if you have a connection
with a local consumer and you Release() that. However, now all apps should
throw away this consumer from the connection lists, invoking the Disconnected()
hook of local producers. The same thing happens if you Ctrl-C the app and
restart it. (Now the old endpoints are purged.)</P></LI>
<LI><P>BMidiProducer::IsConnected() should return false if you pass NULL or an
invalid consumer.</P></LI>
<LI><P>BMidiProducer::Connections() should return a new BList every time you
call it. The objects in this list are the BMidiConsumers that are connected to
this producer; verify that their reference counts are bumped for every call to
Connections().</P></LI>
</UL>
<HR SIZE="1">
<H2>Watching</H2>
<P><I>Required:</I> Client app that creates local consumer and producer
endpoints, and calls Register(), Unregister(), SetName(), SetLatency(), and
SetProperties(). It should also make and break connections.</P>
<UL>
<LI><P>When you call StartWatching(), you should receive B_MIDI_EVENT
notifications for all remote registered endpoints and the connections between
them. You will get no notifications for local endpoints, or for any connections
that involve unregistered endpoints. The BMidiRosterLooper should make a copy
of the BMessenger, so when the client destroys the original messenger, you will
still receive notifications. Verify that calling StartWatching() with the same
BMessenger twice in a row will also send the initial set of notifications
twice. StartWatching(NULL) should be ignored and does not remove the current
messenger.</P></LI>
<LI><P>Run the client app from two different Terminals. Verify that you receive
properly formatted B_MIDI_EVENT notifications when the other app changes the
attributes of its <I>registered</I> endpoints with the various Set() functions.
You should also receive notifications if the app Register()s or Unregister()s
its endpoints. That app that makes these changes does not receive the
notifications.</P></LI>
<LI><P>Run the client app from two different Terminals. Verify that you receive
properly formatted B_MIDI_EVENT notifications when the apps make and break
connections. Every app receives these connection notifications, whether the
endpoints are published or not. The app that makes and breaks the connections
does not receive any notifications.</P></LI>
<LI><P>StopWatching() should delete BMidiRosterLooper's BMessenger copy, if
any. Verify that you no longer receive B_MIDI_EVENT notifications for remote
endpoints after you have called StopWatching().</P></LI>
<LI><P>If the client is watching, and the BMidiRosterLooper receives an mDEL
notification for a registered remote endpoint, it should also send an
"unregistered" B_MIDI_EVENT to let the client know that this endpoint is no
longer available. If the endpoint was connected to anything, you'll also
receive "disconnected" B_MIDI_EVENTs.</P></LI>
<LI><P>If you get a "registered" event, and you do FindEndpoint() for that id,
you'll get its BMidiEndpoint object. If you get an "unregistered" event, then
FindEndpoint() returns NULL. So the events are send <I>after</I> the roster is
modified.</P></LI>
</UL>
<HR SIZE="1">
<H2>Event tests</H2>
<P><I>Required:</I> Several client apps that create and register consumer
endpoints that override the various MIDI event hook functions, as well as
producer endpoints that spray MIDI events. Also useful is a tool that lets you
make connections between all these endpoints (PatchBay), and a tool that lets
you monitor the MIDI events (MidiMonitor).</P>
<UL>
<LI><P>BMidiLocalProducer's spray functions should only try to send something
if there is one or more connected consumer. If the spray functions cannot
deliver their events, they simply ignore that consumer until the next spray.
(No connections are broken or anything.)</P></LI>
<LI><P>All spray functions except SprayData() should set the atomic flag to
true, even SpraySystemExclusive().</P></LI>
<LI><P>When you send a sysex message using SpraySystemExclusive(), it should
add 0xF0 in front of your data and 0xF7 at the back. When you call SprayData()
instead, no bytes are added to the MIDI event data.</P></LI>
<LI><P>Verify that all events arrive correctly and that the latency is minimal,
even when the load is heavy (i.e. many events are being sprayed to many
different consumers).</P></LI>
<LI><P>Verify that the BMidiLocalConsumer destructor properly destroys the
corresponding port and event thread before it returns.</P></LI>
<LI><P>BMidiLocalConsumer should ignore messages that are too small, addressed
to another consumer, or otherwise invalid.</P></LI>
<LI><P>BMidiLocalConsumer's Data() hook should ignore all non-atomic events.
The rest of the events, provided they contain the correct number of bytes for
that kind of event, are passed on to the other hooks.</P></LI>
<LI><P>Hook a producer up to a consumer and call all SprayXXX() functions with
a variety of arguments to make sure the correct hooks are being called with the
correct values. Call SprayData() and SpraySystemExclusive() with NULL data
and/or length 0.</P></LI>
<LI><P>Call GetProducerID() from one of BMidiLocalConsumer's hooks to verify
that this indeed returns the ID of the producer that sprayed the
event.</P></LI>
<LI><P>To test timeouts, first call SetTimeout(system_time() + 2000000), spray
an event to the consumer, and wait 2 seconds. The consumer's Timeout() hook
should now be called. Try again, but now spray multiple events to the consumer.
The Timeout() hook should still be called after 2 seconds, measured from the
moment the timeout was set. Replace the call to SetTimeout() with
SetTimeout(0). After spraying the first event, you should immediately get the
Timeout() signal, because the target time was set in the past. Verify that
calling SetTimeout() only takes effect after at least one new event has been
received.</P></LI>
</UL>
<HR SIZE="1">
<H2>Other tests</H2>
<UL>
<LI><P>Kill the server. Now run a client app. It should recognize that the
server isn't running, and return error codes on all operations. Also kill the
server while the test app is running. From then on, the client app will return
error codes on all operations. Also bring it back up again while the test app
is still running. Now the client app's request messages will be delivered to
the server again, but the server will ignore them, because our app did not
register with this new instance of the server.</P></LI>
<LI><P>Start the midi_server and several client apps. Use PatchBay to make and
break a whole bunch of connections. Quit PatchBay. Start it again. Now the same
connections should show up. Run similar tests with MidiKeyboard. Also install
VirtualMidi (and run the old midi_server for the time being) to get a whole
bunch of fake MIDI devices.</P></LI>
<LI><P><I>Regression bug:</I> After you quit one client app, another app fails
to send request to the midi_server.</P>
<P><I>Required:</I> Client app that creates a new endpoint and registers it. In
the app's destructor, it unregisters and releases the endpoint.</P>
<P><I>How to reproduce:</I> Run the app from two different Terminals. Ctrl-C
app1. Start app1 again. From the Deskbar quit both apps at the same time (that
is possible because app1 and app2 both have the same signature). When it tries
to send the Unregister() request to the midi_server, app2 gives the error
"Cannot send msg to server". The error code is "Bad Port ID", which means that
the reply port is dead. The Mdel message from Release() is sent without any
problems, however, because that expects no reply back. This is not the only way
to reproduce the problem, but it seems to be the most reliable one.</P>
<P>The reason this happens is because you kill app1. When app2 sends a
synchronous request to the midi_server, the server re-used that same message to
notify the other apps. (Because it already contained all the necessary fields.)
But app1 is dead, the notification fails, and this (probably) wipes out the
reply address in the message. I changed the midi_server to create new BMessages
for the notifications, and was no longer able to reproduce the
problem.</P></LI>
</UL>
</BODY>
</HTML>