ftp.nice.ch/pub/next/unix/music/cm.sd.tar.gz#/midi/winmidi.c

This is winmidi.c in view mode; [Download] [Up]

/************************************************************************
   winmidi.c  - Midi DLL for Common Music
   Copyright (c) 1993 Joseph Fosco

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 1, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

   The author may be reached (Email) at the address b38669@anl.gov,
   or (US mail) at 15508 Brianne Lane, Oak Forest, IL  60452
*************************************************************************/

/*------------------------------------------------------------------
   WINMIDI.C -- Dynamic Link Library module for Windows Multimedia
   MIDI FUNCTIONS TO BE CALLED FROM COMMON MUSIC (LISP)
  ------------------------------------------------------------------*/

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h> 

#define MIDI_NOTE_ON			0x90
#define MIDI_NOTE_OFF			0x80
#define MIDI_ACTIVE_SENSING		0xFE

#define FF_MIDI_SUCCESS     0
#define MAX_TIMER_ACCURACY  0
#define MAX_WRITE_QUEUE_ITEMS   4000
#define MAX_READ_QUEUE_ITEMS    4000
#define MAX_SCHED_TIME          0xFFFFFFFF


typedef union {
    DWORD dwMidiMsg;
    BYTE  byMidiByte[4];
    } MIDI_DATA;

typedef struct read_queue_item{
    MIDI_DATA MidiReadData;  /* midi data received                  */
    DWORD dwMidiTime;        /* midi time this message was received */
    WORD  wLongMsg;          /* indicates MIDI_DATA points to a buffer
                                 containing a long midi message (sysex) */
                             /* currently tnis is not used          */
    } READ_QUEUE_ITEM;

typedef READ_QUEUE_ITEM FAR *LPREAD_QUEUE_ITEM;

typedef struct read_queue_hdr{
    LPREAD_QUEUE_ITEM lpReadQueue;         /* Start of Midi In Buffer */
    LPREAD_QUEUE_ITEM lpReadQueueEnd;      /* End of Buffer (Last Byte+1) */
    LPREAD_QUEUE_ITEM lpFirstOpenReadItem; /* Next Item to fill */
    LPREAD_QUEUE_ITEM lpFirstNewReadItem;  /* Next Item to read */
    int               iReadCount;          /* Number of Items in Buffer */
    } READ_QUEUE_HDR;

typedef READ_QUEUE_HDR FAR *LPREAD_QUEUE_HDR;

#if 0
typedef struct write_queue_item{
    MIDI_DATA MidiWriteData;
    DWORD     dwQuantaTime; 
    int       iNextEvent;
    int       iPrevEvent;
    int       iNextOpenItem;
    BYTE      bFill1; 
    BYTE      bFill2; 
    } WRITE_QUEUE_ITEM ;
typedef struct{
    struct write_queue_item Item[];
    } WRITE_QUEUE;
#endif

typedef struct{
    MIDI_DATA MidiWriteData; /* midi data to send                      */
    DWORD     dwQuantaTime;  /* quanta time this event should occur at */
    int       iNextEvent;
    int       iPrevEvent;
    int       iNextOpenItem;
    BYTE      bFill1;       /* Filler to make this structure length      */
    BYTE      bFill2;       /* equal a power of 2 (for allocating > 64k) */
    } WRITE_QUEUE_ITEM ;

typedef struct{
    WRITE_QUEUE_ITEM Item[];
    }WRITE_QUEUE;

typedef struct {
    WORD total_midi_in;
    WORD total_midi_out;
    }TOTAL_MIDI_PORTS_STRUCT ;

WORD FAR PASCAL _export midiclose() ;
void FAR PASCAL _export timer_callback(UINT wID, UINT wMsg,
                                       DWORD dwUser, DWORD dw1, DWORD dw2) ;
void FAR PASCAL _export midi_in_callback(HMIDIIN hMidiIn, WORD wMsg,
                                          DWORD dwInstance,
                                          MIDI_DATA MidiInMsg,
                                          DWORD dwParam2);
void FAR PASCAL _export midi_out_callback(HMIDIOUT hMidiOut, WORD wMsg,
                                          DWORD dwInstance, DWORD dwParam1,
                                          DWORD dwParam2) ;
WORD FAR PASCAL _export midiflushreceive() ;
WORD FAR PASCAL _export midiflushtransmit() ;
WORD FAR PASCAL _export midiallnotesoff(DWORD qtime) ;

void PASCAL SetMidiOutOpenItems();

TIMECAPS tc;
WORD     wTimerId = NULL;
HMIDIOUT hMidiOut = NULL;
HMIDIIN  hMidiIn = NULL;

WORD  wQuantaSize = 1000;
DWORD dwQuantaTime = 0;         /* Current Quanta Time  */
DWORD dwBaseTime = 0;           /* Time (in milliseconds) when the Common
                                     Music timer was started           */
DWORD dwMidiBaseTime = 0;       /* Time (in milliseconds) when MIDI input
                                     was started.                      */
BOOL  bTimerStarted = FALSE;    /* TRUE when the timer is running      */
BOOL  bScheduling = FALSE;      /* TRUE when pointers are being updated 
                                   to schedule another event. When TRUE,
                                   MIDI output is disabled to avoid
                                   loosing events                      */

WRITE_QUEUE FAR * lpWriteQueue = NULL;
int    iFirstOpenWriteItem;
int    iLastOpenWriteItem;
int    iNextSchedEvent = -1;
DWORD  dwNextSchedTime = MAX_SCHED_TIME;
int    iLastSchedEvent = -1;
DWORD  dwLastSchedTime = 0;

LPREAD_QUEUE_HDR lpFirstReadQueueHdr = NULL;

int FAR PASCAL LibMain (HANDLE hInstance, WORD wDataSeg, WORD wHeapSize,
                         LPSTR lpszCmdLine)
	{
    /*  Do not unlock data segment ! */

    if (timeGetDevCaps(&tc, sizeof(TIMECAPS)) == TIMERR_NOCANDO)
        return(0);

    return(1);
    }

int FAR PASCAL _export WEP (int nParam)
	{
    if (bTimerStarted)
        {
        timeKillEvent(wTimerId);
        timeEndPeriod(tc.wPeriodMin);
        }
    if (hMidiOut != NULL)
        midiclose();
    return(1);
    }

WORD FAR PASCAL _export midistarttimer()
    {
    if (1000000 / wQuantaSize > 1000 / tc.wPeriodMin)
        return(1);    /*  Timer can't handle requested resolution */
    if (bTimerStarted)
        return(2);    /*  Timer already started  */

    if (timeBeginPeriod(tc.wPeriodMin) == TIMERR_NOCANDO)
        return(3);    /*  Cannot set timer resolution  */
    wTimerId = timeSetEvent((wQuantaSize / 1000), MAX_TIMER_ACCURACY,
                            timer_callback, NULL, TIME_PERIODIC);
    if (!wTimerId)
        return(4);

    dwBaseTime = timeGetTime();
    bTimerStarted = TRUE;
    return(FF_MIDI_SUCCESS);
    }

WORD FAR PASCAL _export midistoptimer()
    {
    if (!bTimerStarted)
        return(1);           /*  Timer is not running  */
    timeKillEvent(wTimerId);
    wTimerId = NULL;
    timeEndPeriod(wQuantaSize / 1000);
    bTimerStarted = FALSE;
    dwBaseTime = 0;
    dwQuantaTime = 0;
    return(FF_MIDI_SUCCESS);
    }

DWORD FAR PASCAL _export midigettime()
    {
    if (bTimerStarted)
        return (dwQuantaTime);
    else
        return (0);
    }

WORD FAR PASCAL _export midisettime(DWORD new_time)
    {
    if (!bTimerStarted)
        return(1);          /*  Timer must be running to set time  */
    dwBaseTime = timeGetTime() - (new_time * (wQuantaSize / 1000));
    dwQuantaTime = new_time;
    return(FF_MIDI_SUCCESS);
    }

WORD FAR PASCAL _export midisetquantasize (DWORD new_quanta_size)
    {
    if (bTimerStarted)
        return (1); /* can't change wQuantaSize while timer is running */

    /*  Check if new_quanta_size is within the capabilites of the */
    /*  computer.  Compare interrupts per second with interrupts  */
    /*  per second.                                               */
    if (1000000/ new_quanta_size > 1000/ tc.wPeriodMin)
        return(2);
    else
        {
        wQuantaSize = new_quanta_size;
        return(FF_MIDI_SUCCESS);
        }
    }

WORD FAR PASCAL _export midiopen(WORD portnum)
    {
    HANDLE hQueue;
    LPREAD_QUEUE_ITEM lpRdQ;
    LPREAD_QUEUE_HDR  lpRdQHdr;
    WORD rtn;

    /* Allocate storage for queue for outgoing midi data */

    lpWriteQueue =
        (WRITE_QUEUE FAR *) GlobalAllocPtr(GMEM_MOVEABLE | GMEM_SHARE,
                                     ((DWORD)sizeof(WRITE_QUEUE_ITEM) *
                                      MAX_WRITE_QUEUE_ITEMS));
    if (!lpWriteQueue)
        return(100);
    /*  Global storage accessed at interrupt time mest be Page locked  */
    if (GlobalPageLock(HIWORD(lpWriteQueue)) == 0)
        return(110);
    iFirstOpenWriteItem = 0;
    iLastOpenWriteItem = MAX_WRITE_QUEUE_ITEMS - 1;
    SetMidiOutOpenItems();

    /* Allocate storage for incoming midi data                */
    /* First allocate storage for the header                  */
    /* Then allocate storage for the incoming midi data queue */

    hQueue = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
                         (DWORD)sizeof(READ_QUEUE_HDR));
    if (hQueue == (HANDLE)NULL)
        return(150);
    lpRdQHdr = (LPREAD_QUEUE_HDR)GlobalLock(hQueue);
    if (lpRdQHdr == (LPREAD_QUEUE_HDR)NULL)
        {
        GlobalFree(hQueue);
        return(152);
        }
    /*  Global storage accessed at interrupt time mest be Page locked  */
    if (GlobalPageLock(HIWORD(lpRdQHdr)) == 0)
        {
        GlobalFreePtr(lpRdQHdr);
        return(154);
        }

    hQueue = GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
                         (DWORD)(sizeof(READ_QUEUE_ITEM) *
                         MAX_READ_QUEUE_ITEMS));
    if (hQueue == (HANDLE)NULL)
        {
        GlobalPageUnlock(HIWORD(lpRdQHdr));
        GlobalFreePtr(lpRdQHdr);
        return(156);
        }
    lpRdQ = (LPREAD_QUEUE_ITEM)GlobalLock(hQueue);
    if (lpRdQ == (LPREAD_QUEUE_ITEM)NULL)
        {
        GlobalFree(hQueue);
        GlobalPageUnlock(HIWORD(lpRdQHdr));
        GlobalFreePtr(lpRdQHdr);
        return(158);
        }
    /*  Global storage accessed at interrupt time mest be Page locked  */
    if (GlobalPageLock(HIWORD(lpRdQ)) == 0)
        {
        GlobalFreePtr(lpRdQ);
        GlobalPageUnlock(HIWORD(lpRdQHdr));
        GlobalFreePtr(lpRdQHdr);
        return(160);
        }
    /* Save pointer to first READ_QUEUE_HDR and    */
    /* initialize READ_QUEUE_HDR                   */
    lpFirstReadQueueHdr = lpRdQHdr;
    lpFirstReadQueueHdr->lpReadQueue = lpRdQ;
    lpFirstReadQueueHdr->lpReadQueueEnd = lpRdQ + MAX_READ_QUEUE_ITEMS;
    lpFirstReadQueueHdr->iReadCount = 0;
    lpFirstReadQueueHdr->lpFirstOpenReadItem = lpRdQ;
    lpFirstReadQueueHdr->lpFirstNewReadItem = lpRdQ;

    /* Open Midi input and output ports and start midi input */

    rtn = midiOutOpen((LPHMIDIOUT) &hMidiOut, portnum,
                      (DWORD)midi_out_callback, NULL, CALLBACK_FUNCTION);
    if (rtn)
		return(200 + rtn);
    rtn = midiInOpen((LPHMIDIIN) &hMidiIn, portnum,
                     (DWORD)midi_in_callback, (DWORD)(LPVOID)lpRdQHdr,
                     CALLBACK_FUNCTION);
    if (rtn)
		return(300 + rtn);
    dwMidiBaseTime = timeGetTime();
    rtn = midiInStart(hMidiIn);
    if (rtn)
		return(400 + rtn);
    rtn = midistarttimer();
    if (rtn)
		return(500+rtn);
    rtn = midisettime((DWORD)0);
    if (rtn)
		return(600+rtn);
    return(FF_MIDI_SUCCESS);
    }

WORD FAR PASCAL _export midiclose()
    {
    WORD rtn;

    iNextSchedEvent = -1;
    dwNextSchedTime = MAX_SCHED_TIME;
    iLastSchedEvent = -1;
    dwLastSchedTime = 0;
/*    if (bTimerStarted)
        {
        midisettime(0);
        dwMidiBaseTime = 0;
        wQuantaSize = 1000;
        return(FF_MIDI_SUCCESS);
        } */
    midistoptimer();
    if (!hMidiOut)
        return(FF_MIDI_SUCCESS);

    rtn = midiOutClose(hMidiOut);
    if (rtn)
        return(100 + rtn);
    else
        hMidiOut = NULL;
    rtn = midiInClose(hMidiIn);
    if (rtn) 
    	return(200 + rtn);
    else
        hMidiIn = NULL;

    /* Release storage for MIDI input and output queues */

    GlobalPageUnlock(HIWORD(lpWriteQueue));
    lpWriteQueue =
      (WRITE_QUEUE FAR *)GlobalFreePtr(lpWriteQueue);
    GlobalPageUnlock(HIWORD(lpFirstReadQueueHdr->lpReadQueue));
    GlobalFreePtr(lpFirstReadQueueHdr->lpReadQueue);
    GlobalPageUnlock(HIWORD(lpFirstReadQueueHdr));
    lpFirstReadQueueHdr =
      (LPREAD_QUEUE_HDR)GlobalFreePtr(lpFirstReadQueueHdr);

    return(FF_MIDI_SUCCESS);
    }

#if 0
WORD FAR PASCAL _export midireadmessages
                            (void (FAR * lisp_read_func)(DWORD))
    {
    unsigned int wNextItem;
    MIDI_DATA CMMidiData;     /* MidiMessage formatted for Common Music */

    /* Check to be sure midi port has been opened and storage allocated */
    if (!lpReadQueue)
        return(100);
    if (iLastOpenReadItem == MAX_READ_QUEUE_ITEMS - 1)
        wNextItem = 0;
    else
        wNextItem = iLastOpenReadItem + 1;

    while (wNextItem != iFirstOpenReadItem)
        {
        /* Format Midi Message for Common Music */

        CMMidiData.byMidiByte[3] = 0;
        CMMidiData.byMidiByte[2] =
            lpReadQueue->Item[wNextItem].MidiReadData.byMidiByte[0];
        CMMidiData.byMidiByte[1] =
            lpReadQueue->Item[wNextItem].MidiReadData.byMidiByte[1];
        CMMidiData.byMidiByte[0] =
            lpReadQueue->Item[wNextItem].MidiReadData.byMidiByte[2];

        if (iFirstOpenReadItem == -1)
            iFirstOpenReadItem = wNextItem;
        iLastOpenReadItem = wNextItem;

        (*lisp_read_func)(CMMidiData.dwMidiMsg);
        if (wNextItem == MAX_READ_QUEUE_ITEMS - 1)
            wNextItem = 0;
        else
            wNextItem++;
        }

    return(FF_MIDI_SUCCESS);
    }
#endif

#if 0
WORD FAR PASCAL _export midireadmessages
                           (void (FAR * lisp_read_func)(DWORD))
    {
    DWORD val = 1;

    (*lisp_read_func)(val);
    return(FF_MIDI_SUCCESS);
    }
#endif

WORD FAR PASCAL _export midiwritemessage(MIDI_DATA CMMidiMsg, DWORD qtime)
    {
    WORD rtn;
    MIDI_DATA WndwMidiData; /* MIDI data formatted for Windows */
    int iCurItem, iChkItem;

    /* Format MIDI data for Windows */
    WndwMidiData.byMidiByte[3] = 0;
    WndwMidiData.byMidiByte[2] = CMMidiMsg.byMidiByte[0];
    WndwMidiData.byMidiByte[1] = CMMidiMsg.byMidiByte[1];
    WndwMidiData.byMidiByte[0] = CMMidiMsg.byMidiByte[2];

    if (qtime <= dwQuantaTime)
        {
        rtn = midiOutShortMsg(hMidiOut, WndwMidiData.dwMidiMsg);
        return(rtn);
        }
    if (iFirstOpenWriteItem < 0) /* Return an error if there is no more  */
        return(101);             /* space in the queue!                  */

    bScheduling = TRUE;          /* Disable MIDI Output while scheduling */

    iCurItem = iFirstOpenWriteItem;
    iFirstOpenWriteItem = lpWriteQueue->Item[iCurItem].iNextOpenItem;
    if (iLastOpenWriteItem == iCurItem) /* if this was the only open item */
        iLastOpenWriteItem = -1;        /* indicate no more open items    */
    lpWriteQueue->Item[iCurItem].MidiWriteData.dwMidiMsg =
        WndwMidiData.dwMidiMsg;
    lpWriteQueue->Item[iCurItem].dwQuantaTime = qtime;
    lpWriteQueue->Item[iCurItem].iNextOpenItem = -1;

    if (dwNextSchedTime >= qtime) /* Add to Front of Queue */
        {
        lpWriteQueue->Item[iCurItem].iNextEvent = iNextSchedEvent;
        lpWriteQueue->Item[iCurItem].iPrevEvent = -1;
        if (iNextSchedEvent >= 0)
            lpWriteQueue->Item[iNextSchedEvent].iPrevEvent = iCurItem;
        iNextSchedEvent = iCurItem;
        dwNextSchedTime = qtime;
        if (iLastSchedEvent == -1)
            {
            iLastSchedEvent = iCurItem;
            dwLastSchedTime = qtime;
            }
        }
    else if (dwLastSchedTime <= qtime) /* Add to end of Queue */
        {
        lpWriteQueue->Item[iCurItem].iNextEvent = -1;
        lpWriteQueue->Item[iCurItem].iPrevEvent = iLastSchedEvent;
        if (iLastSchedEvent >= 0)
            lpWriteQueue->Item[iLastSchedEvent].iNextEvent = iCurItem;
        iLastSchedEvent = iCurItem;
        dwLastSchedTime = qtime;
        }
    else    /* Add to middle of Queue */
        {
        iChkItem = iNextSchedEvent;
        while (lpWriteQueue->Item[iChkItem].dwQuantaTime < qtime)
            iChkItem = lpWriteQueue->Item[iChkItem].iNextEvent;
        lpWriteQueue->Item[iCurItem].iNextEvent = iChkItem;
        lpWriteQueue->Item[iCurItem].iPrevEvent =
            lpWriteQueue->Item[iChkItem].iPrevEvent;
        lpWriteQueue->Item[iChkItem].iPrevEvent = iCurItem;
        lpWriteQueue->
          Item[lpWriteQueue->Item[iCurItem].iPrevEvent].iNextEvent
          = iCurItem;
        }
    bScheduling = FALSE;         /* Enable MIDI Output */

    return(FF_MIDI_SUCCESS);
    }

WORD FAR PASCAL _export midihush()
    {
    WORD rtn;

    rtn = midiflushtransmit();
    if (rtn)
        return(rtn + 500);
    rtn = midiallnotesoff((DWORD) 0);
    if (rtn)
        return(rtn + 600);
    rtn = midiflushreceive();
    if (rtn)
        return(rtn + 700);
    return(FF_MIDI_SUCCESS);
    }

WORD FAR PASCAL _export midiflushtransmit()
    {
    if (!lpWriteQueue)
        return(100);
		
    bScheduling = TRUE;          /* Disable MIDI Output while scheduling */
    iNextSchedEvent = -1;
    dwNextSchedTime = MAX_SCHED_TIME;
    iLastSchedEvent = -1;
    dwLastSchedTime = 0;
    iFirstOpenWriteItem = 0;
    iLastOpenWriteItem = MAX_WRITE_QUEUE_ITEMS - 1;
    SetMidiOutOpenItems();
    bScheduling = FALSE;         /* Enable MIDI Output                   */

    return(FF_MIDI_SUCCESS);
    }

WORD FAR PASCAL _export midiflushreceive()
    {
    if (!lpFirstReadQueueHdr)
        return(100);
#if 0
    iFirstOpenReadItem = 0;
    iLastOpenReadItem = MAX_READ_QUEUE_ITEMS - 1;
#endif
    return(FF_MIDI_SUCCESS);
    }

WORD FAR PASCAL _export midiallnotesoff(DWORD qtime)
    {
    int chnl, note;
    MIDI_DATA CMMidiData;
    WORD rtn;

    /*  Send a note off message for all notes on all channels */
    for (chnl=0; chnl<16; chnl++)
        for (note=0; note<128; note++)
            {
            CMMidiData.byMidiByte[3] = 0;
            CMMidiData.byMidiByte[2] = MIDI_NOTE_OFF | chnl;
            CMMidiData.byMidiByte[1] = note;
            CMMidiData.byMidiByte[0] = 0x40;
            rtn = midiwritemessage(CMMidiData, qtime);
            if (rtn != FF_MIDI_SUCCESS)
                return(rtn);
            }
    return(FF_MIDI_SUCCESS);
    }

WORD FAR PASCAL _export miditotalports
                         (TOTAL_MIDI_PORTS_STRUCT * total_midi_ports)
    {
    total_midi_ports->total_midi_in = midiInGetNumDevs();
    total_midi_ports->total_midi_out = midiOutGetNumDevs();

    return(FF_MIDI_SUCCESS);
    }

DWORD FAR PASCAL _export totalopen()
    {
    DWORD opncnt=0;
    int chksub;

    chksub = iFirstOpenWriteItem;
    while (chksub != -1)
        {
        opncnt++;
        chksub = lpWriteQueue->Item[chksub].iNextOpenItem;
        }

    return(opncnt);
    }

DWORD FAR PASCAL _export checkread()
    {
    return((DWORD)lpFirstReadQueueHdr->lpFirstOpenReadItem);
    }

void PASCAL SetMidiOutOpenItems()
    {
    int cnt;

    for (cnt = 0; cnt < MAX_WRITE_QUEUE_ITEMS - 1; cnt++)
        {
        lpWriteQueue->Item[cnt].iNextOpenItem = cnt + 1;
        }
    lpWriteQueue->Item[MAX_WRITE_QUEUE_ITEMS - 1].iNextOpenItem = -1;
    return;
    }

void FAR PASCAL _export midi_out_callback(HMIDIOUT hMidiOut, WORD wMsg,
                                          DWORD dwInstance, DWORD dwParam1,
                                          DWORD dwParam2)
    {
    return ;
    }

void FAR PASCAL _export midi_in_callback(HMIDIIN hMidiIn, WORD wMsg,
                                          DWORD dwInstance,
                                          MIDI_DATA MidiInMsg,
                                          DWORD dwParam2)
    {
    /* static LPREAD_QUEUE_HDR  lpRdQHdr = NULL; */

    if (wMsg != MIM_DATA)
        return;

    /*  Ignore Active Sensing messages */
    if (MidiInMsg.byMidiByte[0] == MIDI_ACTIVE_SENSING)
        return;
#if 0
    lpRdQHdr = (LPREAD_QUEUE_HDR)dwInstance;

    /* return if there is no space left in the read (input) queue */
    if (lpRdQHdr->iReadCount >= MAX_READ_QUEUE_ITEMS)
        return;

    lpRdQHdr->lpFirstOpenReadItem->MidiReadData.dwMidiMsg =
        MidiInMsg.dwMidiMsg;
    lpRdQHdr->lpFirstOpenReadItem->dwMidiTime = dwParam2;

    ++lpRdQHdr->iReadCount;
    ++lpRdQHdr->lpFirstOpenReadItem;

    if (lpRdQHdr->lpFirstOpenReadItem == lpRdQHdr->lpReadQueueEnd)
        lpRdQHdr->lpFirstOpenReadItem = lpRdQHdr->lpReadQueue;
#endif
    if (lpFirstReadQueueHdr->iReadCount >= MAX_READ_QUEUE_ITEMS)
        return;
#if 0
    lpFirstReadQueueHdr->lpFirstOpenReadItem->
        MidiReadData.dwMidiMsg = MidiInMsg.dwMidiMsg;
#endif
    lpFirstReadQueueHdr->lpFirstOpenReadItem->dwMidiTime = dwParam2;

    ++lpFirstReadQueueHdr->iReadCount;
    ++lpFirstReadQueueHdr->lpFirstOpenReadItem;

    if (lpFirstReadQueueHdr->lpFirstOpenReadItem >=
            lpFirstReadQueueHdr->lpReadQueueEnd)
        lpFirstReadQueueHdr->lpFirstOpenReadItem =
            lpFirstReadQueueHdr->lpReadQueue;
    return;
    }

void FAR PASCAL _export timer_callback(UINT wID, UINT wMsg,
                                       DWORD dwUser, DWORD dw1, DWORD dw2)
    {
    dwQuantaTime += 1;
    if (bScheduling)  /* Don't send output while scheduling */
        return;

    while (dwNextSchedTime <= dwQuantaTime)
        {
        midiOutShortMsg (hMidiOut,
            lpWriteQueue->Item[iNextSchedEvent].MidiWriteData.dwMidiMsg);
        /*  Make this write queue item the last open write queue item */
        if (iLastOpenWriteItem != -1)
            lpWriteQueue->Item[iLastOpenWriteItem].iNextOpenItem =
                iNextSchedEvent;
        iLastOpenWriteItem = iNextSchedEvent;
        if (iFirstOpenWriteItem == -1)
            iFirstOpenWriteItem = iNextSchedEvent;
        /*  Check for next event  */
        if (lpWriteQueue->Item[iNextSchedEvent].iNextEvent == -1)
            {                    /*  no more events scheduled.  */
            iNextSchedEvent = -1;
            dwNextSchedTime = MAX_SCHED_TIME;
            iLastSchedEvent = -1;
            dwLastSchedTime = 0;
            }
        else                                    /*  Schedule next event */
            {
            iNextSchedEvent =
                lpWriteQueue->Item[iNextSchedEvent].iNextEvent;
            dwNextSchedTime =
                lpWriteQueue->Item[iNextSchedEvent].dwQuantaTime;
            }
        }

    return ;
    }
 
#if 0
extern "C"
{
WORD FAR PASCAL _export midistarttimer();
WORD FAR PASCAL _export midistarttimer();
WORD FAR PASCAL _export midistoptimer();
DWORD FAR PASCAL _export midigettime();
WORD FAR PASCAL _export midisettime(DWORD);
WORD FAR PASCAL _export midisetquantasize(DWORD);
WORD FAR PASCAL _export miditotalports(TOTAL_MIDI_PORTS_STRUCT FAR *);
WORD FAR PASCAL _export midiopen(WORD);
WORD FAR PASCAL _export midiclose();
WORD FAR PASCAL _export midireadmessages(void (FAR *)(DWORD));
WORD FAR PASCAL _export midiwritemessage(MIDI_DATA, DWORD);
WORD FAR PASCAL _export midihush();
WORD FAR PASCAL _export midiflushtransmit();
WORD FAR PASCAL _export midiflushreceive();
WORD FAR PASCAL _export midiallnotesoff(DWORD);
void FAR PASCAL _export midi_out_callback(HMIDIOUT, WORD, DWORD, DWORD, DWORD);
void FAR PASCAL _export midi_in_callback(HMIDIIN, WORD, DWORD, MIDI_DATA, DWORD);
void FAR PASCAL _export timer_callback(UINT, UINT, DWORD, DWORD, DWORD);
DWORD FAR PASCAL _export totalopen();
DWORD FAR PASCAL _export checkread();
}
#endif

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.