Aros/Developer/Docs/Libraries/CAMD

Introduction
To create a link between two programs. both open camd.library, and make a node using CreateMidi. Now to communicate, just create a link to a named location. AddMidiLink. The second program does the same except it uses it's own node, and receiver. Now every MIDI event sent by program A will be received by program B. Also, any other programs that go looking for places to connect will see "uniquename" as an option. They can send to it or receive from it.

One way to get spot on playback is to convert every event time into an offset from the start of the music. Then on playback, send all events with a timestamp earlier or equal to "now", then sleep until Music_Start_Time + offset to next event.

USB will poll and find all midi devices automatically

Amiga CAMD
The original Commodore Amiga MIDI Driver (CAMD) is a shared library for AmigaOS 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. AmigaOS itself did not support MIDI until release 3.1 when Roger Dannenberg's CAMD library was adapted as the standard MIDI API. Commodore's version of CAMD also included a built-in driver for the Amiga serial port. Read more here

AmigaOS 4
Using the AROS version early on as a base, but eventually around 2012, the current version of camd is 41.1 (26.11.2016) which was a mostly closed source rewrite and the most up to date with src is on 50.8

AROS CAMD
A Camd library was rewritten w/src and is compatible was started in the early 2000s.

Preferences->Environment are the connections for the sychronization. There are two kinds of synchronization:
 * controlled by MIDI-clocks (short "clocks" in the Environment window)
 * controlled by MIDI-Time-Code (short "MTC")

MIDI-Clocks only sync the tempo between two MIDI-devices independent when it starts or stops, and MIDI-Time-Code syncs also the timepoint. The MTC is mostly used by synchronization to a special time schedule, like in Power-Point-, Video- or Diapostive presentations.

The sequencer itself in BarsnPipes needs the MIDI-IN and OUT-Tools. These tools are missing currently in the onboard version. We need the mentioned driver as bridge between the Poseidon-USB-System and the CAMD (Commodore Amiga MIDI Driver) - system. The new camd.library does not have a built-in driver for the serial-port. Just use mmp/port 0 instead. It should be more stable than the old internal driver too.

Only copy the driver(s) you use into devs:midi/. Dont have more than one type of driver for the same hardware in devs:midi/. Ie, don`t have both mmp and uaemidi (which both uses the serial-port), for example out.0, out.1, out.2, in.0, in.1, etc. does not exist anymore. The links will have the names .out.0, .out.1, .in.0, etc. instead.

MIDI playback by CAMD has always required the name of the output cluster. In the beginning it was almost always "out.0", and many programs are hard coded for that value only. The CAMD documents state that we ask the user for their desired output, and save that on a per application basis.

Modern PC Hardware unfortunately does not support the transmission speed of MIDI: 31.25 kBaud over the serial port. In addition to that, a so-called MIDI interface is needed, which converts the serial signals for MIDI. The serial transmission on the AMIGA was so nice and easy, because there the transmission speed was freely programmable.

Midi Specs
Link               Links Application --> Cluster --> Application <-- Application

Node                                       Node

Nodes
Each node is hardware, interface or application

Tags 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.

Links
Links are created between nodes

Tags 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 filter 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

A meeting of links from different applications or interfaces (in, out or both) is called a cluster. It allows messages from one link to get to other links automatically.

Link               Links Application --> Cluster --> Application <-- Application

Messages
You can use CAMD for inter-task comms all the time. Most MIDI messages are three bytes or less, but System Exclusive messages can be longer, within reason. All messages are passed between named ports

If multiple streams are sent to the same destination, they will be simply mixed together at reception. the receiver CAN tell from where each message came if it cares to look. You can also send multiple streams FROM a given port, each destination gets a copy of the messages. So mixing (merging) is easy, as is multicasting.

/* MidiMsg struct */ mm_Msg mm_Time mm_Status mm_Data1 mm_Data2 mm_Port mm_Data

GetMidi PutMidi

Filters
lower 4bits contains the channel number, if the MIDI messages is on a channel which does not match the bits set, then message is not used

what midi wants 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

Home to General Midi info

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), since the eventually there will be a "patch editor" which will allow the user to manipulate linkages without the application being aware of it.

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.

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.

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 seperate 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 reqiuirements, even when the disk drive is running.

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. (Note that the sender will always be a task, and not an interrupt, since the actual hardware drivers are serviced via a 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.

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.

System Exclusive

For those of you not familier 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 seperate 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 unfamilier 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.

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 "rendevous 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.

So the rules of clusters are as follows: -- 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 other properties of clusters:

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:


 * 1) 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. [Issue: Should the word "cluster" be used in UI?]


 * 1) 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.

(Eventually we will want to have a "performance manager" which can "snapshot" a whole network of apps, allowing the user to go back to that "state" later. This might be done using ARexx).

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 timig 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 contually 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.

Interfacing to Hardware

CAMD maintains a list of hardware drivers which are used to access serial ports, DSP boards, or any other hardware interface. A preferences file (ENV:sys/midi.prefs) is used to indicate which hardware drivers should be loaded and which ports should be activated. Note that using file notification, CAMD is capable of changing this setup at any time, even while applications are running.

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. The cluster name is specified in the preferences file, on a per-port basis.

Examples

 * 1) include 
 * 2) include 
 * 3) include 
 * 4) include 


 * 1) define TABSIZE 4

struct Library *CamdBase=NULL;

ULONG GetMidiLinkAttrs(struct MidiLink *ml, Tag tag, ...){ return GetMidiLinkAttrsA(ml, (struct TagItem *)&tag ); }
 * 1) ifndef GetMidiLinkAttrs
 * 1) endif

ULONG GetMidiAttrs(struct MidiNode *ml, Tag tag, ...){ return GetMidiAttrsA(ml, (struct TagItem *)&tag ); }
 * 1) ifndef GetMidiAttrs
 * 1) endif

struct MidiLink *GetMidiLinkFromOwnerNode(struct MinNode *node){ struct MidiLink dummy; return (struct MidiLink *)((char *)((char *)(node)-((char *)&dummy.ml_OwnerNode-(char *)&dummy))); }

void printSpaces(int level){ int lokke; for(lokke=0;lokke<level*TABSIZE;lokke++){ printf(" "); } }

void printLink_brancheNodes(struct MidiLink *midilink,int level,int maxlevel); void printLink_brancheClusters(struct MidiLink *midilink,int level,int maxlevel); void printCluster(struct MidiCluster *cluster,int level,int maxlevel);

void printNode(struct MidiNode *midinode,int level,int maxlevel){ char *nodename=NULL; struct MinNode *node;

if(level==maxlevel) return;

GetMidiAttrs(midinode,MIDI_Name,(IPTR)&nodename,TAG_END);

printSpaces(level); printf(		"%p, -%s-\n",		midinode,		nodename	);

if(level+1==maxlevel) return;

if( ! (IsListEmpty((struct List *)&midinode->mi_OutLinks))){ printSpaces(level); printf(" -OutLinks:\n"); node=midinode->mi_OutLinks.mlh_Head; while(node->mln_Succ!=NULL){ printLink_brancheClusters(GetMidiLinkFromOwnerNode(node),level+1,maxlevel); node=node->mln_Succ; }	}

if( ! (IsListEmpty((struct List *)&midinode->mi_InLinks))){ printSpaces(level); printf(" -InLinks:\n"); node=midinode->mi_InLinks.mlh_Head; while(node->mln_Succ!=NULL){ printLink_brancheClusters(GetMidiLinkFromOwnerNode(node),level+1,maxlevel); node=node->mln_Succ; }	} }

BOOL printLink(struct MidiLink *midilink){ char *linkname=NULL;

if(midilink->ml_Node.ln_Type==NT_USER-MLTYPE_Receiver || midilink->ml_Node.ln_Type==NT_USER-MLTYPE_Sender){ GetMidiLinkAttrs(midilink,MLINK_Name,(IPTR)&linkname,TAG_END); printf(			"%p, -%s-\n",			midilink,			linkname		); return TRUE; }

printf("%p, (private)\n",midilink); return FALSE; }

void printLink_brancheNodes(struct MidiLink *midilink,int level,int maxlevel){ struct MidiNode *midinode;

if(level==maxlevel) return;

printSpaces(level); if(printLink(midilink)==TRUE){ midinode=midilink->ml_MidiNode; printSpaces(level); printf(" -Owner (MidiNode): \n"); printNode(midinode,level+1,maxlevel); } }

void printLink_brancheClusters(struct MidiLink *midilink,int level,int maxlevel){ if(level==maxlevel) return;

printSpaces(level); printLink(midilink);

if(level+1==maxlevel) return;

printSpaces(level); printf(" -Cluster: \n"); printCluster(midilink->ml_Location,level+1,maxlevel); }

void printCluster(struct MidiCluster *cluster,int level,int maxlevel){ struct MidiLink *midilink;

if(level==maxlevel) return;

printSpaces(level); printf("clustername: -%s-\n",cluster->mcl_Node.ln_Name);

if(level+1==maxlevel) return;

if(!(IsListEmpty(&cluster->mcl_Receivers))){ printSpaces(level); printf(" "); printf("-Receiver links:\n");

midilink=(struct MidiLink *)cluster->mcl_Receivers.lh_Head; while(midilink->ml_Node.ln_Succ!=NULL){ printLink_brancheNodes(midilink,level+1,maxlevel); midilink=(struct MidiLink *)midilink->ml_Node.ln_Succ; }	}

if(!(IsListEmpty(&cluster->mcl_Senders))){ printSpaces(level); printf(" "); printf("-Sender links:\n"); midilink=(struct MidiLink *)cluster->mcl_Senders.lh_Head; while(midilink->ml_Node.ln_Succ!=NULL){ printLink_brancheNodes(midilink,level+1,maxlevel); midilink=(struct MidiLink *)midilink->ml_Node.ln_Succ; }	} }

int main{

APTR lock; struct MidiCluster *cluster;

CamdBase=OpenLibrary("camd.library",40L);

if(CamdBase!=NULL){

lock=LockCAMD(CD_Linkages);

cluster=NextCluster(NULL); if(cluster==NULL){

printf("No clusters available.\n");

}else{

printf("-Clusters:\n\n"); do{ printCluster(cluster,1,6); printf("\n"); cluster=NextCluster(cluster); }while(cluster!=NULL);

}

UnlockCAMD(lock); CloseLibrary(CamdBase);

}else{ printf("Could not open at least V40 of camd.library.\n"); return 1; }

return 0; }


 * 1) include 
 * 2) include 
 * 3) include 
 * 4) include 
 * 5) include 
 * 6) include 

int main(int argc, char **argv) { struct MidiNode *ournode; struct MidiLink *to; MidiMsg mmsg;

if((ournode = Camd->CreateMidi( MIDI_MsgQueue, 2048L, MIDI_SysExSize, 10000L, MIDI_Name, argv[0], TAG_END))) { if((to = Camd->AddMidiLink(ournode, MLTYPE_Sender, MLINK_Location, argv[1], TAG_END))) { mmsg.mm_Status = MS_Start; mmsg.mm_Data1 = 0; mmsg.mm_Data2 = 0; Camd->PutMidi(to, mmsg.mm_Msg);

Camd->RemoveMidiLink(to); } Camd->DeleteMidi(ournode); }

CloseLibrary(CamdBase); return 0; }

Create a midi-driver for camd.library
As its first action in the init-routine, Camd.library searches through all files in Devs:Midi/ and loads all possible midi-device drivers. For this, the drivers must of course be placed in the Devs:Midi directory.

The central point of a midi-device driver is the MidiDeviceData struct, which must be included in your binary, somewhere. Camd.library searches through your file to locate the structure. For this, there are some rules you have to follow, to let Camd.library recognize the driver as legal.

The structure looks like this (taken from Include/devs/camddevices.h): struct MidiDeviceData {   ULONG Magic; char *Name; char *IDString; UWORD Version; UWORD Revision;

BOOL (ASM *Init)(       REG(a6) APTR SysBase     );

void (*Expunge)(void);

struct MidiPortData *(ASM *OpenPort)(       REG(a3) struct MidiDeviceData *data,        REG(d0) LONG portnum,        REG(a0) ULONG (* ASM transmitfunc)(APTR REG(a2) userdata),        REG(a1) void (* ASM receivefunc)(UWORD REG(d0) input,APTR REG(a2) userdata),        REG(a2) APTR userdata    );

void (ASM *ClosePort)(       REG(a3) struct MidiDeviceData *data,        REG(d0) LONG portnum    );

UBYTE NPorts; UBYTE Flags; };


 * Magic   - This one must contain the MDD_MAGIC constant, which is also found in Include/devs/midi/camddevices.h.
 * Name      Must contain the name of your file. I.e. if the name of the driver-file is "Pinnacle-Fiji", 'Name' must also be a pointer to a string containing "Pinnacle-Fiji". If 'Name' and the name of the file don't match, the driver will not be loaded. (However, this is not a requirement if 'Flags' is 0, more about that later).
 * IDString  pointer to a string describing your driver.
 * Version   16 bit unsigned integer containing the version of the driver.
 * Revision  16 bit unsigned integer containing the revision of the driver.

Init

Must contain a pointer to a legal function. This is the first function to be called after the driver is loaded into memory. The function will be called with sysbase as its only argument and it must return FALSE if a necessary initialisation failed, and otherwise TRUE. If FALSE is returned, the 'Expunge'-function will not be called, so any resources already allocated before the failure will have to be freed from within the function, before returning. The 'Init'-function is normally called from the Camd.library's 'Init'-function, but due to some, hmm, bad design in AROS/Camd.library; you shouldn't be too sure the Forbid-state is not broken, so please call Forbid/Permit if that is needed.

In this function you may also set the 'Expunge', 'OpenPort', 'ClosePort' and 'NPorts' attributes if you want to. If they were not set before, you must set them now. This feature might be of use if you don't know how many ports are available before running. (As an example, if you want to make drivers for Turtle Beachs' pinnacle and fiji, instead of making two drivers, you just check if the device is pinnacle or fiji, and set 'NPorts' one or two higher for pinnacle than you would if it were a fiji, since pinnacle and fiji are very similar, apart from the fact that pinnacle has a built-in midi-synth and possibly an add-on synth-card.)

Expunge This function is called right before the driver is unloaded from memory. It is normally called from Camd.libraries Expunge-routine, but don't trust the Forbid-state not to be broken.

OpenPort

Pointer to a function that is called from Camd.library the first time an application wants to use a specified port in your driver. It provides the following arguments:


 * 'data' want to use this one if you don't want to have any global data in your driver. It points to your MidiDeviceData struct.
 * 'portnum'  The number of the port that wants to be used.
 * 'transmitfunc' A pointer to a function you call when there is midi-data waiting to be sent thru the port. Important note: 'transmitfunc' is rather single-threaded, i.e. having two visitors simultaneously with the same 'userdata' will not work. Interrupts may be disabled when calling this one. More about the 'transmitfunc' below.
 * 'receiverfunc' A pointer to a function you call when there is midi-data coming from the port that you want to distribute to the Camd.library. The 'receiverfunc' is rather single-threaded as well, so no more than one visitor at the time with the same 'userdata' (which shouldn't be necesarry to point out, though). Interrupts can not be disabled when calling because it signals a task. The 'input'-argument is the midi-byte you want to send to camd. It is not a message, just a single byte. If bit 8 is set, you tell Camd.library that there is overflow on the port. If your midi-device uses running status to lower the data-stream (which is almost always the case), you won't have to worry about that, since the receiverfunc in Camd.library handles running status.
 * 'userdata' Pointer to unique data for this port that must be provided when calling the functions pointed to by 'transmitfunc' and 'receiverfunc'.

About the 'transmitfunc' and 'receiverfunc' arguments. They always point to the same functions, and could just as well have been provided with the Init-function. However, this is how Camd.library was designed from the start, and it probably won't be changed. Still, it is safe to keep just two pointers in your driver, containing 'transmitfunc' and 'receiverfunc'. See the debug-driver-source for example. And, most important, they can both be called from interrupts.

ClosePort A pointer to a function that is called when the last application that is using your port tells Camd.library that it doesn't need the port anymore.

NPorts Number of ports the driver provides. May be set directly at compile-time, or in the Init-function. If bit 0 in 'Flags' is not set, 'NPorts' must be set directly at compile-time (bit 0 in 'Flags' is only for the old driver-format, and you probably don't want to use that if you're reading this doc-file.)

Flags Only one flag is used for now, and that is bit 0. Don't set any other flags. If bit 0 is NOT set, camd.library knows that this is a driver of the old type. However, that mode is not very fit for the new camd.library, so please set this flag.

'OpenPort' should return a pointer to a MidiPortData struct. If the port could not be opened, you return NULL.

The MidiPortData struct looks like this: struct MidiPortData {   void (* ASM ActivateXmit)(ULONG REG(d0) portnum); }; 'ActivateXmit' contains a pointer to a function in your driver that is called from Camd.library whenever Camd.library has some midi-data it wants to be distributed. In other words, when Camd.library calls this function, it is time for you to as soon as possible call the 'transmit'-function to get mididata. Here is an example how you can do that: while((data=(transmitfunc)(UserData[portnum-1])!=0x100)   SendDataToPort(portnum-1,data); (Here, 'UserData' is an array of 'usedrata'. The 'userdata' is provided when 'OpenPort' is called.)

As you see, there might be more than one byte that is waiting to be picked up (that is actually the common case), and when there is no more data to be picked up, 'transmitfunc' returns 0x100.

Unlike the original driver-format, there's no harm in calling 'transmitfunc' even though there is no more data waiting to be picked up. (That's the most important change between the old and the new driver-format: How on earth are you able to know there aren't any more datas to be picked up for all possible situations?)

Also beware that 'ActivateXmit' might be called even though there's no more data to be picked up, and also while you are currently picking up data with another task or interrupt. But fortunately, 'ActivateXmit' will not be called again until you return, so it's single-threaded per port.

Note that 'transmitfunc' returns data that is optimized with running status, so if you are not sending data that is going to be directly transmitted via a midi-cable, or the place you are sending the data is in some way not able to handle running-status, you have to manually keep track of running status. But if this is the case, its probably better if you write a normal Camd.library application that makes a new cluster and you receive data via a hook, which you again distribute to wherever you want them. Then you'll get all status-bytes. (Applications using Camd.library don't see any difference between a normal driver and the driver-technique described here)

/*   Copyright � 1995-2001, The AROS Development Team. All rights reserved. $Id$

Desc: Lang: English

/********************************************************************************* Not a very good example at all. But at least it should prove that AROS is able to use camd-drivers. -Kjetil M. Compiling it up is easy. Its just like any other AROS-program, showed by this makefile: " debugdriver: debugdriver.o   gcc -nostartfiles -nostdlib -Xlinker -i ../../lib/startup.o debugdriver.o -o debugdriver -L../../lib -larossupport -lamiga -larosc -larosm

debugdriver.o: debugdriver.c makefile gcc debugdriver.c -c -I../../Include -Wall "




 * 1) include 
 * 2) include 
 * 3) include 
 * 4) include 


 * 1) define NUMPORTS 4

struct ExecBase *SysBase;

int main(void){ /* A camd mididriver is not supposed to be run directly, so we return an error. */ return -1; }

/*   Prototypes    */

extern void kprintf(char *bla,...);

BOOL ASM Init(REG(a6) APTR sysbase); void Expunge(void); SAVEDS ASM struct MidiPortData *OpenPort(					 REG(a3) struct MidiDeviceData *data,					 REG(d0) LONG portnum,					 REG(a0) ULONG (* ASM transmitfunc)(APTR REG(a2) userdata),					 REG(a1) void (* ASM recievefunc)(UWORD REG(d0) input,APTR REG(a2) userdata),					 REG(a2) APTR userdata					 ); ASM void ClosePort(		  REG(a3) struct MidiDeviceData *data,		   REG(d0) LONG portnum		   );

/*  End prototypes  */

/***********************************************************************  The mididevicedata struct. Note. It doesn't have to be declared with the const qualifier, since NPorts may be set at Init. You should set the name-field to the same as the filename, that might be a demand... const struct MidiDeviceData mididevicedata={ MDD_Magic, "debugdriver", "Debugdriver V41.0 (c) 2001 AROS - The AROS Research OS", 41, 0,  Init, Expunge, OpenPort, ClosePort, NUMPORTS, 1 };

/****************************************************************  We only store sysbase, thats all we need in this example. Otherwise, you may want to open libraries, set number of  ports, obtain interrupts, etc. ***************************************************************/ SAVEDS ASM BOOL Init(REG(a6) APTR sysbase){ SysBase=sysbase; return TRUE; }

/****************************************************************  Nothing to do here. Normally, you may want to free memory, close some libraries, free some interrupts, etc. void Expunge(void){ return; }

ULONG (ASM *TransmitFunc)(REG(a2) APTR userdata); APTR UserData[NUMPORTS];

/****************************************************************  Normally, you may want to start an interrupt, or signal another task, or send a message to a port, that calls the transmit-function. But for this small example, sending the signal directly via kprintf is excactly what we want to do. SAVEDS ASM void ActivateXmit(REG(a2) APTR userdata,ULONG REG(d0) portnum){ ULONG data; for{

data=(TransmitFunc)(userdata);

if(data==0x100) return; kprintf("Debugdriver has received: %lx at port %ld\n",data,portnum); } }

struct MidiPortData midiportdata={ ActivateXmit };

/****************************************************************  This one is called whenever a program that has opened camd.library wants to use your services. SAVEDS ASM struct MidiPortData *OpenPort(					 REG(a3) struct MidiDeviceData *data,					 REG(d0) LONG portnum,					 REG(a0) ULONG (* ASM transmitfunc)(APTR REG(a2) userdata),					 REG(a1) void (* ASM recieverfunc)(UWORD REG(d0) input,APTR REG(a2) userdata),					 REG(a2) APTR userdata					 ){ /* We haven't got any receiver function, so we don't bother about storing the receiverfunc variable. */

TransmitFunc=transmitfunc; UserData[portnum-1]=userdata; return &midiportdata; }

/****************************************************************  Nothing to do here. Normally, you may want to free memory, mark the port not to be in use anymore, delete a task, etc. ASM void ClosePort(		  REG(a3) struct MidiDeviceData *data,		   REG(d0) LONG portnum		   ){ return; }