Open main menu

Wikibooks β

Aros/Developer/Docs/Libraries/CAMD

< Aros‎ | Developer/Docs

IntroductionEdit

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

USB will poll and find all midi devices automatically

Current version of camd is 41.1 (26.11.2016) and the most up to date with src is on 50.8

AROS still needs implementing the code for a camd.library USB driver. The m68k asm src version is at the bottom of this page


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 <drivername>.out.0, <drivername>.out.1, <drivername>.in.0, etc. instead.

Camd library inside technical specs specifications src


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.


Midi SpecsEdit

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

  Node                                        Node


NodesEdit

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.


LinksEdit

Links are created beween 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 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

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 


MessagesEdit

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


FiltersEdit

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


ExamplesEdit

#include <stdio.h>
#include <proto/exec.h>
#include <proto/camd.h>
#include <midi/camd.h>

#define TABSIZE 4

struct Library *CamdBase=NULL;

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

#ifndef GetMidiAttrs
ULONG GetMidiAttrs(struct MidiNode *ml, Tag tag, ...){
        return GetMidiAttrsA(ml, (struct TagItem *)&tag );
}
#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, <driverdata> (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;
}
#include <proto/camd.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <midi/camd.h>
#include <stdlib.h>
#include <stdio.h>

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

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'
       I guess you 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. But I guess that's pretty obvious... 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
"

***********************************************************************************/


#include <proto/exec.h>
#include <exec/types.h>
#include <midi/camddevices.h>
#include <libcore/compiler.h>

#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;
}


ReferencesEdit

APTR LockCAMD(ULONG locktype) 
void UnlockCAMD(APTR lock)

struct MidiNode *CreateMidiA(struct TagItem *tags) 
void DeleteMidi(struct MidiNode *midinode) 
BOOL SetMidiAttrsA(struct MidiNode *midinode, struct TagItem *tags) 
ULONG GetMidiAttrsA(struct MidiNode *midinode, struct TagItem *tags) 
struct MidiNode *NextMidi(struct MidiNode *midinode) 
struct MidiNode *FindMidi(STRPTR name) 
void FlushMidi(struct MidiNode *midinode)

struct MidiLink *AddMidiLinkA(struct MidiNode *midinode, LONG type, struct TagItem *tags) 
void RemoveMidiLink(struct MidiLink *midilink) 
BOOL SetMidiLinkAttrsA(struct MidiLink *midilink, struct TagItem *tags) 
ULONG GetMidiLinkAttrsA(struct MidiLink *midilink, struct TagItem *tags) 
struct MidiLink *NextClusterLink(struct MidiCluster *cluster, struct MidiLink *midilink, LONG type) 
struct MidiLink *NextMidiLink(struct MidiNode *midinode, struct MidiLink *midilink, LONG type) 
BOOL MidiLinkConnected(struct MidiLink *midilink)

struct MidiCluster *NextCluster(struct MidiCluster *last)
struct MidiCluster *FindCluster(STRPTR name)

void PutMidi(struct MidiLink *link, ULONG msg) 
BOOL GetMidi(struct MidiNode *midinode, MidiMsg *msg) 
BOOL WaitMidi(struct MidiNode *midinode, MidiMsg *msg)

void PutSysEx(struct MidiLink *midilink, UBYTE *buffer) 
ULONG GetSysEx(struct MidiNode *midinode, UBYTE *Buf, ULONG len) 
ULONG QuerySysEx(struct MidiNode *midinode) 
void SkipSysEx(struct MidiNode *midinode)

UBYTE GetMidiErr(struct MidiNode *midinode)
WORD MidiMsgType(MidiMsg *msg) 
WORD MidiMsgLen(ULONG msg)

void ParseMidi(struct MidiLink *midilink, UBYTE *buffer, ULONG length) 
struct MidiDeviceData *OpenMidiDevice(UBYTE *name) 
void CloseMidiDevice(struct MidiDeviceData *mididevicedata) 
LONG RethinkCAMD()

void StartClusterNotify(struct ClusterNotifyNode *cn) 
void EndClusterNotify(struct ClusterNotifyNode *cn) 
APTR GoodPutMidi(struct MidiLink *midilink, ULONG msg, ULONG maxbuff) 
BOOL Midi2Driver(APTR driverdata, ULONG msg, ULONG maxbuff) 



AddMidiLinkA()  CloseMidiDevice()       CreateMidiA()   DeleteMidi()
EndClusterNotify()      FindCluster()   FindMidi()      FlushMidi()
GetMidi()       GetMidiAttrsA() GetMidiErr()    GetMidiLinkAttrsA()
GetSysEx()      GoodPutMidi()   LockCAMD()      Midi2Driver()
MidiLinkConnected()     MidiMsgLen()    MidiMsgType()   NextCluster()
NextClusterLink()       NextMidi()      NextMidiLink()  OpenMidiDevice()
ParseMidi()     PutMidi()       PutMidiMsg()    PutSysEx()
QuerySysEx()    RemoveMidiLink()        RethinkCAMD()   SetMidiAttrsA()
SetMidiLinkAttrsA()     SkipSysEx()     StartClusterNotify()    UnlockCAMD()
WaitMidi()                       


AddMidiLinkA()

Synopsis

struct MidiLink * AddMidiLinkA(

        struct MidiNode * midinode,
        LONG type,
        struct TagItem * tags );

struct MidiLink * AddMidiLink(

        struct MidiNode * midinode,
        LONG type,
        TAG tag, ... );

Function

Adds a midilink to a midinode. Inputs

midinode - Owner. type - MLTYPE_Receiver or MLTYPE_Sender tags - Tag-values supplied to SetMidiLinkAttrs. See also CreateMidiA() SetMidiLinkAttrsA()


CloseMidiDevice()

Synopsis

void CloseMidiDevice(

        struct MidiDeviceData * mididevicedata );

Function

Remind me to fill in things here later. See also OpenMidiDevice()


CreateMidiA()

Synopsis

struct MidiNode * CreateMidiA(

        struct TagItem * tags );

struct MidiNode * CreateMidi(

        TAG tag, ... );

Inputs

tags - Tag-values supplied to SetMidiAttrs. Result NULL if failed.


DeleteMidi()

Synopsis

void DeleteMidi(

        struct MidiNode * midinode );

Function

First deletes all midilinks attached to the midinode, then frees all buffers, before it frees itself.


EndClusterNotify()

Synopsis

void EndClusterNotify(

        struct ClusterNotifyNode * cn );

Function

void EndClusterNotify(struct ClusterNotifyNode *) Inputs

pointer to previously added ClusterNotifyNode. Result

void Notes

DO NOT call with a ClusterNotifyNode that has not been added. Bugs

None known See also StartClusterNotify(),


FindCluster()

Synopsis

struct MidiCluster * FindCluster(

        STRPTR name );

Function

Finds a midicluster from camd's internal list of midiclusters. Inputs

name - Name of cluster to find. Result

NULL if cluster could not be found. Notes

- CL_Linkages must be locked before calling. See also FindMidi()


FindMidi()

Synopsis

struct MidiNode * FindMidi(

        STRPTR name );

Function

Finds the midinode with name 'name'. Inputs

name - Name of midinode to find. Result

NULL if no midinode with that name or a pointer to the midinode if success. Notes CL_Linkages must be locked.


FlushMidi()

Synopsis

void FlushMidi(

        struct MidiNode * midinode );

Function

Remind me to fill in things here later. Bugs

Not tested. See also GetMidi() GetSysEx()


GetMidi()

Synopsis

BOOL GetMidi(

        struct MidiNode * midinode,
        MidiMsg * msg );

Function

Gets a message from a midinodes buffer. Inputs

midinode - pointer to midinode msg - The message is removed from the internal buffer and copied into msg. Result

TRUE if message was copied, FALSE if buffer was empty. See also WaitMidi()


GetMidiAttrsA()

Synopsis

ULONG GetMidiAttrsA(

        struct MidiNode * midinode,
        struct TagItem * tags );

ULONG GetMidiAttrs(

        struct MidiNode * midinode,
        TAG tag, ... );

Notes

If you are not the owner of the midinode, you should lock Camd before calling to ensure that it wont go away. See also SetMidiAttrsA(),


GetMidiErr()

Synopsis

UBYTE GetMidiErr(

        struct MidiNode * midinode );

Function

Gets the current error-state of a midinode. Inputs

midinode - pointer to midinode Result

0 if everything was okey, not 0 else. See also GetMidi() WaitMidi()


GetMidiLinkAttrsA()

Synopsis

ULONG GetMidiLinkAttrsA(

        struct MidiLink * midilink,
        struct TagItem * tags );

ULONG GetMidiLinkAttrs(

        struct MidiLink * midilink,
        TAG tag, ... );

Function

Remind me to fill in things here later. Notes

If you are not the owner of the midilink, you should lock Camd before calling to ensure that it wont go away. Theres no point in locking if you know it wont go away. See also SetMidiLinkAttrsA()


GetSysEx()

Synopsis

ULONG GetSysEx(

        struct MidiNode * midinode,
        UBYTE * Buf,
        ULONG len );

Function

Remind me to fill in things here later. See also SkipSysEx() QuerySysEx()


GoodPutMidi()

Synopsis

APTR GoodPutMidi(

        struct MidiLink * midilink,
        ULONG msg,
        ULONG maxbuff );

Function

This is a private function, and will probably be obsolete. Please don`t use. Result

NULL if success, driverdata if not. See also PutMidi() PutMidiMsg() Midi2Driver()


LockCAMD()

Synopsis

APTR LockCAMD(

        ULONG locktype );

Function

Locks the internal lists in camd. You must call UnlockCAMD later. Inputs

locktype - Only CD_Linkages is legal. Result APTR to send to UnlockCAMD


Midi2Driver()

Synopsis

BOOL Midi2Driver(

        APTR driverdata,
        ULONG msg,
        ULONG maxbuff );

Function

This is a private function, and will probably be obsolete. Please don`t use. Result

TRUE if max(buffer,maxbuffer) was big enough to hold the message, FALSE if not. See also PutMidi() GoodPutMidi() PutMidiMsg()



MidiLinkConnected()

Synopsis

BOOL MidiLinkConnected(

        struct MidiLink * midilink );

Function

If midilink is a sender, returns FALSE if the cluster has no receivers. If midilink is a receiver, returns FALSE if the cluster has no senders. Else TRUE. Inputs midilink - pointer to midilink we want to check.


MidiMsgLen()

Synopsis

WORD MidiMsgLen(

        ULONG msg );

Function

Returns the length of a midimessage. sysex message leads to a length of zero. Inputs msg - Message.


MidiMsgType()

Synopsis

WORD MidiMsgType(

        MidiMsg * msg );

Function

Return the type of a message (see <midi/camd.h>). sysex messages returns -1. Inputs

msg - midimessage. NextCluster()

Synopsis

struct MidiCluster * NextCluster(

        struct MidiCluster * last );

Function

Finds the next cluster in camds list of clusters. Inputs last - cluster to start searching for. Result Next cluster in list, or first if 'last' is NULL.

#include <stdio.h>
#include <proto/exec.h>
#include <proto/camd.h>
#include <midi/camd.h>

int main(){

        APTR lock;
        struct MidiCluster *cluster;

        struct Library *CamdBase=OpenLibrary("camd.library",0L);
        if(CamdBase!=NULL){

                lock=LockCAMD(CD_Linkages);

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

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

                }else{

                        do{
                                printf("clustername: -%s-\n",cluster->mcl_Node.ln_Name);
                                cluster=NextCluster(cluster);
                        }while(cluster!=NULL);

                }

                UnlockCAMD(lock);
                CloseLibrary(CamdBase);

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

        return 0;
}

Notes

- CL_Linkages must be locked.

- Often, a program wants to use this function for finding available

 clusters a user can choose from. It is then recommended to also
 let the user have the possibility to write in the name of a new cluster,
 so that camd can make new clusters automatically to be used for
 communication between various applications without having hardware-drivers
 etc. interfere with the datastreams. Applications do
 not need to make special concerns about how cluster works or
 what they contain; that is all managed by camd.

See also NextMidiLink() NextMidi() FindCluster()


NextClusterLink()

Synopsis

struct MidiLink * NextClusterLink(

        struct MidiCluster * cluster,
        struct MidiLink * midilink,
        LONG type );

Function

Finds the next midilink of a specified type in a midicluster. Inputs

cluster - Pointer to the midicluster that the midilink belongs to. midilink - Pointer to the midilink to begin searching from. type - Either MLTYPE_Receiver or MLTYPE_Sender Result

Returns the next MidiLink of a spevified type, or NULL if the last in the list. If midilink is NULL, returns the first. Notes CL_Linkages must be locked.


NextMidi()

Synopsis

struct MidiNode * NextMidi(

        struct MidiNode * midinode );

Function

Returns the next midinode in the list of midinodes, or NULL if midinode was the last one. Inputs

midinode - The midinode to begin searching from. If NULL,

          returns the first midinode in the list.

Notes CL_Linkages must be locked.


NextMidiLink()

Synopsis

struct MidiLink * NextMidiLink(

        struct MidiNode * midinode,
        struct MidiLink * midilink,
        LONG type );

Function

Returns the next MidiLink of a specified type that belongs to a midinode. Or NULL if midilink was the last. If midilink is NULL, returns the first one. Inputs type - MLTYPE_Sender or MLTYPE_Receiver.

Notes CL_Linkages must be locked.


OpenMidiDevice()

Synopsis

struct MidiDeviceData * OpenMidiDevice(

        UBYTE * name );

Function


See also CloseMidiDevice()



ParseMidi()

Synopsis

void ParseMidi(

        struct MidiLink * midilink,
        UBYTE * buffer,
        ULONG length );

Function

   Puts a midibuffer to a midilinks clusters midilinks midinodes and hardware.
   To help understand what it does, the following macro makes PutMidi
   use ParseMidi instead of calling camd.library's PutMidi function for
   small-endian cpus:
   #define PutMidi(midilink,message) ParseMidi((midilink),&(message),MidiMsgLen(message))
   (But please don't use this macro, since its not big-endian compatible,  and that PutMidi is faster than ParseMidi)

Notes

If its more convenient to use PutMidi and PutSysEx instead of ParseMidi, do that. ParseMidi is a bit heavier function to use than PutMidi and PutSysEx.

MLINK_Parse must have be set when calling either AddMidiLinkA or SetMidiLinkAttrsA.

See also PutMidi() PutSysEx()



PutMidi()

Synopsis

void PutMidi(

        struct MidiLink * link,
        ULONG msg );

Function

Puts a midimessage to hardware and all sender-links that belongs to the midilink's cluster. Does only wait if a hardware send- buffer is full, and then tries again and again until the message is sent. Else, the function should return immediately.Inputs

link - pointer to the midilink to send to.

msg - The complete message to send. A message can not hold more

      than 3 bytes, so it fits fine in a ULONG integer. See NOTES
      to see how a message is built up.

Notes

Sending an illegal message may have serious consequences. If you for some reason are not completely sure whether your message is legal, you could do the following test:

if((msg>>24)<0x80 || (msg>>24)==0xf0 || (msg>>24)==0xf7 || (msg>>16&0xff)>0x7f || (msg>>8&0xff)>0x7f){
        debug("Warning, illegal midimessage: %x\n",msg);
}else{
        PutMidi(midilink,msg);
}

See also PutMidiMsg()


PutMidiMsg()

Synopsis

PutMidiMsg(

  link,
  msg);

Function

Calls PutMidi((midilink),(msg)->mm_Msg) Notes Implemented as macro.


PutSysEx()

Synopsis

void PutSysEx(

        struct MidiLink * midilink,
        UBYTE * buffer );

Function

Distributes a SysEx message. First sends the message to the hardware and all midinodes connected to the midilinks cluster, then waits for the complete message to be sent to the hardware, if any. If a midinodes sysex-buffer is to small to carry the message, it will not be sent. If the buffer is big enough, but there is not enough room, a sysex-full-error will be set to the node. The message is sent to hardware regardless of transmit buffer size. Inputs

midilink - pointer to link. buffer - message to send, must start with 0xf0 and end with 0xf7.

        No bytes higher than 0x7f are allowed in the message.

See also GetSysEx()


QuerySysEx()

Synopsis

ULONG QuerySysEx(

        struct MidiNode * midinode );

Function

Returns the number of bytes remaining in the current sys/ex message. Inputs

midinode - pointer to MidiNode Result Remaining bytes in sys/ex message. 0 is returned if the last message read from GetMidi() wasn't a sys/ex message. Bugs Tested. See also SkipSysEx() GetSysEx()



RemoveMidiLink()

Synopsis

void RemoveMidiLink(

        struct MidiLink * midilink );

Function

Removes and frees a midilink from the system. Inputs midilink - pointer to midilink to remove.


RethinkCAMD()

Synopsis

LONG RethinkCAMD(); Function

Make camd reload midi preferences. Result 0 on success. Bugs Not implemented.


SetMidiAttrsA()

Synopsis

BOOL SetMidiAttrsA(

        struct MidiNode * midinode,
        struct TagItem * tags );

BOOL SetMidiAttrs(

        struct MidiNode * midinode,
        TAG tag, ... );

Inputs

tagList -- pointer to an array of tags describing the player's

            attributes or NULL.

Tags

MIDI_Name (STRPTR) -- The name of the midinode; default is NULL or a pointer to a string.

MIDI_SignalTask (struct Task *) -- Task to signal whenever a midimessage is arriving to the node;

                                  default is the task of the caller of this function. (FindTask(NULL))

MIDI_RecvHook (struct Hook *) -- Function to call whenever a midimessage is arriving to the node.

                                  You should get the midimessage as the first argument in the function,
                                  however, that has not yet been implemented. Default is NULL.

MIDI_PartHook (struct Hook *) -- Don't really know what this one is for. Have to check amigos-autodocs.

                                  It does not currently do anything.

MIDI_RecvSignal (BYTE) -- Signal bit to use when signalling a task whenever a midimessage is

                                  arriving at the node, or -1 to disable signalling. Default is -1.

MIDI_PartSignal (BYTE) -- Signal bit to use when signalling a task when..... Default is -1.

MIDI_MsgQueue (ULONG) -- Number of messages the messagequeue is able to hold.

MIDI_TimeStamp (ULONG *) -- Pointer to an ULONG value which value is copied directly into the timestamp

                                  attribute in midimessages whenever a new message is received at the node.


MIDI_ErrFilter (UBYTE)         -- Filters out the errors you don't want to see.


MIDI_ClientType (UWORD)        -- What sort of application you that owns this node.
MIDI_Image (struct Image *)    -- Pointer to an image representing this node.
MIDI_ErrorCode (ULONG *)       -- Pointer to an ULONG which will be set if something went wrong.

Result

TRUE if everything went okey, FALSE if not. Errorcode is put in an ULONG pointed to by the MIDI_ErrorCode tag, if supplied.

Notes

- If the midinode is not owned by yourself, please lock

 camd to ensure it wont go away.

- Allthough you are able to modify midinodes owned by

 others, please avoid it, its normally "non of your buziness",
 and may lead to crashes and other "unexpected" behaviors.
 However, if you have full control of the owner of the
 midinode (f.ex when both you and the owner belongs to the
 same probram and you are absolutely shure you know what
 you are doing), there is no problem.

See also GetMidiAttrsA()



SetMidiLinkAttrsA()

Synopsis

BOOL SetMidiLinkAttrsA(

        struct MidiLink * midilink,
        struct TagItem * tags );

BOOL SetMidiLinkAttrs(

        struct MidiLink * midilink,
        TAG tag, ... );

Function

Remind me to fill in things here later. Notes

- If the midilink is not owned by yourself, please lock

 camd to ensure it wont go away.

- Allthough you are able to modify midilinks owned by

 others, please avoid it, its normally "non of your buziness",
 and may lead to crashes and other "unexpected" behaviours.
 However, if you have full control of the owner of the
 midilink (f.ex when both you and the owner belongs to the
 same probram and you are absolutely shure you know what
 you are doing), there is no problem.

- Warning! If another task have locked Camd and is waiting

 for you to finish, there will be a deadlock if you try
 to change priority or change/set cluster.

See also GetMidiLinkAttrsA()



SkipSysEx()

Synopsis

void SkipSysEx(

        struct MidiNode * midinode );

Function

Remind me to fill in things here later. See also QuerySysEx() GetSysEx()



StartClusterNotify()

Synopsis

void StartClusterNotify(

        struct ClusterNotifyNode * cn );

Function

void StartClusterNotify(struct ClusterNotifyNode *cn) Inputs

pointer to initialized ClusterNotifyNode structure Result void

struct ClusterNotifyNode cnn;

cnn.cnn_Task=IExec->FindTask(NULL);
cnn.cnn_SigBit=IExec->AllocSignal(-1);
StartClusterNotify(&cnn);

            somewhere down the line...

Wait(1L<<cnn.cnn_SigBit)
    printf("Cluster Changes have happened\n");

Notes

ClusterNotifyNode structure must remain valid until EndClusterNotify(); Will only signal added and removed clusters, not internal state changes. See also EndClusterNotify()


UnlockCAMD()

Synopsis

void UnlockCAMD(

        APTR lock );

Function

UnLocks the internal lists in camd. Inputs

Pointer received from LockCAMD. Result

APTR to send to UnlockCAMD See also LockCAMD()


WaitMidi()

Synopsis

BOOL WaitMidi(

        struct MidiNode * midinode,
        MidiMsg * msg );

Function

Waits until a new message is received at the node, and copy the message to msg. Inputs

msg - Pointer to a midimessage where the message will be copied. Result

Returns TRUE if a new message arrived or had arrived, FALSE, if there was en error on the midinode. See also GetMidi()


CAMD m68k srcEdit

*
* CAMD Driver for Poseidon USB camdusbmidi.class
*
* Copyright 2006 Chris Hodges
*
************************************************************************
* Date        | Change
*-----------------------------------------------------------------------
* 04-Feb-2006 : Initial
************************************************************************

        ;output DEVS:midi/poseidonusb

        include "AmigaLVOs.s"
        include "Macros.lnk"
        
        include "exec/types.i"
        include "exec/execbase.i"           ; for FlushDevice()
        include "exec/macros.i"
        include "exec/ports.i"
        include "utility/hooks.i"
        include "midi/camddevices.i"

_LVOusbCAMDOpenPort     equ     -108
_LVOusbCAMDClosePort    equ     -114

Version  equ 1
Revision equ 2
Ports    equ 16

DEBUG_DETAIL    set     0

   STRUCTURE CAMDAdapter,0
        APTR    ca_ActivateFunc
        BOOL    ca_IsOpen
        STRUCT  ca_CAMDRXFunc,h_SIZEOF
        STRUCT  ca_CAMDTXFunc,IS_SIZE
        ULONG   ca_PortNum
        APTR    ca_TXFunc
        APTR    ca_RXFunc
        APTR    ca_UserData
        APTR    ca_TXBuffer
        ULONG   ca_TXBufSize
        ULONG   ca_TXWritePos
        ULONG   ca_TXReadPos
        APTR    ca_MsgPort
        LABEL   CAMDAdapter_SIZE

****************************************************************
*
*   Standard MIDI Device driver header
*
****************************************************************

; code at start of file in case anyone tries to execute us as a program

FalseStart
        moveq   #-1,d0
        rts

MDD ; struct MidiDeviceData
        dc.l    MDD_Magic       ; mdd_Magic
        dc.l    Name            ; mdd_Name
        dc.l    IDString        ; mdd_IDString
        dc.w    Version         ; mdd_Version
        dc.w    Revision        ; mdd_Revision
        dc.l    Init            ; mdd_Init
        dc.l    Expunge         ; mdd_Expunge
        dc.l    OpenPort        ; mdd_OpenPort
        dc.l    ClosePort       ; mdd_ClosePort
        dc.b    Ports           ; mdd_NPorts
        dc.b    0               ; mdd_Flags

        ;        123456789012
Name    dc.b    'poseidonusb',0
        dc.b    '3456789012345678901',0                 ; 32 bytes

IDString    
        dc.b    '$VER: Poseidon USB camdusbmidi.class driver 1.1 (21-Feb-06)',0
        dc.b    'Copyright 2006 Chris Hodges',0
        even

****************************************************************
*
*   MidiDeviceData Functions
*
****************************************************************

****************************************************************
*
*   Init
*
*   FUNCTION
*       Gets called by CAMD after being LoadSeg'ed.
*
*   INPUTS
*       None
*
*   RESULTS
*       TRUE if successful, FALSE on failure.
*
****************************************************************

Init
        PUTMSG  10,<"Init!">
        PUSHM   a6/a0/a1/d1
        move.l  4.w,a6
        move.l  a6,SysBase
        lea     .classname(pc),a1
        moveq.l #0,d0
        CALL    OpenLibrary
        move.l  d0,USBClsBase
        bne.s   .good
        lea     .classname2(pc),a1
        moveq.l #0,d0
        CALL    OpenLibrary
        move.l  d0,USBClsBase
.good   POPM
        rts
.classname2
        dc.b    'USB/'
.classname
        dc.b    'camdusbmidi.class',0

        even

****************************************************************
*
*   Expunge
*
*   FUNCTION
*       Gets called by CAMD immediately before being
*       UnLoadSeg'ed.
*
*   INPUTS
*       None
*
*   RESULTS
*       None
*
****************************************************************

Expunge
        PUTMSG  10,<"Expunge!">
        PUSHM   a6/a1/d0/d1
        move.l  SysBase(pc),a6
        move.l  USBClsBase(pc),a1
        CALL    CloseLibrary
        clr.l   USBClsBase
        POPM
        rts


****************************************************************
*
*   SendToCAMD
*
* Called by CallHookA() from camdusbmidi.class with Buffer address in a2
* and size in a1.
*
****************************************************************

SendToCAMD
        PUTMSG  10,<"SendToCAMD %08lx, buf=%08lx">,a0,a2
        PUSHM   a2-a4/d2
        moveq.l #0,d2
        move.l  a2,a3 ; object
        move.l  h_Data(a0),a0
        move.b  (a3)+,d2
        PUTMSG  10,<"size=%08ld">,d2
        move.l  ca_UserData(a0),a2
        move.l  ca_RXFunc(a0),a4
.loop   moveq.l #0,d0
        move.b  (a3)+,d0
        jsr     (a4)
        subq.l  #1,d2
        bgt.s   .loop
        POPM
        PUTMSG  10,<"done">
        rts

****************************************************************
*
*   GetFromCAMD
*
* Called by Cause() from ActivateXmit. 
*
****************************************************************

GetFromCAMD
        PUTMSG  10,<"GetFromCAMD %08lx">,a1
        PUSHM   a2-a6/d2/d3
        move.l  a1,a3
        move.l  ca_UserData(a3),a2
        move.l  ca_TXBuffer(a3),a5
        move.l  ca_TXFunc(a3),a4
        move.l  ca_TXBufSize(a3),d3
        move.l  ca_TXWritePos(a3),d2
        subq.l  #1,d3
.loop   jsr     (a4)
        PUTMSG  10,<"Byte... %lx %ld">,d0,d1
        move.b  d0,(a5,d2.l)
        addq.l  #1,d2
        and.l   d3,d2
        cmp.l   ca_TXReadPos(a3),d2
        beq.s   .oops                   ; this will lose data!
        move.l  d2,ca_TXWritePos(a3)
        tst.b   d1
        beq.s   .loop
.oops   
        move.l  ca_MsgPort(a3),d0       ; now inform usb driver
        beq.s   .noport
        move.l  d0,a0
        move.l  MP_SIGTASK(a0),a1
        moveq.l #1,d0
        moveq.l #0,d1
        move.b  MP_SIGBIT(a0),d1
        lsl.l   d1,d0
        move.l  SysBase(pc),a6
        CALL    Signal
        
.noport POPM
        PUTMSG  10,<"Done">
        rts

****************************************************************
*
*   OpenPort
*
*   FUNCTION
*       Open a MIDI port.
*
*   INPUTS
*       D0.b - Port number (should always be 0 for this driver)
*       A0 - Xmit function
*       A1 - Recv function
*       A2 - Data
*
*   RESULT
*       D0 - pointer to MidiPortData structure.
*
****************************************************************

OpenPort
        PUTMSG  10,<"OpenPort %ld, Xmit=%08lx,Recv=%08lx,Data=%08lx">,d0,a0,a1,a2
        PUSHM   a6/a0-a3/d1/d2
        move.l  USBClsBase(pc),a6
        moveq.l #0,d2
        move.b  d0,d2
        move.l  d2,d0
        lea     Name(pc),a3
        CALL    usbCAMDOpenPort
        lea     CAMDPortBases(pc),a0
        move.l  d0,(a0,d2.l*4)
        beq.s   .toobad
        move.l  d0,a0
        lea     ActivateXmit0(pc,d2.l*4),a1
        move.l  a1,ca_ActivateFunc(a0)

        move.l  d0,ca_CAMDRXFunc+h_Data(a0)
        lea     SendToCAMD(pc),a1
        move.l  a1,ca_CAMDRXFunc+h_Entry(a0)

        move.b  #NT_INTERRUPT,ca_CAMDTXFunc+LN_TYPE(a0)
        clr.b   ca_CAMDTXFunc+LN_PRI(a0)
        move.l  d0,ca_CAMDTXFunc+IS_DATA(a0)
        lea     GetFromCAMD(pc),a1
        move.l  a1,ca_CAMDTXFunc+IS_CODE(a0)
        st      ca_IsOpen(a0)
.toobad PUTMSG  10,<"Result=%08lx">,d0
        POPM
        rts


****************************************************************
*
*   ClosePort
*
*   FUNCTION
*       Close a MIDI port.
*
*   INPUTS
*       D0.b - Port number (always 0 for this driver).
*       A1 - USBID
*
*   RESULT
*       None
*
****************************************************************

ClosePort
        PUSHM   a6/a0/a1/d1
        move.l  USBClsBase(pc),a6
        and.l   #$ff,d0
        lea     Name(pc),a1
        CALL    usbCAMDClosePort
        POPM
        rts

        IFNE    DEBUG_DETAIL
kprintf PUSHM   a2-a3/a6
        lea     .putchr(pc),a2
        move.l  4.w,a6
        move.l  a6,a3
        CALL    RawDoFmt
        POPM
        rts
.putchr dc.w    $CD4B,$4EAE,$FDFC,$CD4B,$4E75
        ENDC

****************************************************************
*
*   ActivateXmit
*
* Called by CAMD with Midi Data in a0 (or a2?)
* BUG: Actually, the Midi Data is not in any register. This sucks
*
****************************************************************

ActivateXmit0
        moveq.l #0,d0
        bra.s   ActivateXmit
        moveq.l #1,d0
        bra.s   ActivateXmit
        moveq.l #2,d0
        bra.s   ActivateXmit
        moveq.l #3,d0
        bra.s   ActivateXmit
        moveq.l #4,d0
        bra.s   ActivateXmit
        moveq.l #5,d0
        bra.s   ActivateXmit
        moveq.l #6,d0
        bra.s   ActivateXmit
        moveq.l #7,d0
        bra.s   ActivateXmit
        moveq.l #8,d0
        bra.s   ActivateXmit
        moveq.l #9,d0
        bra.s   ActivateXmit
        moveq.l #10,d0
        bra.s   ActivateXmit
        moveq.l #11,d0
        bra.s   ActivateXmit
        moveq.l #12,d0
        bra.s   ActivateXmit
        moveq.l #13,d0
        bra.s   ActivateXmit
        moveq.l #14,d0
        bra.s   ActivateXmit
        moveq.l #15,d0
;       bra.s   ActivateXmit

ActivateXmit
        PUTMSG  10,<"ActivateXmit Port %ld">,d0
        PUSHM   a6
        move.l  SysBase(pc),a6
        move.l  CAMDPortBases(pc,d0.l*4),a0
        lea     ca_CAMDTXFunc(a0),a1
        CALL    Cause
        POPM
        rts

USBClsBase      
        ds.l    1
SysBase ds.l    1

CAMDPortBases
        ds.l    16

        END