home *** CD-ROM | disk | FTP | other *** search
- 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
-
-