MIDI driver interface
Примечание
This document is currently only a draft and as such will most likely change
before it is accepted as an official specification. It might also be
deprecated if a better approach has been found in the mean time, and
doesn't necessarily correspond precisely with the current implementations.
This document describes how to make a midi-driver for the camd.library.
This is just a short explanation of how things works right now. Everything
might change in the future, I guess.
CAMD is designed in such a way that writing device drivers should be very
easy. Looking at the source for the debug-driver placed in
AROS/workbench/devs/midi should explain the most, but some additional and
more detailed information is added here.
First, the new Camd.library will be described as AROS' Camd.library. However,
this Camd.library also works for AmigaOS, and everything described here goes
for the AmigaOS version of this Camd.library too. Beware that camd midi-device
drivers made as described below will not work with the original Camd.library
as the file-format was changed slightly. Fortunately, the new Camd.library is
also able to use camd midi-device drivers designed for the old Camd.libraries
driver-format (with the help of a dirty trick). (AROS' Camd.library does not
handle the old format, only the AmigaOs/SASC - version).
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
- A 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)
|