WARNING: This is an early specification for CAMD.  Keep in mind that 
some of the information is out of date.  See the includes and Autodocs 
for realtime.library and camd.library for the latest information.

CAMD: Commodore Amiga Multimedia Driver

	CAMD is a general library for synchronizing different multimedia
applications and passing data at high rates from one application to
another in real time. The emphasis is on MIDI and music applications;
However, CAMD can be used to synchronize other types of applications
as well, such as laser disk, animation players, sound effects, CD Audio,
videotape / SMPTE and other real-time applications.

Issues being worked on:

Named Rendezvous:
	The new architecture for CAMD will use the concept of named "rendezvous
points". CAMD maintains a list of "names" which represent "meeting places"
for real-time applications. (In the include files, a "meeting place" is
referred to as a "cluster").
	Each application can have any number of input or output linkages. Each
MidiLink structure will have the name of a "meeting place" which will be used
to rendezvous with other applications at the same "meeting place". Any number
of senders and receivers can meet at a single meeting place. Once the
particpants arrive, the ports are "wired" together with pointers so that
communication can be very fast and efficient. Any participant can leave at
any time; When all participants have left, the meeting place is
deleted. Senders may send "events" which are clumps of data in MIDI format.
(Each "clump" contains one complete MIDI message). If there are no
receivers, the messages go nowhere, otherwise each receiver gets a copy of
the sender's event. Events are buffered, using memory allocated by the
receiving application, or by CAMD.
	Some meeting places are "public" while others are "private". Public
meeting places can be seen by the user -- for example, a MIDI user can see
a scrolling list of meeting places, and select one for his application to
rendezvous with. The user could also add a public meeting place to the list
by clicking an "Add" button.
	Private meeting places do not appear on user lists. They are used for
special types of communication between applications that do not wish to be
disturbed. Privacy is actually an attribute of a participant -- the meeting
place is only private if all participants are private. This allows for
participants to discretely open a port at a meeting place which will only
become public if another participant joins in. This avoids cluttering up
user lists with features that are not active.
	The nice thing about the meeting place concept is that it is totally
forgiving. If a user sets up his MIDI sequencer application to control a
light-show application, using CAMD, he can save the configuration in the
"Save Settings" menu. Then later, if he decides to run only one of those
applications, it will still work, even though the other half of the link is
not present. Thus, it will be easy for musicians and multimedia artists to
create modular setups without having to spend tedious time linking and
configuring their system because some software or equipment is not
currently activated.
	Meeting places can also have a comment field, which is determined
by the comment field of the MidiLink with the highest priority that
is attached to it.

Time structures for CAMD.

	Within the library base of CAMD is a central clock called the heartbeat.
This clock is always counting, wrapping around when it overflows, at a rate
of 500Hz. It is driven by one of the CIA timers (whichever one is available).
	From the heartbeat clock are derived the various "Conductors". Each
conductor represents a group of applications which wish to remain
synchronized together. Like the meeting places, conductors are created
automatically when one member attempts to open it, and are deleted when the
last member leaves. There is a default conductor called "main" which is
used as a default for applications who want to be synchronized with
whomever else is out there.
	Unlike meeting places, however, there is a limited number of 'senders',
or in this case 'external time sources' such as SMPTE, MIDI Time Code, etc.
There can be either zero or one external time sources per conductor. If
there are zero, then it is a free-running clock, that is to say that it
runs entirely from the heartbeat clock. If an external time source is
present and active however, the conductor will still run off of the
heartbeat clock, but the values will be constrained to be "near" the value
of the external clock. Thus, the external clock keeps the internal clock in
time, but is the internal clock that actually does the real work. Thus, we
can "sing along" with SMPTE, but at a much higher clock rate and
consequently much higher precision. After all, SMPTE only runs at 30 frames
a second or so, while the heartbeat runs at 500 times a second.
	Conductors have essentially three attributes: A list of clients, called
"players" a current time value, and a "play state", which indicates if the
Conductors is stopped, paused, locating or running. These states will be
defined in more detail later.

	Attached to the Conductor are any number of players. A PlayerInfo
is a structure created by the application, which is used to deliver clock
pulses from the conductor to the application. This is done through
two mechanisms: A "wake-up call", where the application can be informed
whenever a particular point in time passes, and a "tick hook" which allows
for an application callback function to be invoked each time a new clock
value is posted.
	In addition, there is a provision for applications to keep track of time
in their own internal format. This is useful for music programs which may
find measures and tempo (metered time) more useful than 500Hz ticks. In
addition, CAMD allows for MIDI events posted to a particular port to be
timestamped in the application's own format. Basically, each MidiNode
contains a pointer to a longword value to use as the source of timestamping
information. This could be used to point to any one of several fields
in the PlayerInfo structure, depending on what type of timestamp was
desired, or it could in fact point to any longword who's value is incremented
on a regular basis.
	The PlayerInfo field also contains a facility for signaling a task
whenver a change of state occurs, such as play, stop, etc.

	There is also a facility for distributing metered time between players.
The flag PLAYERF_CONDUCTED is set for applications that want to take advantage
of this feature. The application who's PlayerInfo node has the highest
priority will determine the time for applications of lower priority.
The first CONDUCTED player fills in the cdt_Metronome with the number
of 384ths of a quarter note (unless someone has a beter number they want
to use...) that have elapsed.
	Note that this only allows one "kind" of metered time to be distributed
between players, namely musical time. Note that applications can still
distribute MIDI Clocks to one another, if they need something special.

A word of note on System overhead:
	A number of program authors are concerned because of the fact that some
of the CIA timers run on a higher interrupt level than the Amiga serial
port -- thus, potentially interfering with MIDI reception. Rest assured
that CAMD will do most of it's work in a Soft Interrupt (the lowest level
interrupt) and that the timer interrupt is extremely short.

Clock States:
	The four states are: STOP, PAUSE, LOCATE and PLAY. The can be set by
anybody at any time (even an interrupt). Thus, if one application signals
to start playing, they all play together. Other applications which are
driven from different Conductor are not affected, of course.
	An external timing source can also start the clock. This allows the
user to start the playback simply by pressing the "play" button on the
videotape recorder, laser disk, etc.
	There are two ways to start the clock: The first way is to set the
clock to CLOCKSTATE_RUNNING. This starts the clock immediately, and then
informs the applications that the clock has started, and what time it
started at. This will most often be the case for an external time source,
such as SMPTE -- it can't slow down the tape to wait for the applications
to get ready.
	The other way is to set the state to CLOCKSTATE_LOCATE. This informs each
player that the clock is about to start (and what time), then waits until
each application has set its 'ready' bit. At that point, the state changes
to CLOCKSTATE_RUNNING. The purpose of this is simple: Some applications can't
start immediately, because of the fact that they have to set their current
"state" based on the starting time. In the case of some music applications,
this can take considerable time, as the application attempts to determine
the "state" of all of it's internal variables for a particular clock time.
So the CLOCKSTATE_LOCATE state allows simpler applications to wait until the
more complex ones get ready.
	You may be asking: What happens when an external timing source sets the
clock to CLOCKSTATE_RUNNING? What happens to these applications that can't
start immediately? The answer is that they now have to "chase" the current
clock time, like a man running after a train that is leaving the station,
until they "catch up" at which point they will join in along with the
others. This turns out to be no problem in practice, and professional
sequencers do it all the time.

More on External Synchronization Sources:
	An external timing source is a special kind of player. It sets a
flag in it's PlayerInfo structure indicating that new clock times should
never be posted. (In fact the only purpose of the PlayerInfo in this case
is to keep the Conductor from being deleted in the case that all other
PlayerInfos should go away).
	The software driver for the external input can, at any time, call the
CAMD function ExternalSync(). This function constrains the Conductor to
only allow clock values which are between the time of the current external
clock tick and the estimated time of the next external clock tick. Each time
an external timing pulse is received, it should call this function to allow
the conductor to tick ahead a few more units. If the conductor falls
behind, ExternalSync() will push it ahead to catch up with the external
time, and if it pulls to far ahead, ExternalSync will cause it to stop and
wait while the external clock catches up.
	I envision that external sync drivers will come as 3rd-party utilities
with their own user interface. The utility will open a window which allows
the user to set the following attributes:

	-- The name of the conductor to affect.
	-- A checkbox to enable or disable AutoStart
	-- A reading of the current external time value in whatever units
		appropriate, such as SMPTE time code
	-- an offset value, used to 'bias' the time (change the origin of zero)
	-- Various device-specific parameters.

	The "AutoStart" feature simply means that the external timing driver
should start the clock when the external source starts, and should stop it
when the external source stops.
	Note that when the external source is not running, the software driver
can "release" the Conductor so that it becomes a free-running clock
again. This allows users to play their melodies, test them out and edit
them, while the tape is not running -- without having to press any special
buttons.
	(Issue: If that's the case, does 'Autostart' have any meaning? Instead
should we have a 'disable' button that allows them to play the tape without
playing the music?)
	(Issue: We should also have a mode where the clock does NOT become
a free-running clock when the tape is not running. This will allow us
to have applications pre-located and "waiting" for the external clock
to start).

Private Conductors
	Sometimes, an application may which to have a free-running source-clock
of it's own, so that it can get timing-based services unrelated to the
externally-synchronized tracks. For example, dragging notes with the mouse
may require a timing signal to shut off the note after so many seconds, and
you would not want this to be controlled by the video tape -- in fact,
you would want this to work even if the clock were stopped. How the
application manages the complexities of dealing with more than one
PlayerInfo is left as an excersize to the reader.

Details of the Conductor

	Note that all time values can be negative. This is useful in cases
where either the videotape has rewound back too far, or where the
application wishes to provide a "count-in".

	cdt_ClockTime: This is the current value of the clock. It is
incremented once each heartbeat, subject to external constraints. It will
never go backwards while the clock is running -- if it does, then the clock
is considered to have "stopped" and "rewound".
	Note: If the clock is being driven externally, ClockTime will not
be incremented until the first external "tick" is received. This is to
prevent the music from playing one tick's worth while waiting for the
tape to start.

	cdt_StartTime: This is the starting time of the clock. It is set when
the clock goes into either LOCATE or RUNNING state.

	cdt_ExternalTime: The clock time of the last external timing pulse.
If external sync is enabled, the clock will never be earlier than this.
Note that the external software driver will have to convert from it's units
to 500Hz CAMD units.

	cdt_MaxExternalTime: The estimated clock time of the next external
timing pulse. If the ClockTime is greater than this, it will not be
incremented.
	(Note: Signed comparisons are used, in a "wrap-around" manner)

	cdt_Metronome: This is the metered time to be ditributed to applications
that want it. The CONF_METROSET flag will be set if this field has been filled
in. If it is not set, and the application is expecting metered time, then
the application's time hook must fill it in itself, so that other players will
be able to follow it's lead.

	cdt_PlayersNotReady: This is a count of the number of PlayerInfos
that are not ready to go into RUNNING mode.

	cdt_State: This is the current playing state.
	
	cdt_Players: This is an Exec List of PlayerInfos.

struct Conductor {
    struct Node	   cdt_Node;		/* conductor list node		*/
    struct MinList cdt_Players;		/* list of players		*/
    ULONG	   cdt_Flags;		/* clock flags			*/
    ULONG	   cdt_ClockTime,	/* current time of this sequence*/
		   cdt_StartTime,	/* start time of this sequence	*/
		   cdt_ExternalTime,	/* time from external unit	*/
		   cdt_MaxExternalTime,	/* upper limit on sync'd time	*/
		   cdt_Metronome; 	/* metric time of highest pri node */
    UWORD	   cdt_PlayersNotReady;	/* count of players not ready	*/
    UBYTE	   cdt_State;		/* playing or stopped		*/
    UBYTE          cdt_Pad[1];
};

#define CONDUCTF_EXTERNAL    (1<<0)       /* clock is externally driven   */
#define CONDUCTF_GOTTICK     (1<<1)       /* received 1st external tick   */
#define CONDUCTF_METROSET    (1<<2)       /* metered tie filled in        */

enum time_states {
    CLOCKSTATE_STOPPED=0,                 /* clock is stopped             */
    CLOCKSTATE_PAUSED,                    /* clock is paused              */
    CLOCKSTATE_LOCATE,                    /* go to 'running' when ready   */
    CLOCKSTATE_RUNNING,                   /* run clock NOW                */
};

Details of Players:

    pi_Hook: This is a pointer to the application's Callback function. The
callback function is always called in the context of a software interrupt.
    (ISSUE: List calling parameters/registers for hooks)
    Also, the Hook structure conveniently contains a Node structure which
is used to maintain a list of PlayerInfos.

    pi_Source: A pointer to the Conductor driving this clock.
    
    pi_Task: The task to signal when a state change occurrs.
    (ISSUE: What about non-task-based sequencers?)
    
    pi_StateSignal: The signal to use when signaling the task that the state
        has changed.

    pi_AlarmSignal: The signal to use when signaling the task that the alarm
        has gone off.
    
    pi_MetricTime: The current time, converted to the application's own
metric. The flag PLAYERF_METRIC indicates whether this field is
filled in by CAMD (to the conductor's current time) or filled in
by the application (either by the tick hook or by the application's
task).

    pi_AlarmTime: The clock time of when to call the hook function next.
    
    pi_Flags:
        PLAYERF_READY: Indicates that the player is ready to run.
        PLAYERF_WAIT: Indicates that the player is waiting for an alarm.
        PLAYERF_CONTINUOUS: Call the TickHook every cycle.
        PLAYERF_METRIC: Indicates that the tick hook will fill in the
            pi_MetricTime
        PLAYERF_NULL: This player is inactive, ignore it.
        PLAYERF_ALARMSIG: Use the signal to alarm the task, not the callback.
        PLAYERF_CONDUCTED: This application wants to receive metered time.
            The player must have a callback hook in CONTINUOUS mode.

    pi_UserData: For application use.
    pi_PlayerID: For application use.

struct PlayerInfo {
    struct Node       pi_Node;              /* player node                 */
    struct Hook       pi_Hook;             /* callback for player          */
    struct Conductor *pi_Source;           /* pointer to parent context    */
    struct Task      *pi_Task;             /* task to signal for changes   */
    ULONG             pi_StateSignal,      /* signal to send for changes   */
                      pi_AlarmSignal;      /* signal to send for alarms    */
    LONG              pi_MetricTime;       /* current time in app's metric */
    LONG              pi_AlarmTime,        /* time to wake up              */
    ULONG             pi_Flags;            /* general PlayerInfo flags     */
    void             *pi_UserData;         /* for application use          */
    LONG              pi_PlayerID;         /* for application use          */
};

#define PLAYERF_READY      (1<<0)     /* player is ready to go!           */
#define PLAYERF_WAIT       (1<<1)     /* call hook when alarm goes off    */
#define PLAYERF_CONTINUOUS (1<<2)     /* call hook every tick             */
#define PLAYERF_METRIC     (1<<3)     /* hook should fill in MetricTime   */
#define PLAYERF_NULL       (1<<4)     /* a dummy player, used for    sync */
#define PLAYERF_ALARMSIG   (1<<5)     /* use signal for alarm, not callback */
#define PLAYERF_CONDUCTED  (1<<6)     /* give me metered time              */

Using the callback hook:
    If the CONDUCTED flag is set
        if the conductor's METROSET is NOT set:
            convert cdt_ClockTime to cdt_Metronome
            set METROSET
        convert cdt_Metronome to pi_MetricTime
    else if the PLAYERF_METRIC flag is set
        convert cdt_ClockTime to pi_MetricTime
    else ;

    if (MetricTime > (some time, say AlarmTime)) then wake up application...

A note on MIDI Clocks:

    Since CAMD is a MIDI driver, it should be able to respond either to
MIDI clocks or MIDI time code. Actually, it's probably not possible to
interpret MIDI clocks, since they are really dependent on the application's
interpretation of tempo. Instead, these should probably just be passed on
to the application directly. We _might_ be able to help out by recording the
time interval between MIDI clocks so that the application can interpolate
the tempo.
    MIDI Time Code, on the other hand, is a lot like SMPTE. Basically,
CAMD should be able to convert a MIDI Time Code event into a frame number
internally, and drive the correct clock with it. It will need a library
interface to set up the variables (autostart, etc.), although we _could_
pass those things on to the application and let them handle it.
    In addition, the App should have the option of processing the MTC
itself, in case they decide that they aren't using our clock.

    MIDI Start and Stop should be handled by the application, probably. It
can decide if it wants to start the clock or not.

Application-Specific Time:
    It is the job of the application to convert CAMD's clock times into
it's own 'Metric', or local format, but CAMD provides some help in
this regard.
    This section doesn't describe any specific features of CAMD, but
rather contains some advice on how to get tempos that are accurate over
long periods of time (important for film and video work).
    I have found that the most accurate way to convert time from one
format to another is to not deal with the time values directly, but
only with their delta-values. Basically, when a new clock time arrives,
subtract it from the previous clock time to get the delta time.
Then do the neccessary multiplication and division to get the converted
delta time. Be careful to save any leftover remainders, and accumlate them
into the total.

    Example:

        input_delta = clock_time - last_time;
        last_time = clock_time;
        
        output_delta = (input_delta * k1 + last_rmainder) / k2;
        last_remainder = (input_delta * k1 + last_remainder) % k2;
            /* (actually you would just do this in assembly and take
                the remainder of the divide).
            */

        local_time += output_delta;

    This method allows for expremely accurate timing over the long term.
With this formula, assuming that tempos are stored in 32-bit fixed-point
precision (16 bit integer, 16 bit fraction), you could have a song 23
minutes long, and adjusting the tempo up or down by the smallest possible
increment would allow you to change the timing of the final beat of the
song by one film frame! (That's if I have my calculations right, of
course). I think that this will satisfy the film-scoring people well enough.

    Note that the tempo conversion routines should probably _not_ be called
when locating, as the locator will want to adjust the pi_MetricTime in
leaps and jumps as it locates through the song.

    In addition, the TickHook should also do what's needed for actually
playing back the notes. In my cases, I send a signal to a high-priority
task, telling it to wake up and play the next batch of notes, if needed
(actually, the UserData points to a structure which tells it at what metric
time it should wake up the task... as a result, the task is not woken up
uneccesarily).

=================== Library Function Protypes ===========================

    /* ---- list access */
void LockCAMD(ULONG);
void UnlockCAMD(ULONG);

    /* ---- MidiNode */
struct MidiNode *CreateMidiA(STRPTR, struct TagItem *);
struct MidiNode *CreateMidi(STRPTR, Tag tag, ...);
void DeleteMidi (struct MidiNode *);
BOOL SetMidiAttrsA(struct MidiNode *, struct TagItem *);
BOOL SetMidiAttrs(struct MidiNode *, Tag tag, ...);
BOOL GetMidiAttr(struct MidiNode *, LONG, void *, LONG);
struct MidiNode *NextMidi(struct MidiNode *);
struct MidiNode *FindMidi(STRPTR);
void FlushMidi (struct MidiNode *);

    /* ---- MidiLink */
struct MidiLink *AddMidiLinkA(struct MidiNode *, STRPTR, LONG, 
                                              struct TagItem *);
struct MidiLink *AddMidiLink(struct MidiNode *, STRPTR, LONG, Tag tag, ...);
BOOL SetMidiLinkAttrsA(struct MidiLink *, struct TagItem *);
BOOL SetMidiLinkAttrs(struct MidiLink *, Tag tag, ...);
BOOL GetMidiLinkAttr(struct MidiLink *, LONG, void *, LONG);
void RemoveMidiLink(struct MidiLink *);
struct MidiLink *NextClusterLink(struct MidiCluster *, struct MidiLink *, BOOL);
struct MidiLink *NextNodeLink(struct MidiNode *, struct MidiLink *, BOOL);
BOOL MidiLinkConnected(struct MidiLink *);

    /* ---- MidiCluster */
struct MidiCluster *NextCluster(struct MidiCluster *);
struct MidiCluster *FindCluster(STRPTR);

    /* ---- Message */
void PutMidi (struct MidiLink *, LONG);
void PutSysEx (struct MidiLink *, UBYTE *);
BOOL GetMidi (struct MidiNode *, MidiMsg *);
BOOL WaitMidi (struct MidiNode *, MidiMsg *);
ULONG GetSysEx (struct MidiNode *, UBYTE *, ULONG);
ULONG QuerySysEx (struct MidiNode *);
void SkipSysEx (struct MidiNode *);
UBYTE GetMidiErr (struct MidiNode *);
WORD MidiMsgType (MidiMsg *);
WORD MidiMsgLen (ULONG StatusByte);

    /* ---- Conductors */
struct PlayerInfo *CreatePlayerA(STRPTR,STRPTR,struct TagItem *);
struct PlayerInfo *CreatePlayer(STRPTR,STRPTR,Tag tag,...);
void DeletePlayer(struct PlayerInfo *);
BOOL SetPlayerAttrsA(struct Player *, struct TagItem *);
BOOL SetPlayerAttrs(struct Player *,Tag tag,...);
BOOL SetConductorState(struct Player *, LONG, LONG);
BOOL ExternalSync(struct PlayerInfo *, LONG, LONG);

    /* ---- Devices */
struct MidiDeviceData *OpenMidiDevice (UBYTE *Name);
void CloseMidiDevice (struct MidiDeviceData *);

=================== Some Important Structures ===========================

/***************************************************************
*
*   MidiLink -- links a cluster and a MidiNode
*
*   All fields are READ ONLY.  Modifications to fields may
*   performed only through the appropriate library function
*   calls.
*
***************************************************************/

struct MidiLink {
    struct Node          ml_Node;            /* node for cluster list */
    WORD                 ml_Pad;
    struct MinNode       ml_OwnerNode;       /* node for interface list */
    struct MidiNode     *ml_MidiNode;        /* interface we belong to... */
    struct MidiCluster  *ml_Location;        /* location we are a member of */
    char                *ml_ClusterComment;  /* comment for cluster    */
    UBYTE                ml_Flags;           /* general flags          */
    UBYTE                ml_PortID;          /* number of this port    */
    UWORD                ml_ChannelMask;     /* mask flags for channel */
    ULONG                ml_EventTypeMask;   /* mask flags for events  */

    /* Sys/Ex Filter */
    union SysExFilter 
    {
    UBYTE            b[4];                    /* 1 byte mode, 3 bytes for match id(s) */
    ULONG            sxf_Packed;
    } ml_SysExFilter;

    APTR            ml_UserData;            /* attached to events... */
};

    /* Types of links (used for creation) */

enum {
    MLTYPE_RECEIVER=0,
    MLTYPE_SENDER
};

#define MLF_SENDER         (1<<0)                /* this link sends from app */
#define MLF_PARTCHANGE     (1<<1)                /* part change pending */
#define MLF_PRIVATELINK    (1<<2)                /* make this link private */

#define MLINK_Base    TAG_USER+64

#define MLINK_Location     (MLINK_Base+1)   /* rendezvous point     */
#define MLINK_ChannelMask  (MLINK_Base+2)   /* channel mask bits    */
#define MLINK_EventMask    (MLINK_Base+3)   /* event type mask bits */
#define MLINK_UserData     (MLINK_Base+4)   /* user data for incoming events */
#define MLINK_Comment      (MLINK_Base+5)   /* comment for cluster           */
#define MLINK_PortID       (MLINK_Base+6)   /* port id number for MidiMsg    */
#define MLINK_Private      (MLINK_Base+7)   /* not a public link    */
#define MLINK_Priority     (MLINK_Base+8)   /* priority of node     */
#define MLINK_SysExFilter  (MLINK_Base+9)   /* three 1-byte headers */
#define MLINK_SysExFilterX (MLINK_Base+10)  /* one 3-byte header    */

/***************************************************************
*
*   MidiNode
*
*   All fields are READ ONLY.  Modifications to fields may
*   performed only through the appropriate library function
*   calls.
*
***************************************************************/

struct MidiNode 
{
    struct Node      mi_Node;             /* linked list node      */
    UWORD            mi_ClientType;       /* type of application   */
    struct Image    *mi_Image;            /* image for patch panel */

    struct MinList   mi_OutLinks,         /* list of output links  */
                     mi_InLinks;          /* list of input links   */
    
    struct Task     *mi_SigTask;          /* task to signal        */
    struct Hook     *mi_ReceiveHook,      /* hook (and list node)  */
                    *mi_ParticipantHook;  /* hook for participant change    */
    BYTE             mi_ReceiveSigBit,    /* signalmask for new data        */
                     mi_ParticipantSigBit;/* signalmask for part change     */
    UBYTE            mi_ErrFilter;        /* CMEF_ error filter for ErrFlags*/
    UBYTE            mi_Alignment[1];     /* for longword alignment         */

    ULONG           *mi_TimeStamp;        /* where timestamps come from     */

    ULONG            mi_MsgQueueSize,     /* size of buffers       */
                     mi_SysExQueueSize;

    /* private stuff below here */
};

    /* client types */

#define CCType_Sequencer          (1<<0)
#define CCType_SampleEditor       (1<<1)
#define CCType_PatchEditor        (1<<2)
#define CCType_Notator            (1<<3)        /* transcription of MIDI notes    */
#define CCType_EventProcessor     (1<<4)        /* any data altering functions    */
#define CCType_EventFilter        (1<<5)
#define CCType_EventRouter        (1<<6)        /* e.g MIDI thru task */
#define CCType_ToneGenerator      (1<<7)        /* i.e.using Amiga to make sound*/
#define CCType_EventGenerator     (1<<8)        /* e.g algorithmic composition */
#define CCType_GraphicAnimator    (1<<9)        /* e.g syncing animation software to MIDI */


    /* Tags for CreateMidi() and SetMidiAttrs() */

#define MIDI_Base           (TAG_USER+64)
#define MIDI_Name           (MIDI_Base+1)    /* name of application */
#define MIDI_SignalTask     (MIDI_Base+2)    /* task to signal if not this one*/
#define MIDI_RecvHook       (MIDI_Base+3)    /* hook for incoming data */
#define MIDI_PartHook       (MIDI_Base+4)    /* hook for participant change */
#define MIDI_RecvSignal     (MIDI_Base+5)    /* signal to use if incoming */
#define MIDI_PartSignal     (MIDI_Base+6)    /* signal to use if incoming */
#define MIDI_MsgQueue       (MIDI_Base+7)    /* size of event buffer */
#define MIDI_SysExSize      (MIDI_Base+8)    /* size of sysex buffer */
#define MIDI_TimeStamp      (MIDI_Base+9)    /* timer to timestamp with */
#define MIDI_ErrFilter      (MIDI_Base+10)   /* error filter bits */
#define MIDI_ClientType     (MIDI_Base+11)   /* client type mask */
#define MIDI_Image          (MIDI_Base+12)   /* application image */

    /* SysExFilter members */
#define sxf_Mode b[0]        /* SXFM_ mode bits */
#define sxf_ID1  b[1]        /* 3 1-byte id's or 1 3-byte id */
#define sxf_ID2  b[2]
#define sxf_ID3  b[3]

=========================== Application Notes ============================

Locating:

    On some applications, locating is very simple: Just take the current
conductor time, convert to to a local MetricTime, and poke it into the
correct variable.
    However, for some MIDI applications, life is not so simple. Because the
playback of a sequence can cause many different state changes, both to
external MIDI equipment and the sequencer's internal state, it is not easy
to determine the total "state" of the sequencer for any particular time
without actually playing the sequence. So, many sequencers do just that --
they "play" the sequence (silently, with notes supressed) to themselves as
fast as they can, until the silent playback reaches the point at which the
current clock setting. At that point, they enable note playing and follow
along with the other participants at normal speed.
    In a multi-track sequencer, this can be a complex process. One
technique is, each time a track event is processed, the track with the next
lowest non-processed event is processed. If the tracks are in the
application's local metric (which they probably are) this can be even more
complex. Either way, the "position time" is set to the time of the last
processed event. This represents the clock time at which the sequencer is
really positioned to at that instant.
    A potential problem lies in the fact that the time to do this is
non-zero. Two things can happen: 1. The clock starts running before the
sequencer is ready (before it has reached the start point), in which case
the sequencer will have even further to go before it can start playing for
real. In this case, the first couple of notes from that sequencer will be
absent, since it was busy catching up and couldn't play them.
    The other situation is when the sequencer is busy trying to locate, and
some other agency changes the clock setting out from under it. This is
especially serious if reading from videotape and we have batches of
non-continuous timecode (In other words, they haven't re-striped the tape
after assembling the individual scenes). (Note that a "smart" time code
reader could have translation tables to overcome this problem, but that's
another product entirely).
    If the new clock setting is the same as the old one, then there's no
problem. If the new clock setting is _later_ than the old one, then there's
also no problem -- the sequencer can continue locating, but this time it
just goes on past the old location and proceeds to the new one. If the new
location is _slightly_ earlier than the old one, then it's a toss-up -- the
sequencer could, if it wanted, simply declare "close enough" and flag
itself as 'ready'. When the clock actually starts running, the sequencer
will be waiting around a bit while the others catch up, since it got
located too far ahead. When the others catch up, they will all be in sync.
Note that this technique should _not_ be used when starting at the
beginning of the song, since we want everybody together on the first note.
If starting from the middle of a song, it's not so serious.
    Finally, if the new location is significantly earlier than the old
location, the sequencer will have no choice but to start the location
process all over again. Tough luck. (Fortunately, this case will be detected
instantly, because the equation which terminates the location process
(positiontime >= starttime) will suddenly and unexpectedly be true).
    What that means is simple: If, during a locate, someone changes the
time, a new LOCATE or RUNNING signal will be sent to all applications...
but if your application is still in the locating process, you can pretty
much ignore it. (Use SetSignal to check and reset the signal...)

    Another problem arises in that for many music-applications, the
relationship between the CAMD time and the applications local time format
is not always linear. An example of this is tempo change, either "instant"
tempo changes, or gradual tempo changes (accelerando and ritardando).
    Essentially, the relationship between musical time and real time
is mathematically equivalent to taking the value of tempo (changing or
not) and performing an integration on it, with respect to time. Doing
this in real-time isn't always easy. It's especially difficult because
the kinds of tempo changes the human ear likes to hear best are (e^x)
curves -- the same curve you get when you have compound interest that
is compounded continuously.
    Another way of looking at it is that the tempo should change linearly
wth respect to METERED time, but not linearly with respect to REAL time.
(Since the tempo is changing, the relationship between real and metered
is not linear).
    Doing this during playback is actually pretty easy -- each time
you get a new clock tick in the local meter, update the tempo by some
fixed amount. It's during a locate where the situation gets tough.
    This is because there are lots of cases during the locate process where
you will want to know the Conductor time for a given Metered Time
(the reverse of the usual conversion). For example, if your next event
in the queue is at some particular metered time, you want to know how far
forward to wind the conductor to get there.
    Unfortunately, the simple summation technique described above only
works in one direction. You can't use it to convert the other way...even
in the simplest case (flat tempo) the numbers aren't going to come out
exactly the same, which means that you are going to locate to just slightly
the wrong place and be just slightly out of sync.
    The best advice I can give at this point is to study your logarithms.
You can get good tempo changes by interpolating over a table of logs, and
you can get very accurate logarithms in only a few machine instructions,
if you take logs to the base 2.

User Interface issues:
    What should the interface be for getting applications together on
a Conductor group? What determines whether an application is independent
or joins with a sync group?
=========================================================================
Issues that haven't been completely addressed: (Note that the old
CAMD didn't address these issues either).

    Several people seem to want the ability to have a setup involving a
filter type module, where if the filter is not present, it goes right
"through" and connects directly to what the filter would have connected to.
    This becomes more complex when we think about clusters, because what
the filter was connected "to" may be less well defined. Perhaps it's
better to say "If this application is talking to nobody, then here's
another place where we might find some listeners..."
    Another way to handle it might be to replace the filter with a
"thru"-style connection, which gets supplanted by the application.
    So then what we need is 1) A utility that will establish "thru" links.
2) A way of making thru links be supressed whenever a certain other link
is established.

    Which brings us to the second issue: Thru processing -- the ability to
set up a direct copying process between two clusters. This can be done
with a "callback" style of receiver, which just echoes each posting to the
sender.
