http://www.aros.orgAROS-ExecAROS-Exec ArchivesPower2People
kitty mascottop logo menu

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;
This one must contain the MDD_MAGIC constant, which is also found in Include/devs/midi/camddevices.h.
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).
A pointer to a string describing your driver.
16 bit unsigned integer containing the version of the driver.
16 bit unsigned integer containing the revision of the driver.

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

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.

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:

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.
The number of the port that wants to be used.
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.
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.
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.

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.
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.)
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:


(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-2017, The AROS Development Team. All rights reserved.
Amiga® is a trademark of Amiga Inc. All other trademarks belong to their respective owners.