home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Club Amiga de Montreal - CAM
/
CAM_CD_1.iso
/
files
/
066.lha
/
MidiDev
/
library.doc
< prev
next >
Wrap
Text File
|
1986-11-20
|
31KB
|
766 lines
MIDI LIBRARY
==== =======
WHAT IS THE MIDI LIBRARY?
The MIDI library is a standard Amiga disk-based library that
provides multitasking access to MIDI resources such as the serial port.
The library is based around a "copying" message system so that a single
message sent in can cause several messages to be sent back out. Included
as part of the library are tasks that convert serial I/O to MIDI messages
which can be sent to any task that cares to listen. This provides for
unlimited sharing of the serial port by MIDI applications.
The need for such a service seems obvious in a multitasking
environment. Without this sort of interface, two tasks trying to receive
MIDI data from the serial port at the same time will exclude each other
from working correctly. Neither of them will get all the data they
seek. By using the MIDI library this problem is eliminated because there
is only one task actually receiving data from the serial port. Data is
then distributed to all who are waiting for it.
A similar problem occurs with transmitting. MIDI is based around a
message system. Each message contains a certain number of bytes and must
be received intact to make sense to the receiver. If two tasks try to
transmit data to the serial port at the same time it is likely that the
partial messages from each stream will merge producing disastrous
results. The MIDI library handles this by keeping messages intact, and
again only one task is actually sending data to the serial port.
The MIDI library is intended for developers writing MIDI
applications to use with their products. The library takes care of all
the grunginess of transferring MIDI data between your applications and
the serial port. Instead of getting an unformatted stream of bytes your
programs receive complete messages as asynchronous events.
The MIDI library is not SoundScape! It does NOT contain a sequencer
or sound sampler. It is merely a message distributor.
Features:
* Unlimited MIDI message merging.
* Provides an interface for multiple MIDI applications to access
the serial port without conflict.
* Versatile MIDI filtering including message, channel, controller,
and SysEx ID number filtering.
* Exists as a standard disk-based Amiga library less than 10K in
size.
* Supports the full MIDI message specification including system
exclusive.
* Transfers MIDI packets using the Amiga Executive message system.
* Almost entirely written in Assembly.
OVERVIEW
Tasks wishing to perform MIDI I/O create source and destination
nodes. These points where messages are sent out and received from.
Sources and destinations are then routed together. Any number of routes
can be connected to a source or destination. In addition to piping
information along, a route processes MIDI data. The following operations
can be performed by a route in any combination:
Message filtering
Channel filtering
Channel offset
Note offset
Controller number filtering
Sys/Ex id number filtering
In addition to nodes that the applications create there are
"resident" nodes that launch built-in tasks. Currently the only built-in
tasks are for transmitting and receiving MIDI messages to and from the
serial port.
LIBRARY
Before using the MIDI library it must be opened like any other Amiga
library. The global symbol for the library base is "MidiBase".
MidiBase = OpenLibrary ("midi.library",1L);
The current version number is 1. This will eventually be set to the
standard value for KickStart 1.2, which is 33, to avoid version number
confusion.
When done using the library you should close it:
CloseLibrary (MidiBase);
SOURCES AND DESTINATIONS
The MIDI source and destination nodes are the terminals for MIDI
communication. Messages are placed at a source to be distributed to
destinations through whatever routes are in place.
Nodes can be either public or private. Private nodes can only be
accessed by the task that creates them. Public nodes are available to
everyone. This way an application can perform it's own private
communications or can be made available for routing in a patch bay.
The MIDI source is defined in "midi/midi.h" is follows:
struct MSource {
struct Node Node;
struct Image *Image;
struct MinList RPList;
APTR UserData;
};
where
Node
is an instance of an Exec Node used for linking public
nodes into the library's source list. The ln_Name field
is set for public nodes to the name supplied by the
caller. ln_Type is set to NT_MSOURCE for user MSource's
or NT_PERMMSOURCE for resident MSource's.
Image
is a pointer to an Intuition Image structure. The image
may be used by graphics-based patch bay applications. It
is only used for public nodes. A NULL here indicates that
the creator of this node does not have an Image for it;
patch bay programs may deal with that as they see fit.
RPList
is a linked list of MRoutePtr structures. Each member of
the list is attached to an MRoute structure. Scanning
this list allows you to determine what other nodes are
connected to this one.
UserData
a generic pointer to any user extension data. For
resident nodes this points to an instance of an MTaskInfo
structure.
There are three functions dealing with MSource's:
CreateMSource (name,image)
allocates, initializes, and returns a pointer to a new
MSource structure. If name is non-NULL the MSource will
be considered public and will be linked into the library's
source list. Otherwise it will be considered private.
DeleteMSource (source)
removes and frees a source created by CreateMSource().
FindMSource (name)
looks for a public MSource with the specified name. If it
is found a pointer to it is returned.
The MIDI destination is defined in "midi/midi.h" is follows:
struct MDest {
struct Node Node;
struct Image *Image;
struct MinList RPList;
struct MsgPort *DestPort;
APTR UserData;
};
where the fields differ from an MSource
Node
is the same as in an MSource except ln_Type is set to
NT_MDEST for user MDest's or NT_PERMMDEST for resident
MDest's.
DestPort
is a pointer to an Exec MsgPort that is automatically
created. This is where MIDI messages will arrive. The
port flags are set to PA_SIGNAL so you can Wait() for
something to arrive. You don't GetMsg() from this port,
however. See the messages section for the proper way to
read messages.
As with MSource there are three functions dealing with MDest's:
CreateMDest (name,image)
allocates, initializes, and returns a pointer to a new
MDest structure. If name is non-NULL the MDest will be
considered public and will be linked into the library's
destination list. Otherwise it will be considered private.
DeleteMDest (source)
removes and frees a source created by CreateMDest(). Also
any unread messages will be freed.
FindMDest (name)
looks for a public MDest with the specified name. If it
is found a pointer to it is returned.
Most of the fields within the node structures can be ignored by
casual users since they are managed by the library. You are responsible
for deleting any nodes you create.
ROUTES
Routes are the cables that connect sources to destinations. Nodes
can have any number of routes connected to them and routes may be
connected in parallel. Additionally routes process MIDI messages as they
are transferred along them.
The route structure and related structures are defined in
"midi/midi.h" is follows:
The MRoute structure is the actual route structure managed by the
library. You probably need not be concerned with it's contents.
struct MRoute {
struct MSource *Source;
struct MDest *Dest;
struct MRoutePtr SRoutePtr, DRoutePtr;
struct MRouteInfo RouteInfo;
};
where
Source
points to the source end or input to this route. If is
NULL, then the source no longer exists.
Dest
points to the destination end or output of this route. If
is NULL, then the destination no longer exists.
SRoutePtr
is an instance of an MRoutePtr structure. This is the
node that is linked into the source's RPList.
DRoutePtr
is an instance of an MRoutePtr structure. This is the
node that is linked into the destination's RPList.
RouteInfo
is an instance of an MRouteInfo structure. This is a copy
of data supplied by the user.
The MRoutePtr structure is used to link routes to nodes. It is
primarily an internal structure.
struct MRoutePtr {
struct MinNode node;
struct MRoute *Route;
};
where
node
is an instance of an Exec MinNode structure.
Route
points to the MRoute structure containing this MRoutePtr
structure.
You initialize an MRouteInfo structure before creating a route to
indicate what sort of processing the route is to perform.
struct MRouteInfo {
UWORD MsgFlags;
UWORD ChanFlags;
BYTE ChanOffset;
BYTE NoteOffset;
struct RIMatch SysExMatch;
struct RIMatch CtrlMatch;
};
where
MsgFlags
are flag bits indicating which messages are to be
supported by this route. "midi/midi.h" contains MMF_
constants which may be ored together. A value of -1
indicates all message types.
ChanFlags
are flag bits indicating which channels for channel
messages are to be supported by this route. The least
significant bit corresponds to MIDI channel 1, the most
significant bit corresponds to MIDI channel 16. A value
of -1 indicates all channels.
ChanOffset
is a signed offset to be applied to all channel messages
passed through this route. If the resulting channel is
invalid the message is not sent.
NoteOffset
is a signed offset to be applied to note on and off
messages. It is half steps. If the resulting note number
is invalid the message is not sent.
SysExMatch
is an instance of an RIMatch structure. It allows you to
specify up to three System Exclusive ID numbers to pass
through this route. If left unset, all will be passed.
To use this the MMF_SYSEX bit must be set in MsgFlags.
CtrlMatch
is an instance of an RIMatch structure. It allows you to
specify up to three controller numbers to pass through
this route. If left unset, all will be passed. To use
this the MMF_CTRL bit must be set in MsgFlags.
The RIMatch structure is used to specify up to three values to match.
struct RIMatch {
UBYTE count;
UBYTE match[3];
};
where
count
indicates the number of values in the match list. If it
is 0, all values will be passed.
match
contains count values to match.
In order to create a route you need to have a source pointer, a
destination pointer, and a properly filled out MRouteInfo structure. The
contents of your MRouteInfo is copied to the created route so you do not
need to preserve it after creation.
There are several routines dealing with route management. First a
note about public nodes. Since public nodes are available to other tasks
besides the creator there needs to be a way to insure the validity of a
given node between the time that someone finds it and the time that
someone attempts to route to it. There is a pair of routines to lock the
node lists to prevent a node from being removed while you are dealing
with it. Additionally there are some special routing functions for
dealing with public nodes that lock the lists for you. Thus, there is
route creation function for every permutation of public and private nodes.
source dest function
private private CreateMRoute
private public MRouteSource
public private MRouteDest
public public MRoutePublic
CreateMRoute (source,dest,routeinfo)
allocates and links a route into the specified source and
destination nodes. The data pointed to by routeinfo is
copied to the new route structure. This is primarily used
when both source and destination are private.
MRouteSource (source,destname,routeinfo)
routes a source to a named public destination.
MRouteDest (sourcename,dest,routeinfo)
routes a named public source to a destination.
MRoutePublic (sourcename,destname,routeinfo)
routes a named public source to a named public destination.
The remaining routines deal with modifying and deleting routes
ModifyMRoute (route,newrouteinfo)
copies the contents of the new MRouteInfo structure to the
specified route.
DeleteMRoute (route)
unlinks and frees a route.
You are responsible for deleting any routes you create even if the
nodes it connected have been deleted. You should not delete or modify
anyone else's routes.
MESSAGES
The messages passed between nodes are UBYTE arrays in the form of
standard MIDI messages. There is no restriction on alignment since they
are bytes. Each message contains a status byte as its first byte and any
data bytes associated with that message. With the exception of system
exclusive, all messages are from 1 to 3 bytes in length. A given status
byte always has the same number of data bytes.
System exclusive messages can be any length. Also, the last byte in
a system exclusive message is the EOX status byte used as a terminator
(like '\0' in a C string).
When a message arrives at your destination's MsgPort, it is not in a
form that you can read directly. Picture the MDest as a mailbox. The
message that arrives is in an envelope or packet that can be discarded.
When you discover that MDest->DestPort has become non-empty you must call
GetMidiMsg() rather than GetMsg() since this will take care of opening
the envelope and throwing it away for you and give you a readable
message. Likewise, PutMidiMsg() places a message in an envelope and
posts it.
These functions deal with receiving messages:
GetMidiMsg (dest)
gets the first MIDI message available at a destination and
returns a pointer to it. If none are available NULL is
returned. Any message that you receive from GetMidiMsg()
should be freed with FreeMidiMsg(). See below for an
example message reader.
FreeMidiMsg (msg)
frees a message received from GetMidiMsg().
These functions deal with sending messages:
PutMidiMsg (source,msg)
places a message at a source for distribution. Upon
return you may recycle you msg buffer since its contents
has been copied to anyone who's listening. This routine
assumes that you have constructed a valid MIDI message as
described above. Invalid or undefined messages are
ignored, however an unterminated system exclusive message
cannot be detected and runs the risk of crashing the
machine.
PutMidiStream (source,fillbuffer,buf,bufsize,cursize)
converts an unformatted data stream into MIDI messages
which are then sent by calling PutMidiMsg(). See the
function documentation for proper usage.
These functions give you information about specific messages:
MidiMsgType (msg)
returns an MMF_ flag indicating the type message. It
returns 0 for invalid or undefined messages.
MidiMsgLength (msg)
returns the number of bytes in a message. For
non-exclusive messages this is the status byte + data
bytes. For system exclusive messages it is status byte +
data bytes + EOX. 0 is returned for invalid or undefined
messages.
Some additional notes about messages:
EOX is not considered a valid message and is only used to terminate
system exclusive messages. It will be ignored if sent with
PutMidiMsg()
Trivial messages are ignored. Trivial messages are those that
contain no useful data. An example is a system exclusive message
that contains no data.
Note on messages with velocity == 0 are considered note off
messages. MidiMsgType() will return MMF_NOTEOFF rather than
MMF_NOTEON for these.
Controller numbers 122-127 are reserved for MIDI mode changes and
are treated as such. MidiMsgType() will return MMF_MODE rather than
MMF_CTRL for these.
As of this writing MIDI Time Code is not supported due to lack of
documentation. As soon as I get proper specs, I'll add it.
Here is a code fragment showing the recommended MIDI message receive
technique:
domidi(dest)
struct Dest *dest;
{
UBYTE *msg;
for (;;) {
Wait (1L << dest->DestPort->mp_SigBit);
while (msg = GetMidiMsg (dest)) {
/* process the message */
.
.
.
/* then free it */
FreeMidiMsg (msg);
}
}
}
DO NOT WAIT FOR ACTIVITY LIKE THIS:
while (!(msg = GetMidiMsg(dest))) ;
unless you really want your Amiga to lock up.
INNER WORKINGS
This section is for the more adventurous MIDI programmer. The
information here is necessary for authors of patch bay or other route
managing applications.
MidiBase
The structure of the library base is defined in "midi/midibase.h".
struct MidiBase {
struct Library libnode;
struct List SourceList, DestList;
struct SignalSemaphore ListSemaphore;
struct SignalSemaphore RouteSemaphore;
BPTR SegList;
APTR SysBase, DosBase;
};
where
libnode
is the standard Amiga library node
SourceList
is an Exec linked list containing all public MSource nodes.
DestList
is an Exec linked list containing all public MDest nodes.
ListSemaphore
is a SignalSemaphore for locking the source and
destination lists. This is the semaphore used when you
call LockMidiBase() or UnlockMidiBase(). Exclusive access
to lists is required when managing or scanning either the
source or destination list. It is not required for
message propagation. It is required for route management
only when a public node is involved.
RouteSemaphore
is a SignalSemaphore for locking the route system. The
routines LockMRoutes() and UnlockMRoutes() will use this
semaphore when implemented. Exclusive access to the
routing system is required for message propagation and
route management. It is not needed for managing or
scanning the node lists. If necessary you may call
ObtainSemaphore() (and of course ReleaseSemaphore()) with
a pointer to this structure to examine the routes. Use
this with care since it blocks message propagation.
SegList
is a BPTR to the segment list for the library.
SysBase & DosBase
are the the library's pointers to Exec and Dos.
These routines are useful for examining the base:
LockMidiBase()
Gains exclusive access to the source and destination
lists. Use of this will block anyone else from managing
nodes or scanning the lists. Messages propagation is not
blocked, however. Calls may be nested but each call must
be matched with a call to UnlockMidiBase().
UnlockMidiBase()
Relinquishes exclusive access to the source and
destination lists.
There currently are no routines for locking the route system. You
may however Forbid() or ObtainSemaphore(&MidiBase->RouteSemaphore) to
lock the routes. You shouldn't be very likely to need to do this,
however, and if you do it should only be to read the route lists not to
modify anything. A patch bay application should not use this approach to
keep track of its routes. Instead it must maintain its own list or
routes. As long as everyone observes the rule of modifying only their
own structures there shouldn't be any trouble.
Resident Nodes
As noted before there are some nodes that are always present. They
are created by the library when it is first loaded. These nodes are
attached to built-in tasks (actually processes) that are launched when
the nodes are routed to. There are currently only two resident nodes:
one for receiving from the serial port and one for transmitting to the
serial port.
MidiIn source Launches a task called MidiIn running at +10
priority when it is first routed to. It receives
data from the serial port and converts it to MIDI
messages which are posted to its MSource node.
Anyone with destinations routed to this node will
receive MIDI messages from it.
MidiOut dest Launches a task called MidiOut which also runs at
+10 priority. It is responsible for receiving
messages from it's MDest node and sending them to
the serial port. Any number of MSource's can be
routed to it without fear of mangled data; messages
are merged properly. Each message sent to it will
be transmitted to the serial port in the order
received.
NOTE: The "serial.device" has a hard time receiving MIDI data at
full speed, even with the SERF_RAD_BOOGIE flag set. It apparently loses
a byte or two on larger transfers (>4K). Perhaps someone knowledgeable
in these matters might write a streamlined MIDI serial device capable of
handling full speed MIDI data. For now, it is strongly recommended that
you make sure that you get what you expect when reading system exclusive
messages from MidiIn. Use whatever error detection methods are
appropriate for the data being transferred (like dump sizes or checksums).
Resident nodes can be identified by a node type value (in ln_Type)
of NT_PERMMSOURCE or NT_PERMMDEST. Resident nodes have an additional
MTaskInfo structure attached to them that is pointed to by the UserData
field in the MSource or MDest structure. The MTaskInfo structure is
defined in "midi/midibase.h".
struct MTaskInfo {
char *Name;
WORD Pri;
void (*Entry)();
UWORD Stack;
UWORD Sources;
struct MNodeInfo *SourceList;
struct Dests;
struct MNodeInfo *DestList;
struct SignalSemaphore Semaphore;
UWORD UsageCount;
struct MsgPort *TaskPort;
BPTR Segment;
};
where these fields are defined by the task's code:
Name
points to the name of the task.
Pri
is the task's priority.
Entry
is the start of the task's code.
Stack
is the size of stack to allocate for the task.
Sources
is the the number of MSource nodes defined for this task.
SourceList
points to an array of MNodeInfo structures containing the
definition of this task's MSource nodes.
Dests
is the the number of MDest nodes defined for this task.
DestList
points to an array of MNodeInfo structures containing the
definition of this task's MDest nodes.
and these fields are managed by the library (don't mess with them)
Semaphore
is a signal semaphore for locking this MTaskInfo
structure. This is only used when launching or shutting
down this task.
UsageCount
is the number of accessors to this task. If it is
non-zero the task is running otherwise it is dormant.
TaskPort
points to the message port used to communicate with the
task.
Segment
is a BPTR to a fake segment list necessary for
CreateProc(). The segment contains a jump instruction to
the startup code for the task. The task's code is
actually part of the library's segment list. This might
be considered making use of undocumented features, but as
long as no one tries to UnloadSeg() this segment (and no
one should) there shouldn't be any trouble.
The MNodeInfo structure referred to above is used to define the
resident source or destination nodes attached to this task. Tasks are
not restricted to just one resident node, but that is the current usage.
This structure is also defined in "midi/midibase.h".
struct MNodeInfo {
char *Name;
struct Image *Image;
APTR Node;
};
where
Name
points to the name of the node. This shouldn't ever be
NULL.
Image
points to an Intuition Image structure for this node.
This may be NULL.
Node
points to the node allocated for this MNodeInfo structure
so that the task can find it easily.
CONCLUSION
Hopefully this document has provided you with an understanding of
the MIDI library and will enable you to make use of its features in your
own MIDI applications. Our own MIDI applications have now been
retrofitted to make use of the library. I was amazed at how simple a
task this was and how much code I was able to dispose of.
At this point, I don't consider the library to be a finished
product. It is functional though, and I present it now with the hopes
that it will be used by other MIDI developers with whose help I would
like to see this library mature into a standard for the Amiga. This is
the sort of thing that can make the Amiga a powerful tool for the
musician.
In addition to library revisions, the MIDI tools need to be enhanced.
First, there needs to a graphics-based Patch Bay application for
creating and managing routes to public nodes. Unlike SoundScape I
decided this should be outside the library so that it need not be present
all the time. My vision in this area seems to be limited, however. What
I picture is a window with the Images provided by public nodes appearing
on each side of the window. The user can drag lines from source to
destination to create routes and edit a requester to modify them.
Perhaps patch files could be saved, too. This really needs some
creativity. There is also a risk of having it resemble SoundScape too
much.
Next, there should probably be a program to bridge this library to
SoundScape so that no one complains about incompatibility problems
between this library and what already exists. I have a number of gripes
about SoundScape (part of what prompted me to write the MIDI library in
the first place) but this is not the place to go into them. I only
mention this now because SoundScape does some similar functions and might
be considered by some to be the standard already. Also SoundScape does
have a fairly good sequencer and I'd hate to exclude anyone from using
the library just because they like SoundScape.
If you have any comments on any of this (and hopefully you will)
here is how I can be reached.
Bill Barton
1111 El Sur Way
Sacramento, CA 95864
(916) 487-9472
BIX: peabody
Delphi: BBARTON