Copyright (c) Hyperion Entertainment and contributors.
Camd Library
Contents
- 1 Camd Library
- 2 Goals
- 3 Operating System Requirements
- 4 SDK Requirements
- 5 The MIDI System
- 6 Creating a MidiNode
- 7 Establishing Links
- 8 MIDI Messages
- 9 How MIDI Data is received
- 10 How MIDI Data is Sent
- 11 System Exclusive
- 12 Filters
- 13 MIDI Timestamps
- 14 Interfacing to Hardware
- 15 Function List
- 16 Credits
- 17 Example code
- 18 Further Resources
Camd Library
CAMD is an Amiga shared library which provides a general device driver for MIDI data, so that applications can share MIDI data with each other in real-time, and interface to MIDI hardware in a device-independent way.
Goals
The goals of CAMD are:
- To encourage development of music software and synchronized multimedia applications by providing a working driver to the public.
- To enable music and multimedia applications to operate concurrently and exchange MIDI data in real-time.
- To improve on existing "freeware" drivers by enhancing performance, by providing hooks for getting raw input, and by simplifying the interface as much as possible.
Operating System Requirements
CAMD library functions identically since Workbench 1.3. However, this is a complete re-write and contains no code from the original versions. Camd is a contribution to AmigaOS and is not part of the core OS. It can be found at http://www.os4depot.net/share/driver/misc/camd.lha
SDK Requirements
Programming for CAMD requires some additional includes to be added to the SDK. When the camd.library install script is run, it will offer to add these files if it sees an SDK: assign in your system. The CAMD includes are added to SDK:local/common/include/, and the autodoc is copied to SDK:local/documentation/autodoc/.
The MIDI System
The MIDI distribution system is based on the idea of "linkages" (called MidiLinks) between applications. Each application or hardware driver can establish linkages to other applications or hardware drivers. Multiple links can be established to a single source, so that more than one application can see the MIDI stream coming out of a hardware port or application output. Similarly, more than one application can send a MIDI stream to a single hardware port or application input. The ability to have one application send data to another allows "pipelining" of the MIDI stream, for example connecting an interactive composing program to a sequencer and running both concurrently. Note that there is no requirement that the data sent actually be valid musical data -- it is possible for a pair of applications to set up a private linkage, and communicate anything they want, as long as it follows the syntactic rules of MIDI. However, it is suggested that such linkages be hidden from the user using a special bit which makes a linkage private.
Creating a MidiNode
Each MIDI application must create a MidiNode. This structure is used as a central dispatch point for incoming and outgoing messages, and holds all of the application-specific information, including:
- location and size of input buffers.
- the name of the application
- the icon to be used for the application in the patch editor.
- the address of the task to signal when messages are received, and the signal bit to use.
MidiNodes are created by
ICamd->CreateMidiA(struct TagItem *TagsList)
or by
ICamd->CreateMidi(Tag tag1,...).
For both forms of CreateMidi, tags may include:
- MIDI_Name (STRPTR)
- name of the node, usually the program name
- MIDI_SignalTask (struct Task *)
- Task to be signaled, defaults to current task
- MIDI_RecvHook (struct Hook *)
- the hook to be called when new messages arrive
- MIDI_PartHook (struct Hook *)
- the hook to call when linkages are added or removed
- MIDI_RecvSignal (int8)
- the signal to send when messages arrive
- MIDI_PartSignal (int8)
- the signal to send when linkages are added or removed
- MIDI_MsgQueue (uint32)
- the desired size of incoming message queue
- MIDI_SysExSize (uint32)
- the desired byte size of the System Exclusive buffer
- MIDI_TimeStamp (uint32*)
- pointer to the desired MIDI time stamp source.
- MIDI_ErrFilter (uint16)
- the desired error filter for this node. see camd.h
- MIDI_ClientType (uint16)
- the desired client type for this node. see camd.h
- MIDI_Image (struct Image *)
- Image (suggested 32X32) for this node.
See sdk:local/common/include/midi/camd.h for more information.
Establishing Links
One of the nice thing about MidiLinks is that they can be established even if the other application or hardware driver hasn't been loaded yet. This is because a MidiLink does not connect directly to the other application, but rather it connects to a "meeting place" or "rendezvous point" for MidiLinks called a Cluster. Each cluster is referred to by name. For example, if I establish an output link to the cluster "foo", and someone else establishes an input link to that same cluster, then any data that my application sends to that link will be received by that other application. If a third application creates an input link to "foo", then it will also receive the data, whereas if another application creates an output link to "foo" then it's MIDI data will be merged with mine, and distributed to all the input links.
Some cluster properties:
- The first attempt to link to a cluster creates the cluster, and the last link to leave deletes it.
- Each sender link to a cluster is merged with all the other senders.
- Each receiver link to a cluster gets a copy of what all the other receivers get.
In addition, there are some tips for managing clusters:
Cluster names are case sensitive, and must be enclosed in quotes if there are spaces in the cluster name.
Most MIDI interfaces use .in or .out at the end of each name to indicate direction. This is NOT a requirement, and should not be used by an application to restrict the choice of nodes available.
Participants: The library function MidiLinkConnected() can be used to check a cluster to see if there are any linkages of the opposite type. For example, a sender could check to see if anybody is listening or if they are just talking to vacuum. Similarly, a receiver could check to see if there are any senders. In addition, you can request to be notified (via signal) whenever the participants in a cluster change. This feature is primarily used by the hardware interface in the library itself -- it allows a driver to be shut down (and freeing the hardware resources) when there are no applications using it.
Cluster Comments: For purposes of building user interface to select clusters, each link to a cluster can specify a "comment", up to 34 characters long, which describes what this cluster actually is. However, since there can only be one comment for a cluster, the comment from the first link is the one used.
One of the advantages of the cluster model is that applications can be started up in any order and still work. The following are suggestions as to how applications should handle linkages:
- An application should allow the user to see a list of existing clusters (which CAMD can provide), or allow the user to type in a new cluster name. (camdtools has ReAction based "clist.c" to help with this)
- The application should save the current linkages either in the applications "settings", or embedded in the document or performance file (perhaps using an IFF chunk). When the application is restarted (or that performance loaded or whatever) the application should then automatically establish the links specified.
If every application does this, then it will be easy for the user to set up the same configuration of linkages as they did last time, even if they launch their applications in a different order. Even if the applications are invoked via a script, the network of applications can come into existence automatically.
To connect our MidiNode to a cluster,
struct MidiLink *ICamd->AddMidiLinkA(struct MidiNode *myNode, int32 type, struct TagItem *TagsList);
or
struct MidiLink *ICamd->AddMidiLink(struct MidiNode *myNode, int32 type, Tag tag1, ...);
Supported tags include:
- MLINK_Name (STRPTR)
- name for this link
- MLINK_Location (STRPTR)
- Cluster to connect to, Case sensitive
- MLINK_ChannelMask (uint16)
- Mask of which MIDI channels to listen to, defaults to ~0
- MLINK_EventMask (uint16)
- Mask of which types of MIDI events to listen for, defaults to ~0
- MLINK_UserData (CPTR)
- User defined
- MLINK_Comment (STRPTR)
- highest priority link will comment the cluster
- MLINK_PortID (uint8)
- Value to copy to any msgs arriving through this link
- MLINK_Private (BOOL)
- if TRUE, link requests to be hidden
- MLINK_Priority (int8)
- priority of this MidiLink
- MLINK_SysExFilter (uint32)
- data is 3 1 byte SysEx ID's to filtter with
- MLINK_SysExFilterX (uint32)
- data is one 3 Byte SysEx ID to filter with
- MLINK_Parse (BOOL)
- If true, CAMD will parse incoming stream into MIDI Messages
- MLINK_ErrorCode, (uint32*)
- points to an error code buffer
MIDI Messages
Each MIDI message sent or received is contained in a MidiMsg structure. This 8-byte structure contains a timestamp, the actual MIDI bytes (up to 3) and a link number (so that applications which have several input links can determine which one received the message). Note that since the message is so small, the entire message is copied when MIDI data is transferred, rather than passing pointers around.
From camd.h:
typedef union { ULONG l[2]; UBYTE b[4]; } MidiMsg; /* MidiMsg field definitions */ #define mm_Msg l[0] #define mm_Time l[1] #define mm_Status b[0] #define mm_Data1 b[1] #define mm_Data2 b[2] #define mm_Port b[3] #define mm_Data b
How MIDI Data is received
MIDI applications can be either task-based or callback based. A task-based application uses a signal to wait for incoming MIDI data. Once the signal is received, the application can call GetMidi() to actually look at what was received. All incoming messages are queued, and there is a separate queue for system exclusive messages (which can be quite long).
Each incoming MIDI event is both timestamped and marked with the linkage number (settable by the application) from the link that it came in on. Some people have questioned whether a task can respond fast enough to incoming MIDI data to meet professional standards of timing accuracy. Our experimentation has determined that a high-priority task (say, 30 or so), can meet these requirements, even when the disk drive is running.
uint32 SignalMask = (1UL << CamdSigBit) | Other_bits_we_care_about; uint32 Signals = IExec->Wait(SignalMask); if(Signals & (1UL << CamdSigBit)) { while(ICamd->GetMidi(my_midi_node, &newMessage)) { //newMessage is a Midi Message for us. } }
However, if the application's handling of MIDI is very fast, it may be better to use a callback. The callback occurs in the context of the sender, so it is best to be quick so as not to slow down the sending task. The callback is invoked through a standard Hook structure. Using a callback avoids the overhead of task switching, and can allow improved overall performance.
Note |
---|
The sender will always be a task, and not an interrupt, since the actual hardware drivers are serviced via a task. |
How MIDI Data is Sent
Sending MIDI data is very simple, mainly a matter of filling out a MidiMsg structure and calling PutMidi(). Note that if the receive buffer is full, then the function will fail, rather than waiting for the receive buffer to empty.
MidiMsg mm; mm.mm_Status = MS_NoteOn | (chan-1); // for MIDI channel 1 to 16 mm.mm_Data1 = MiddleC; // may be any note from 0 to 127 mm.mm_Data2 = DefaultVelocity; // may be from 1 to 127 // Now we have a "Note On" event, let's play it! ICamd->PutMidi(outLink, mm); IDOS->Delay(25); // wait a half-second mm.mm_Status = MS_NoteOff | (chan-1); // for MIDI channel 1 to 16 mm.mm_Data1 = MiddleC; // may be any note from 0 to 127 mm.mm_Data2 = DefaultVelocity; // may be from 1 to 127 // Now we end the note ICamd->PutMidi(outLink, mm);
We have separate events for turning notes on and off. These are the same as pressing, then later releasing a Middle C key on a piano. the Data1 byte will determine which key is pressed or released, and Data2 will tell how hard the key was hit.
System Exclusive
For those of you not familiar with MIDI, system exclusive messages (called SysEx for short) are a kind of escape hatch in the MIDI spec which allows developers to define their own messages. Unlike other MIDI events which are limited to 3 bytes or less, SysEx messages can be any length. In CAMD, SysEx messages are handled by placing the header of the message (the first three bytes) in the regular receive queue as a MidiMsg, and placing the full message in a separate buffer. The receiver can look at the first three bytes, and decide whether they want to read the rest by calling GetSysEx() or throw it away by calling SkipSysEx(); Sending SysEx is done by calling the function PutSysEx().
Filters
To reduce the load on the system, MIDI data can be filtered so that only useful data shows up in the application's input buffer. Each MidiLink has a set of filter bits, can allow incoming messages to be ignored (not placed in the receive queue).
The first set of filter bits correspond to the 16 MIDI channels. (For those of you unfamiliar with MIDI, the low nybble of the first MIDI byte contains the channel number). If the incoming MIDI messages is on a channel which does not correspond to one of the bits set in the filter word. the message is skipped.
A second filter is based on the type of the event, of which CAMD breaks up into 14 categories:
- note on/off
- program change
- pitch bend
- controller change MSB
- controller change LSB
- controller change Boolean switch
- controller change single byte
- controller parameter change
- undefined controllers
- mode change messages
- channel after touch
- polyphonic after touch
- system real-time messages (MIDI clock, MTC Quarter Frame)
- system common messages (Start, Stop, etc)
- system exclusive messages
In addition, there is a special filtering system for SysEx messages which allows them to be filtered based on the first byte or the first three bytes after the SysEx header byte. If the first byte only is used, then three different filters can be specified. If the first three bytes are used, then only one filter can be specified.
MIDI Timestamps
Each incoming MIDI message may be timestamped, however, you must supply a source of timing information (the recommended method is to use RealTime Library, however many other timestamp sources are possible). You can tell CAMD to use a particular timing source. The MidiNode contains a pointer (of type LONG *), which may be pointed to the source of timestamps. Whenever a MidiMsg is received, the longword that is pointed to by this pointer is used as the current time, and copied into the MidiMsg. Normally, what you would want to do is point this pointer at a longword that was continually being updated. Note that in this fashion, your application can have timestamps in any format it wants, since CAMD never looks at the timestamp field once it is set. One important point is that the timestamp is set at the time the message is placed into the receiver's buffer. It would have been nice to timestamp the messages at the interrupt time of the first MIDI status byte, however this would have made the cluster model of distribution impossible. Application which desire ultimate accuracy should probably adjust the timestamp to compensate for the length of the MidiMsg.
CAMD ignores timestamps internally.If you don't require them, feel free to ignore timestamp initialization completely.
Interfacing to Hardware
Most hardware drivers live in the directory DEVS:midi. They can be created by third-party developers, and are fairly simple. CAMD maintains a task for each input MIDI stream. This task is responsible for reading bytes from the hardware, parsing them into MidiMsgs, and sending them to a cluster.
Under AmigaOS 4.x we no longer support CAMDPrefs. All drivers found in DEVS:midi will be run when camd is started. Do NOT put a driver into DEVS:midi unless you want it to be run.
There is a driver for USB based MIDI devices. This installs as a USB function driver, and does not reside in DEVS:Midi. It will automatically start and mount every class compatible MIDI device as soon as each device connects to the USB stack.
Function List
More details on all functions are in the autodoc at SDK:Local/documentation/autodoc/camd.doc.
struct MidiLink *ICamd->AddMidiLink(struct MidiNode *, int32 type); void ICamd->CloseMidiDevice(); /*Not supported in AmigaOS 4.x*/ struct MidiNode *ICamd->CreateMidi(Tag tag1, ...); void ICamd->DeleteMidi(struct MidiNode *); void ICamd->EndClusterNotify(struct ClusterNotifyNode *); struct MidiCluster *ICamd->FindCluster(STRPTR name); struct MidiNode *ICamd->FindMidi(STRPTR name); void ICamd->FlushMidi(struct MidiNode *); BOOL ICamd->GetMidi(struct MidiNode *, MidiMsg *); uint32 ICamd->GetMidiAttrsA(struct MidiNode *, struct TagItem *); uint8 ICamd->GetMidiErr(struct MidiNode *); uint32 ICamd->GetMidiLinkAttrsA(struct MidiLink *, struct TagItem *); uint32 ICamd->GetSysEx(struct MidiNode *, uint8 *buf, uint32 len); APTR ICamd->LockCAMD(uint32); BOOL ICamd->MidiLinkConnected(struct MidiLink *); int16 ICamd->MidiMsgLen(uint32); int16 ICamd->MidiMsgType(MidiMsg *); struct MidiCluster *ICamd->NextCluster(struct MidiCluster *); struct MidiLink *ICamd->NextClusterLink(struct MidiCluster *, struct MidiLink *, int32); struct MidiNode *ICamd->NextMidi(struct MidiNode *); struct MidiLink *ICamd->NextMidiLink(struct MidiNode *, struct MidiLink *, int32); ICamd->OpenMidiDevice(); /*Not supported in AmigaOS 4.x */ void ICamd->ParseMidi(struct MidiLink *, const uint8 *buf, uint32 len); void ICamd->PutMidi(struct MidiLink *, uint32); void ICamd->PutMidiMsg(struct MidiLink *, MidiMsg *); void ICamd->PutSysEx(struct MidiLink *, uint8 *); uint32 ICamd->QuerySysEx(struct MidiNode *); void ICamd->RemoveMidiLink(struct MidiLink *); int32 ICamd->RethinkCAMD(void); BOOL ICamd->SetMidiAttrs(struct MidiNode *, Tag, ...); BOOL ICamd->SetMidiLinkAttrs(struct MidiLink *, Tag, ...); void ICamd->SkipSysEx(struct MidiNode *); void ICamd->StartClusterNotify(struct ClusterNotifyNode *); void ICamd->UnlockCAMD(APTR); BOOL ICamd->WaitMidi(struct MidiNode *, MidiMsg *);
Note |
---|
OpenMidiDevice and CloseMidiDevice are not supported. Camd simply opens all devices found in DEVS:Midi |
Credits
CAMD has a long and convoluted history. It was originally created at Carnegie-Mellon university by Roger B. Dannenberg and Jean-Christophe Dhellemmes. After that is was worked on by Bill Barton, and later by Darius Taghavy, followed by Carolyn Scheppner. The final form of the design was conceived by David Joiner (a.k.a. Talin) and implemented by Joe Pearce.
The AROS version was written from scratch by Kjetil Matheussen. This was ported to AmigaOS 4.0 by Davy Wentzler. CAMD for AmigaOS is currently maintained by Lyle Hazelwood.
Example code
Following is a complete example of "MidiThru". This is a virtual "patch cord" to connect MIDI clusters. The in and out cluster names are provided from the command line.
/* ** MidiThru.c ** Creates a link between named clusters ** 3/24/2012 Lyle Hazelwood */ #include <proto/camd.h> #include <proto/dos.h> #include <proto/exec.h> #include <stdlib.h> void bailout(char *); // our cleanup struct Library *CamdBase = NULL; struct CamdIFace *ICamd = NULL; // these don't have to be global, // but it makes cleanup easier. struct MidiNode *ournode = NULL; struct MidiLink *fromLink = NULL, *toLink = NULL; int8 midisig = -1; int main(int argc, char **argv) { MidiMsg mmsg; uint32 signal; BOOL alive = TRUE; if(argc != 3) { IDOS->Printf("Shell usage %s from to\n", argv[0]); IDOS->Printf("where from and to are named MIDI clusters\n"); return(1); } // We open camd.library and get the main interface CamdBase = IExec->OpenLibrary("camd.library", 36L); if(NULL == CamdBase) bailout("Can't open camd.library"); ICamd = (struct CamdIFace *)IExec->GetInterface(CamdBase, "main", 1, NULL); if(NULL == ICamd) bailout("Can't get CAMD interface"); // camd will use this signal when we have incoming MIDI midisig = IExec->AllocSignal(-1); if(-1 == midisig) bailout("Cant get a signal"); // We create our MidiNode here, requesting buffers as desired ournode = ICamd->CreateMidi(MIDI_MsgQueue, 2048L, MIDI_SysExSize, 10000L, MIDI_RecvSignal, midisig, TAG_END); if(NULL == ournode) bailout("Can't Create MidiNode"); // This is our input stream of MIDI messages fromLink = ICamd->AddMidiLink(ournode, MLTYPE_Receiver, MLINK_Location,argv[1], TAG_END); if(NULL == fromLink) bailout("Can't Add Input Link"); // and our output stream toLink = ICamd->AddMidiLink(ournode, MLTYPE_Sender, MLINK_Location, argv[2], TAG_END); if(NULL == toLink) bailout("Can't Add Output Link"); IDOS->Printf("Success Linking %s to %s\n",argv[1],argv[2]); // Now with all the setup finished, we copy incoming // MIDI messages back out again while(alive) { // this will run until a BREAK is received (Ctrl-C) signal = IExec->Wait(SIGBREAKF_CTRL_C | 1L << midisig); if(signal & SIGBREAKF_CTRL_C) alive = FALSE; while(ICamd->GetMidi(ournode, &mmsg)) { // Every message we get... ICamd->PutMidi(toLink, mmsg.mm_Msg); // we send back out. } } IDOS->Printf("MIDI Link from %s to %s is broken\n",argv[1],argv[2]); bailout(NULL); // return all resources. return (0); } void bailout(char *reason) { if(reason) { IDOS->Printf("%s\n",reason); } ICamd->RemoveMidiLink(toLink); ICamd->RemoveMidiLink(fromLink); ICamd->DeleteMidi(ournode); IExec->FreeSignal(midisig); IExec->DropInterface((struct Interface *)ICamd); IExec->CloseLibrary(CamdBase); if(reason) exit(-1); }
Further Resources
The official source of MIDI documentation is the "MIDI Manufacturers Association" (MMA) http://www.midi.org/
The source code, executable, and documentation for the example above, "MidiThru", is available from OS4Depot.net, along with an assortment of other midi tools and code examples. http://www.os4depot.net/share/driver/misc/camdtools.lha
An excellent online reference to all things MIDI has been provided by Jeff Glatt: http://home.roadrunner.com/~jgglatt/