home *** CD-ROM | disk | FTP | other *** search
-
- /*
- MIDI.C -- Midi Function interface
-
- */
- #ifdef DOC
-
- MPU-401 MIDI Interface Module v1.0
-
- Copyright (c) 1991, Robin Davies.
-
- DESCRIPTION
-
- This module provides basic MIDI message handling for IBM-PC systems
- equipped with a Roland MPU-401 or compatible MIDI interface card.
-
- It provides interrupt driven Recording and playing and directly supports
- most of the MPU-401 message filtering options.
-
- Up to four MPU-401's can be driven simultaneously.
-
- This module allows MIDI messages to be received and transmitted.
-
- Received messages are automatically timestamped.
-
- Transmitted messages may either be sent as scheduled messages (i.e. sent
- at a specified time), or sent immediately.
-
- Scheduled messages must be sent at least 240 ticks before they are
- scheduled to be sent. There is no mechanism to slow down the transmission
- process, so client applications must be careful to throttle themselves to
- prevent all free memory from being used for midi messages. The best way
- to do this is something like the following:
-
- if (MidiMessagesPending(midiChannel) < 300) // or some arbitrary #
- SendNextMidiMessage(channel);
-
- This module has been compiled and tested under Turbo-Pascal 2.0. with
- a variety of different programs, although it is far from completely
- tested.
-
- Three test programs, READTEST.C, WRITETST.C, and DELAYTST.C,
- are included as sample programs.
-
-
- FUNCTION SUMMARY
-
- CreateMidiChannel -- Create a channel for sending and receiving
- midi messages.
- DestroyMidiChannel -- Destory the channel, and all resources used by
- the channel (i.e. pending midi messages, turns off the MPU-401,
- removes interrupt handlers, etc).
- SetMidiOperatingMode -- Change the operating mode of a midi channel
- (to record, play, record/play, or stop).
-
- MidiStatus -- Returns the current error status of a midi channel.
- MidiErrorString -- Returns an ASCII string describing the meaning of
- an error code returned by MidiStatus.
-
- AllocMidiMessage -- Allocate a midi message block (in preparation for
- filling it in and sending it).
- FreeMidiMessage -- Return a midi message block to the midi message
- pool.
- SetMidiMessage -- Fill a midi message block with data.
- GetMidiMessageData -- Get a copy of the data in a message block.
-
- SendMidiMessage -- Send a midi message immediately.
- ScheduleMidiMessage -- Schedule a midi message for sending at a future
- time.
- ReceiveMidiMessage -- Get the next received midi message (or returns
- NULL if no messages are waiting).
-
- MidiMessagePending -- Returns the number of scheduled midi messages which
- have not yet been sent.
-
-
- DATA TYPES
-
- The philosophy of this module is vaguely object oriented. The actual
- contents of data structures are considered private to this module.
- Client routines should have no need to access struture members directly
- since access routines are provided to do so where appropriate. If you
- really need to access a structure member, write an access routine in
- this module to do so. This will minimize problems whith future versions
- of this module.
-
-
- MidiTimeT -- Time in ticks after playing or recording started.
- Currently a long integer.
-
- MidiMessageT -- The object which contains all data associated with
- a midi message.
-
- MidiChannelT -- Midi channel control block. Contains all information
- associated with a midi communications channel.
-
-
-
- HOW TO USE THIS MODULE
-
- MIDI.H contains all typedefs, defines and function prototypes required
- by clients of MIDI.C.
-
- MIDI.C must be linked with the module MPU.C. The module MPU.C must be
- compiled with register optimization disabled, and stack checking
- disabled.
-
- This module was compiled using Turbo C++ v1.0. It should work without
- modification with Turbo C 2.0. It should work with Microsoft C equally
- well, with minor tweaking, but I don't have a copy of MSC handy to do
- the actual port. Feel free to chip in.
-
- Three sample applications have been included: READTEST.EXE and WRITETST.EXE.
- They may be built with the supplied MAKEFILE.MAK and Turbo MAKE.EXE.
-
-
- ABOUT THE DOCUMENTATION
-
- The documentation for this project was written using a simplified version
- of Donald Knuth's literate programming style. The documentation was
- written in the source code, and stripped out afterward using the utility
- CDOC.EXE (included in this arc file).
-
- Documentation is interwoven between actual source code by using enclosing
- documentation in "ifdef DOC" and "endif" tag lines. The program CDOC.EXE
- will filter out the documentation in a source file as follows:
-
- CDOC.EXE <infile >outfile
-
- The reason why this is done is to ensure that documentation accurately
- reflects the current status of the code. If you are going to modify this
- code, I would ask you to update the documentation in the source code(!)
- when you do so. The HISTORY section of this document (below in the source
- code) provides a good place for you to record the changes you made to
- the sources.
-
- If you follow this practice, then you will be able to generate up-to-date
- documentation quickly and easily.
-
- The MPU module also uses a more advanced form of literate programming
- style which automatically inserts function prototypes into the
- documentation, but I am unfortunately not able to distribute the program
- which supports this, and am not willing to manually insert the
- prototypes.
-
- RELATED FILES
-
- MIDI.H -- Contains defines and prototypes for clients of this module.
-
- MPU.C -- This module must be linked with MPU.C.
-
- MIDINAME.C .H -- Module which provides ascii translations of
- midi messages.
-
- READTEST.C -- A sample program which demonstrates usage of
- the MIDI.C module. It provides an ASCII dump of all
- messages read from the MIDI-IN port of the MPU-401.
-
- WRITETST.C -- A sample program which demonstrates usage of the
- MIDI.C module. It sends random notes to the MIDI-OUT port of
- the MPU-401. Sounds kinda nice with bell-like patches.
-
- RELATED DOCUMENTS
-
- 1) Midi Processing Unit MPU-401 Technical Reference Manual, version 1.5
- (5/29/85), Roland Corporation, 1985. (Available directly from Roland for
- a nominal fee).
-
- BUGS
-
- SYSEX messages, and Common messages may be sent as immediate messages
- only. They may NOT be sent using SendScheduledMessage.
-
- Due to the way that the MPU-401 works, System Common messages may or may
- not work reliably. The MPU-401 handles most Common messages other than
- SYSEX messages itself as part of the Record/Play sequence.
-
- The parser looks like it needs some work. In particular, it will not
- correctly parse MPU measure end marks.
-
-
- STATEMENT OF COPYRIGHT
-
- Copyright (c) 1991, Robin Davies. All Rights Reserved.
-
- You may use or modify this source code freely as long as the following
- conditions are met:
-
- (1) You may distribute object code derived from this work without
- displaying copyright notices of any kind. However, if this work or
- derivatives of this work are distributed in source form, this
- copyright notice must be left intact.
-
- If you wish to express your gratitute in any way (such as sending me
- a complimentary copy of *your* work), you may reach me at:
-
- Robin Davies
- 224 3rd Avenue
- Ottawa, Ontario
- Canada. K1S 2K3.
-
-
- DISCLAIMER OF LIABILITY
-
- If these programs, or programs based on this source code get up and
- burn your house down in the middle of the night, I accept no
- responsibility.
-
- This source has not been fully tested. It may in fact have terrible bugs
- in it still. It is provided "as-is", and without warranty, either express
- or implied, and no representations are made as to its fitness for a
- particular purpose. You have been warned, so don't come crying to me.
-
- However, if you do find any bugs, do let me know, and I'll see what *we*
- can do to fix them.
-
- If you find anything else wrong, or have suggestions for improvements,
- please contact me at 73520,1736, since Robin (the author of the original
- post) has indicated that he is not currently using these sources.
-
- - Larry Troxler
-
-
- HISTORY
-
- 1/5/91 - Version 1.0 Posted on Compuserve for the first time.
-
- 5/6/92 - Larry Troxler
- Compuserve: 73520,1736
- Bix: ltroxler
- Internet: 73520.1736@compuserve.com
-
- I did some work on the scheduler. Also I
- had to increase the timeout constants (see MPU.C), and it
- appears the incoming time-stamps were missing.
-
- - lt
-
- 06Jul93 - Robin Davies
-
- Officially ammended the copyright notice. Taken from
- MPUSR2.ARC on Compuserve, repackaged as MPUSR2.ZIP,
- and posted on internet at ftp.ircam.fr. A copy of the
- ammended copyright notice was forwarded to
- Larry Troxler.
-
-
-
- #endif
-
-
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <mem.h>
- #include <dos.h>
-
- #include "std.h"
- #include "mpuregs.h"
- #include "mpu.h"
-
- #define MIDI_PRIVATES
- #include "midi.h"
-
- // Manifest constants
-
- #define MIDI_MESSAGE_ALLOC 100 /* Number of MidiMessageT's to alloc at one time */
- #define INITIAL_SYSEX_BLOCK_LENGTH 1024 /* Initial SYSEX block length */
-
- BYTE MidiCommandLength[128] = {
- 3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 80
- 3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 90
- 3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // A0
- 3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // B0
-
- 2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2, // C0
- 2,2,2,2, 2,2,2,2, 2,2,2,2, 2,2,2,2, // D0
-
- 3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // E0
-
- 1,0,3,2, 0,0,1,0, 0,0,1,1, 1,0,0,0 // F0
- };
-
- #ifdef PDOC
-
- DESCRIPTION
-
- This routine deallocates sysex data memory which has been sent using
- midi command scheduling. The problem is that sysex data can't be deallocated
- from the interrupt service routine. Therefore a free chain is built by
- the interrupt service routine so that the memory can later be deallocated
- under more controlled circumstances.
-
- All of this is moot since SYSEX data can't currently be sent through
- scheduled midi commands. (I can't figure out what to do if the
- SYSEX message should collide when the REQUEST_TO_SEND_SYSEX_DATA_CMD
- command is send).
-
- I know there *is* a way to handle scheduled sysex data (interestingly
- Cakewalk won't). However, for the meantime, this code is left, since
- it may well be required eventually.
-
- You may also wan't to take a look at the rather peculiar, but peculiarly
- ANSI redefinitions of calloc and malloc in MIDI.H which also relate to
- this problem.
-
- -rd
-
- #endif
- typedef struct FreeSysexDataT {
- struct FreeSysexDataT *next;
- } FreeSysexDataT;
-
- FreeSysexDataT *FreeSysexData = NULL;
-
- void ReclaimSysexMemory(void) {
- FreeSysexDataT *sysexData;
- disable();
- while (FreeSysexData) {
- sysexData = FreeSysexData;
- FreeSysexData = FreeSysexData->next;
- enable();
- free(sysexData);
- disable();
- }
- enable();
-
- }
-
- #ifdef DOC
- PROTOTYPE
-
- MidiMessageT *AllocMidiMessage(void);
-
- DESCRIPTION
-
- Allocates a Midi Message block.
-
- Returns NULL if no more memory.
- #endif
-
-
-
- MidiMessageT *FreeMidiMessages = NULL;
-
- MidiMessageT *MidiMessageBlock = NULL;
- int MidiMessagesInBlock = 0;
-
- MidiMessageT *AllocMidiMessage(void) {
- MidiMessageT *msg;
-
- disable(); /* Warning: Interrupt may modify free chain -rd */
- if (FreeMidiMessages) {
- msg = FreeMidiMessages;
- FreeMidiMessages = FreeMidiMessages->next;
- enable();
- msg->sysexLength = 0;
- return msg;
- }
- enable();
-
- if (MidiMessagesInBlock == 0) {
- MidiMessageBlock = calloc(MIDI_MESSAGE_ALLOC,sizeof(MidiMessageT));
- if (MidiMessageBlock == NULL)
- return NULL;
- MidiMessagesInBlock = MIDI_MESSAGE_ALLOC;
- }
- --MidiMessagesInBlock;
- return MidiMessageBlock++;
- }
-
- #ifdef PDOC
-
- DESCRIPTION
-
- Free a MidiMessageT. This version is for INTERNAL USE ONLY. It is
- callable from an interrupt service routine. Instead of freeing SYSEX data
- the data blocks are inserted into a free list in order that they can be
- freed later under more controlled circumstances.
-
- #endif
-
-
-
- PRIVATE void _FreeMidiMessage(MidiMessageT *msg) {
- FreeSysexDataT *sysex_data;
-
- if (msg->sysexLength != 0) {
- sysex_data = (void *) (msg->sysexData);
- disable();
- sysex_data->next = FreeSysexData;
- FreeSysexData = sysex_data;
- enable();
- }
- disable(); /* Warning: interrupt may modify free chain! -rd */
- msg->next = FreeMidiMessages;
- FreeMidiMessages = msg;
- enable();
- }
-
- void FreeMidiMessage(MidiMessageT *msg) {
- if (msg->sysexLength != 0) {
- free(msg->sysexData);
- }
- disable(); /* Warning: interrupt may modify free chain! -rd */
- msg->next = FreeMidiMessages;
- FreeMidiMessages = msg;
- enable();
- }
-
- #ifdef DOC
-
- PROTOTYPE
-
- MidiMessageT *ReceiveMidiMessage(MidiChannelT *channel) {
-
- DESCRIPTION
-
- Returns the next midi message.
-
- RETURNS
-
- Returns NULL if no message is ready to read.
- Returns (MidiMessageT *)(-1) if error.
-
- REVISIONS
-
- 5/6/92 by Larry Troxler
- - Set time-stamp of message being returned.
- - When an timer overflow was received, the time was not being advanced
- by the correct amount.
- - Do not send and overflow response to track requests from this routine.
- Track requests are handled in the track request handler.
- #endif
-
- MidiMessageT *ReceiveMidiMessage(MidiChannelT *channel) {
- int data;
- int cmd;
- MpuPortT *mpu;
- MidiMessageT *msg;
- int i;
- int tick;
- int cmd_length;
-
- mpu = channel->mpu_port;
-
- if (!DataReady(mpu)) {
- return NULL;
- }
- data = ReadData(mpu);
- if (data == -1)
- return (MidiMessageT *)(-1);
-
- if (IsMpuMessageNumber(data)) {
- switch (data) {
- case TIMING_OVERFLOW_MSG:
- mpu->currentTime += 240;
- break;
- case SYSTEM_MESSAGE_MSG:
- cmd = ReadData(mpu);
- if (cmd == -1)
- return (MidiMessageT *)(-1);
- tick = 0;
- goto HandleMidiCommand;
- default:
- break;
- }
- return NULL;
- } else {
- // We have a midi command!
- tick = data;
- mpu->currentTime += tick;
- cmd = ReadData(mpu);
- if (cmd == -1)
- return (MidiMessageT *)(-1);
- if (cmd >= 0xF0) {
- return NULL;
- } else {
- HandleMidiCommand:
- if (cmd < 0x80) // is it a running status message?
- {
- UngetData(mpu,cmd);
- cmd = mpu->lastCommand;
- } else {
- mpu->lastCommand = cmd;
- }
- if (cmd < 0x80)
- cmd_length = 1; /* We're in trouble */
- else
- cmd_length = MidiCommandLength[cmd-0x80];
- msg = AllocMidiMessage();
- if (msg == NULL) {
- channel->errno = MIDI_ENOMEM;
- return (MidiMessageT *)(-1);
- }
- msg->midiCommand = cmd;
- --cmd_length;
- for (i = 0; i < cmd_length; ++i) {
- msg->midiData[i] = data = ReadData(mpu);
- if (data == -1)
- return (MidiMessageT *) (-1);
- }
- if (cmd == SYSEX_MSG) {
- unsigned int sysex_block_size;
- unsigned int sysex_length;
- UCHAR *sysex_data,*sysex_ptr;
- //Ohboy! Allocate a trial block. We'll grow it if we have to.
- // We'll shrink it when we're done.
- sysex_length = 0;
- sysex_block_size = INITIAL_SYSEX_BLOCK_LENGTH;
- sysex_data = calloc(1,sysex_block_size);
- if (sysex_data == NULL) {
- channel->errno = MIDI_ENOMEM;
- return (MidiMessageT *)(-1);
- }
- sysex_ptr = sysex_data;
-
- while (data = ReadData(mpu), data != -1 && data != 0xF7)
- {
- *sysex_ptr++ = data;
- if (++sysex_length >= sysex_block_size) {
- sysex_block_size += INITIAL_SYSEX_BLOCK_LENGTH;
- sysex_data = realloc(sysex_data,sysex_block_size);
- if (sysex_data == NULL) {
- channel->errno = MIDI_ENOMEM;
- return (MidiMessageT *)(-1);
- }
- sysex_ptr = sysex_data+sysex_length;
- }
- }
- *sysex_ptr++ = data;
- ++sysex_length;
- sysex_data = realloc(sysex_data,sysex_length); // trim the block back to size
- msg->sysexData = sysex_data;
- msg->sysexLength = sysex_length;
- }
- }
- }
- msg->time = mpu->currentTime;
- return msg;
- }
-
- #ifdef PDOC
-
- DESCRIPTION
-
- This routine handles requests for track data from the interrupt service
- routine.
-
- The next MidiMessage is scheduled and sent here. Note that for precise
- scheduling, Midi Events must be posted at least 240 ticks prior to their
- actual occurrence.
-
- Warning: this routine gets executed in the interrupts service routine!
-
- REVISIONS
-
- 5/6/92 Larry Troxler
- - If there are no messages queued, keep things alive by sending an MPU
- timer overflow, and adjust the track time accordingly.
- -In the case where a message is taken from the queue and sent, adjust the
- queue head pointer and the time.
- #endif
-
- PRIVATE void HandleNextTrackRequest(void *handler_data,int track) {
- MidiChannelT *channel = handler_data;
- MpuPortT *mpu;
- MidiMessageT *msg;
- MidiTimeT time_to_next_msg;
- int data_length;
- int i;
-
- mpu = channel->mpu_port;
-
- msg = channel->sendQ[track]; // fetch first pending message
- if (msg == NULL)
- {
- /* - lt */
- SendData(mpu,0xf8);
- channel->qTime[track] += 240;
- return;
- }
- time_to_next_msg = msg->time - channel->qTime[track];
- if (time_to_next_msg < 0)
- time_to_next_msg = 0;
- if (time_to_next_msg >= 240) {
- SendData(mpu,0xF8); // MPU MARK. track timer <- 240
- channel->qTime[track] += 240;
- } else {
- SendData(mpu,(UCHAR)time_to_next_msg);
- SendData(mpu,msg->midiCommand); // Note: MPU handles runnning status!
-
- data_length = MidiCommandLength[msg->midiCommand-0x80]-1;
- for (i = 0; i < data_length; ++i)
- SendData(mpu,msg->midiData[i]);
-
- channel->qTime[track] = msg->time; /* - lt */
- channel->sendQ[track] = msg->next; /* -lt */
- _FreeMidiMessage(msg); // Version of same which can be called from an interrupt
- --channel->midiMessagesPending;
- }
- }
-
-
- #ifdef DOC
- PROTOTYPE
-
- void ScheduleMidiMessage(
- MidiChannelT *channel,
- int track,
- MidiTimeT time, // Elapsed time in ticks since playback started
- MidiMessageT *msg
- );
-
- DESCRIPTION
-
- Sends a scheduled midi VOICE message. The supplied time is in ticks after the
- start of playback.
-
- Events may be scheduled BEFORE playback has actually started in order to
- get ahead of the MPU.
-
- Also note there is no throttling mechanism for the event queue. If you
- call this routine too fast you will eventually run out of free memory
- (used to allocate MidiMessageT blocks). You may want to hold off a bit
- by checking to how far ahead of the Track time counter you are.
-
- Messages are freed once they are transmitted. Once the message has been
- scheduled, you may not access it again!
-
- BUGS
-
- Sysex messages and common messages may not be scheduled! Sysex messages
- may only be sent directly.
- #endif
-
-
- void ScheduleMidiMessage(
- MidiChannelT *channel,
- int track,
- MidiTimeT time, // Elapsed time in ticks since playback started
- MidiMessageT *msg
- ) {
- MidiMessageT *head, *tail;
- msg->time = time;
- ++channel->midiMessagesPending;
- disable();
- if ((head = channel->sendQ[track]) == NULL) {
- msg->next = NULL;
- channel->sendQTail[track] = msg;
- channel->sendQ[track] = msg;
- enable();
- return;
- }
- // First check to see if it goes at the end of the queue.
- if ((tail = channel->sendQTail[track])->time <= time) {
- tail->next = msg;
- msg->next = NULL;
- channel->sendQTail[track] = msg;
- enable();
- return;
- }
- // Check to see if we are first in the queue
- if (head->time > time) {
- msg->next = head;
- channel->sendQ[track] = msg;
- enable();
- return;
- }
- // Otherwise search down the queue until we find the p
- while (head->next->time <= time)
- head = head->next; // Note: if we hit null then we would have caught it in 1st test!
- msg->next = head->next;
- head->next = msg;
- enable();
- return;
- }
-
-
- #ifdef DOC
-
- PROTOTYPE
-
- BOOL SendMidiMessage(MidiChannelT *channel,MidiMessageT *msg);
-
- DESCRIPTION
-
- Sends a message immediately. The message is freed before returning(!). Once
- the message has been sent, you may not access it again.
-
- RETURNS
- YES if success.
- NO if failure.
- Call GetMidiStatus to receive error code.
- #endif
-
- BOOL SendMidiMessage(MidiChannelT *channel,MidiMessageT *msg) {
- MpuPortT *mpu;
- unsigned int i;
- unsigned int length;
- int cmd;
- UCHAR *sysex_ptr;
-
- mpu = channel->mpu_port;
-
- if (msg->midiCommand >= COMMON_MSG) {
- SendCommand(mpu,REQUEST_TO_SEND_SYSTEM_MSG_CMD);
- length = msg->sysexLength;
- sysex_ptr = msg->sysexData;
- SendData(mpu,msg->midiCommand);
- for (i = 0; i < length; ++i) {
- SendData(mpu,*sysex_ptr++);
- }
- if (sysex_ptr[-1] != EOX_CMD)
- SendData(mpu,EOX_CMD);
- FreeMidiMessage(msg);
- } else {
- SendCommand(mpu,REQUEST_TO_SEND_DATA_CMD+7); // On track 8
- cmd = msg->midiCommand;
- SendData(mpu,cmd);
- length = MidiCommandLength[cmd-0x80]-1;
- for (i = 0; i < length; ++i) {
- SendData(mpu,msg->midiData[i]);
- }
- FreeMidiMessage(msg);
- }
- return YES;
-
- }
-
- #ifdef DOC
-
- PROTOTYPE
-
- void SetMidiOperatingMode(
- MidiChannelT *channel,
- MpuOperatingModeT operating_mode
- );
-
- DESCRIPTION
-
- Set current midi channel operating mode.
-
- One of:
-
- RECORD_MODE,PLAY_MODE,RECORDPLAY_MODE, STOP_MODE
-
- RECORD_MODE: Allows receiving of timestamped midi messages and
- sending of immediate midi messages.
-
- PLAY_MODE: Allows sending of scheduled and immediate midi messages.
-
- RECORDPLAY_MODE: Allows sending of scheduled and unscheduled midi messages,
- and receiving of timestamped midi messages.
-
- STOP_MODE: Stops sending and receiving of messages.
-
- BUGS
-
- CONTINUE should probably also be supported here, but isn't at the
- present time.
- #endif
-
- void SetMidiOperatingMode(
- MidiChannelT *channel,
- MpuOperatingModeT operating_mode
- ) {
- SetMpuOperatingMode(channel->mpu_port,operating_mode);
- }
-
- #ifdef DOC
-
- PROTOTYPE
-
- MidiChannelT *CreateMidiChannel(
- int mpu_base_address, // Mpu base address (default 0x330)
- int mpu_interrupt, // Mpu interrupt number (default 2)
- int rx_buffersize, // Size of receive buffer (default 1024)
- enum MpuOperatingModeT operating_mode, // RECORD_MODE, PLAY_MODE,RECORDPLAY_MODE,STOP_MODE
- enum MpuClockT mpu_clock_source, // MPU_INTERNAL_CLOCK, MPU_MIDI_CLOCK
- // or MPU_FSK_CLOCK
- int tempo, // Beats per minute
- enum MpuTimebaseT timebase, // Ticks per beat (see MpuTimebaseT in MPU.H)
- int metronome_measure_length, // beats per measure, 0 -> metronome off
- int mode, // See description
- int midi_channel_mask, // bit n controls midi channel n+1
- // bit = 0 -> pass trough without host intervention
- // bit = 1 -> record/filter this midi channel
- int tracks, // number of tracks (0 to 7) for scheduled midi messages
- int *result // retcode placed here
- );
-
- DESCRIPTION
-
- Create a MIDI channel.
-
- mpu_base_address, mpu_interrupt, and rx_buffersize will default to
- appropriate values if zero.
-
- If you plan to send scheduled midi messages, it would probably be wise
- to initially set the operating mode to STOP_MODE, schedule some messages,
- and then set the MidiOperatingMode to PLAY_MODE once messages are ready to
- be scheduled.
-
- The mode parameter allows selection of messages to be received and and
- passed through. Any of the following values (ORed together) may be specified:
-
- MPU_VOICES_THRU -- pass all non-common messages directly from
- MIDI-IN to MIDI-OUT. Note that voice messages
- on channels which are masked are passed through
- whether this mode option is specified or not.
- MPU_EXCLUSIVE_THRU -- Pass through Sysex messages
- MPU_REALTIME_THRU -- Pass through realtime (FA,FB,FC) messages
- MPU_COMMON_THRU -- Pass through other Common (F2,F3,F6) messages
-
- MPU_DEFAULT_THRU -- (MPU_VOICES_THRU)
-
- MPU_RX_EXCLUSIVE -- Receive Sysex messages
- MPU_RX_REALTIME -- Receive realtime (FA,FB,FC) messages
- MPU_RX_BENDER -- Receive bender messages
- MPU_RX_MODE -- receive mode messages
- MPU_RX_COMMON -- Receive common (F2,F3,F6) messages
-
- MPU_RX_DEFAULT -- 0. (Voice messages only).
- MPU_RX_ALL -- Receive ALL messages.
-
- midi_channel_mask selects midi channels for which messages should be
- received. Note that if a channel is masked, messages are passed trough to
- MIDI-OUT automatically. This is a problem with the MPU-401. There is no
- way to prevent ALL messages from being passed through without actually
- receiving them.
-
- The MPU-401 supports up to 8 tracks for playback. The 8th track is
- reserved for sending immediate midi messages. Scheduled messages may
- be placed into any of these tracks. The maximum number of tracks which
- will be used for playback should be set at create time. To be
- perfectly honest, I can't think of a compelling reason to use more than
- one track.
-
- If an error occurs, the error code is placed into *result. This error
- message may originate from either the MIDI module or the MPU module.
- The MidiErrorString() routine will return an appropriate ASCII error message
- for either class of error messages.
-
- NOTES
-
- You must call DestroyMidiChannel before exiting your program, since
- interrupt handlers for the MPU-401 are dropped at Create time.
-
- RETURNS
-
- NULL if error, *retval <= error code.
-
- BUGS
-
- There are currently no provisions for count-in measures during record or
- playback. This will (may) be remedied in future versions of this module.
- #endif
-
- MidiChannelT *CreateMidiChannel(
- int mpu_base_address, // Mpu base address (default 0x330)
- int mpu_interrupt, // Mpu interrupt number (default 2)
- int rx_buffersize, // Size of receive buffer (default 1024)
- enum MpuOperatingModeT operating_mode, // RECORD_MODE, PLAY_MODE,RECORDPLAY_MODE,STOP_MODE
- enum MpuClockT mpu_clock_source, // MPU_INTERNAL_CLOCK, MPU_MIDI_CLOCK
- // or MPU_FSK_CLOCK
- int tempo, // Beats per minute
- enum MpuTimebaseT timebase, // Ticks per beat (see MpuTimebaseT in MPU.H)
- int metronome_measure_length, // beats per measure, 0 -> metronome off
- int mode, // See description
- int midi_channel_mask, // bit n controls midi channel n+1
- // bit = 0 -> pass trough without host intervention
- // bit = 1 -> record/filter this midi channel
- int tracks, // number of tracks (0 to 7) for scheduled midi messages
- int *result // retcode placed here
- ) {
- MpuPortT *mpu = NULL;
- MidiChannelT *channel = NULL;
- int i;
-
- channel = calloc(1,sizeof(MidiChannelT));
- if (channel == NULL) {
- *result = MIDI_ENOMEM;
- return NULL;
- }
-
- mpu = CreateMpuPort(
- mpu_base_address,
- mpu_interrupt,
- rx_buffersize,
- STOP_MODE,
- mpu_clock_source,
- tempo,
- timebase,
- metronome_measure_length,
- mode,
- midi_channel_mask,
- result
- );
- if (*result) {
- goto Cleanup;
- }
- channel->mpu_port = mpu;
-
- for (i = 0; i < MAX_MPU_TRACK; ++i) {
- channel->sendQ[i] = NULL;
- channel->sendQTail[i] = NULL;
- channel->qTime[i] = 0;
- }
- mpu->trackRequestHandler = HandleNextTrackRequest;
- mpu->requestHandlerData = channel;
-
- // Enable tracks
- SendCommand(mpu,ACTIVE_TRACK_MASK_CMD);
- SendData(mpu,(0x07F >> (7-tracks)));
-
- SendCommand(mpu,0xB8);
-
- SetMidiOperatingMode(channel,operating_mode);
-
- SendCommand(mpu,SEND_MEASURE_END_OFF_CMD); /* - lt */
- #ifdef JUNK
- SendCommand(mpu,0xFF);
- SendCommand(mpu,0xEC);
- SendData(mpu,0x01);
- SendCommand(mpu,0xB8);
- SendCommand(mpu,0x0A);
-
- #endif
- return channel;
- Cleanup: // Error exit w/ cleanup
- if (mpu)
- DestroyMpu(mpu);
- if (channel) {
- free(channel);
- }
- return NULL;
- }
-
- void CancelScheduledMessages(MidiChannelT *channel) {
- int i;
- MidiMessageT *msg,*next;
-
- for (i = 0; i < MAX_MPU_TRACK; ++i) {
- disable();
- msg = channel->sendQ[i];
- channel->sendQ[i] = 0;
- enable();
- while (msg != NULL) {
- next = msg->next;
- FreeMidiMessage(msg);
- msg = next;
- }
-
- }
- }
-
-
- #ifdef DOC
-
- PROTOTYPE
-
- int DestroyMidiChannel(MidiChannelT *channel);
-
- DESCRIPTION
-
- Closes a midi channel, and deallocates all memory and resources used
- by that channel. Removes the MPU-401 interrupt handler.
-
- RETURNS
-
- 0 -> Success
- non-zero = MidiErrorT error code.
- #endif
-
- int DestroyMidiChannel(MidiChannelT *channel) {
- int status;
- DestroyMpu(channel->mpu_port);
- CancelScheduledMessages(channel);
- free(channel);
- return 0;
- }
-
- #ifdef DOC
-
- PROTOTYPE
-
- BOOL SetMidiMessage(
- MidiMessageT *msg,
- UCHAR midi_command,
- UCHAR data1,
- UCHAR data2,
- unsigned int sysex_length, // Must be zero for non-SYSEX commands!
- UCHAR *sysex_data
- );
-
-
- DESCRIPTION
-
- Set the data of the supplied midi message.
-
- Note that sysex data (if supplied) is copied into an in internal
- memory block allocated from free store. The caller is responsible
- for deallocating the SUPPLIED sysex data (if applicable).
-
- For sysex messages, data1 and data2 are unused. sysex_data must
- point to the first byte of sysex data following the sysex command.
-
- The sequence of operations for sending a midi message:
-
- msg = AllocMidiMessage();
- SetMidiMessage(msg,cmd,data1,data2,0,NULL);
-
- SendMidiMessage(midiChannel,msg);
- or
- SendScheduleMidiMessage(midiChannel,time,msg);
-
- Note that a copy of the sysex data is made in heap memory, that will
- be deallocated when the message is freed (or sent). This may pose a
- problem for large sysex messages.
-
- RETURNS
-
- No -> insufficient memory for copy of sysex data.
-
- #endif
-
- BOOL SetMidiMessage(
- MidiMessageT *msg,
- UCHAR midi_command,
- UCHAR data1,
- UCHAR data2,
- unsigned int sysex_length, // Must be zero for non-SYSEX commands!
- UCHAR *sysex_data
- )
- {
- msg->midiCommand = midi_command;
- msg->midiData[0] = data1;
- msg->midiData[1] = data2;
-
- msg->sysexLength = sysex_length;
- msg->sysexData = NULL;
- if (sysex_length != 0) {
- msg->sysexData = calloc(1,max(sysex_length,sizeof(void *)));
- if (msg->sysexData == NULL) {
- return NO;
- }
- memcpy(msg->sysexData,sysex_data,sysex_length);
- }
- return YES;
- }
- #ifdef DOC
-
- PROTOTYPE
-
- void GetMidiMessageData(
- MidiMessageT *msg,
- UCHAR *midi_cmd, // Receives midi command
- UCHAR *data1, // Receives data1 (if not NULL)
- UCHAR *data2 // Receives data2 (if not NULL)
- );
-
- DESCRIPTION
-
- Get data for current midi message.
-
- The sequence for reading a midi message:
-
- msg = ReceiveMessage(channel);
- if (msg == (MidiMessageT *)(-1)) {
- handleMidiError(MidiStatus(channel));
- return;
- }
- if (msg != NULL) {
- GetMidiMessageData(msg,&cmd,&data1,&data2);
- }
- #endif
-
- void GetMidiMessageData(
- MidiMessageT *msg,
- UCHAR *midi_cmd, // Receives midi command
- UCHAR *data1, // Receives data1 (if not NULL)
- UCHAR *data2 // Receives data2 (if not NULL)
- ) {
- *midi_cmd = msg->midiCommand;
- if (data1 != NULL)
- *data1 = msg->midiData[0];
- if (data2 != NULL)
- *data2 = msg->midiData[1];
- }
-
- #ifdef DOC
-
- PROTOTYPE
-
- int MidiStatus(MidiChannelT *channel);
-
- DESCRIPTION
-
- Returns the error status code of the specified Midi channel.
-
- The only way to clear the error status is to Destroy the channel
- and recreate it.
-
- RETURNS
-
- 0 -> No error
- non-zero = enum MidiErrorT.
- #endif
-
- int MidiStatus(MidiChannelT *channel) {
- if (channel->errno != 0) {
- return channel->errno;
- }
- return GetMpuStatus(channel->mpu_port);
- }
-
- #ifdef DOC
-
- PROTOTYPE
-
- char *MidiErrorString(int error_code);
-
- DESCRIPTION
-
- Returns a string error message corresponding to the specified
- (enum MidiErrorT or enum MpuErrorT) error code as returned by
- either MidiStatus or CreateMidiChannel. Error codes originating
- from either the MIDI module, or the MPU module are handled correctly.
-
- #endif
-
- char *MidiErrorString(int error_code) {
- if (error_code >= LOWEST_MIDI_ERROR) {
- return MidiErrorStrings[error_code-LOWEST_MIDI_ERROR];
- }
- return MpuErrorString(error_code);
- }
-
- #ifdef DOC
- PROTOTYPE
-
- int MidiMessagesPending(MidiChannelT *channel);
-
- DESCRIPTION
-
- Returns the number of scheduled messages which have not been sent
- to the MPU-401.
-
- This function provides a useful technique for controlling the amount
- of memory used by the midi messages queue:
-
- if (MidiMessagesPending(channel) < 300) // or some arbitrary #
- sendNextMidiMessage();
-
- #endif
-
- int MidiMessagesPending(MidiChannelT *channel) {
- return channel->midiMessagesPending;
- }
-
-
- #ifdef DOC
- PROTOTYPE
-
- void GetMidiMessageSysexData(
- MidiMessageT *msg,
- int *sysexLength,
- UCHAR **sysexData // Filled if not null
- );
-
- DESCRIPTION
-
- This routine returns the length and a pointer to the extra data
- bytes sent or received with a sysex message.
-
- The pointer remains valid until the message is sent or freed. (Clients
- should NOT free the pointer themselves).
-
- The first byte of sysex data is the byte which immediately follows the
- Sysex (FF) midi command. The last byte of sysex data must be EOX_MSG (0xF7).
- The contents of data1 and data2 as returned by GetMidiMessageData are
- unspecified when a sysex command is sent or recieved.
-
- sysexLength includes the EOX_MSG byte at the end of the sysex message.
-
- BUGS
-
- In truth, midi sysex messages may be terminated by ANY Midi status byte.
- The current code only handles MIDI sysex messages terminated by EOX_MSG.
- This is probably a fairly major ommission, although I have been
- lucky enough not to have run into a machine which doesn't terminate
- sysex messages with EOX_MSG. You have been warned.
-
- #endif
- void GetMidiMessageSysexData(
- MidiMessageT *msg,
- int *sysexLength,
- UCHAR **sysexData // Filled if not null
- ) {
- *sysexLength = msg->sysexLength;
- *sysexData = msg->sysexData;
- }
-
- #ifdef DOC
-
- PROTOTYPE
-
- MidiTimeT GetMidiMessageReceiveTime(
- MidiMessageT *msg
- );
-
- DESCRIPTION
-
- Returns the time that the message was received by the MPU-401 in ticks
- since recording started. Results are unspecified for messages which
- haven't been obtained through ReceiveMidiMessage().
-
- #endif
-
- MidiTimeT GetMidiMessageReceiveTime(
- MidiMessageT *msg
- ) {
- return msg->time;
- }
-