/* Copyright (c) 1997 The Regents of the University of California.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

#include "m_imp.h"
#include <stdio.h>

#include <windows.h>

#include <MMSYSTEM.H>

/* ------------------------- audio -------------------------- */

#define NCHANS 2
#define DEFAULTSRATE 44100
#define SAMPSIZE 2
#define BUFSIZE (NCHANS * DACBLKSIZE * SAMPSIZE)

#define REALDACBLKSIZE (4 * DACBLKSIZE)
#define REALBUFSIZE (NCHANS * REALDACBLKSIZE * SAMPSIZE)

#define MAXBUFFER 50
#define DEFBUFFER 30
static int nt_naudiobuffer = DEFBUFFER;

t_sample sys_soundout[MAXCH*DACBLKSIZE];
t_sample sys_soundin[MAXCH*DACBLKSIZE];
float sys_dacsr = DEFAULTSRATE;

static int sys_ch;
static int sys_advance = 16;	/* sched advance in buffers; not used yet */
int sys_schedadvance = 20000;	/* scheduler advance in microseconds */

typedef struct _sbuf
{
    HANDLE hData;
    HPSTR  lpData;      // pointer to waveform data memory 
    HANDLE hWaveHdr; 
    WAVEHDR   *lpWaveHdr;   // pointer to header structure
} t_sbuf;

t_sbuf ntsnd_outvec[MAXBUFFER];	    /* circular buffer array */
HWAVEOUT ntsnd_outdev;  	    /* output device */
static int ntsnd_outphase = 0; 	    /* index of next buffer to send */

t_sbuf ntsnd_invec[MAXBUFFER];	    /* circular buffer array */
HWAVEIN ntsnd_indev;  	    	    /* input device */
static int ntsnd_inphase = 0; 	    /* index of next buffer to read */

static void nt_waveinerror(char *s, int err)
{
    char t[256];
    waveInGetErrorText(err, t, 256);
    fprintf(stderr, s, t);
}

static void nt_waveouterror(char *s, int err)
{
    char t[256];
    waveOutGetErrorText(err, t, 256);
    fprintf(stderr, s, t);
}


static void wave_prep(t_sbuf *bp)
{
    WAVEHDR *wh;
    short *sp;
    int i;
    /* 
     * Allocate and lock memory for the waveform data. The memory 
     * for waveform data must be globally allocated with 
     * GMEM_MOVEABLE and GMEM_SHARE flags. 
     */ 

    if (!(bp->hData =
	GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (DWORD) REALBUFSIZE))) 
	    printf("alloc 1 failed\n");

    if (!(bp->lpData =
	(HPSTR) GlobalLock(bp->hData)))
	    printf("lock 1 failed\n");

    /*  Allocate and lock memory for the header.  */ 

    if (!(bp->hWaveHdr =
	GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (DWORD) sizeof(WAVEHDR))))
    	    printf("alloc 2 failed\n");

    if (!(wh = bp->lpWaveHdr =
	(WAVEHDR *) GlobalLock(bp->hWaveHdr))) 
	    printf("lock 2 failed\n");

    for (i = NCHANS * REALDACBLKSIZE, sp = (short *)bp->lpData; i--; )
    	*sp++ = 0;

    wh->lpData = bp->lpData;
    wh->dwBufferLength = REALBUFSIZE;
    wh->dwFlags = WHDR_DONE;
    wh->dwLoops = 0L;
    wh->lpNext = 0;
    wh->reserved = 0;
}

static int nt_dac, nt_adc;
static int nt_inalloc, nt_outalloc;
static UINT nt_whichdac = WAVE_MAPPER, nt_whichadc = WAVE_MAPPER;

int nt_open_audio(int adc, int dac) 
{ 
    PCMWAVEFORMAT  form; 
    int i;
    UINT mmresult;
    
    nt_dac = dac;
    nt_adc = adc;
    post("ADCs %s; DACs %s", (nt_adc? "on" : "off"), (nt_dac? "on" : "off"));

    form.wf.wFormatTag = WAVE_FORMAT_PCM;
    form.wf.nChannels = NCHANS;
    form.wf.nSamplesPerSec = sys_dacsr;
    form.wf.nAvgBytesPerSec = sys_dacsr * NCHANS * SAMPSIZE;
    form.wf.nBlockAlign = NCHANS * SAMPSIZE;
    form.wBitsPerSample = 8 * SAMPSIZE;

    if (nt_adc)
    {
    	    /* Open a waveform device for input */
	mmresult = waveInOpen(&ntsnd_indev, nt_whichadc,
    	    (WAVEFORMATEX *)(&form), 0L, 0L, CALLBACK_NULL);
    	if (mmresult != MMSYSERR_NOERROR) 
	{
    	    nt_waveinerror("waveInOpen: %s\n", mmresult);
    	    nt_adc = 0;
	}
	else
	{
	    if (!nt_inalloc)
	    {
	    	for (i = 0; i < nt_naudiobuffer; i++)
    		    wave_prep(&ntsnd_invec[i]);
	    	nt_inalloc = 1;
	    }
	    for (i = 0; i < nt_naudiobuffer; i++)
	    {
        	mmresult = waveInPrepareHeader(ntsnd_indev,
        	    ntsnd_invec[i].lpWaveHdr, sizeof(WAVEHDR));
        	if (mmresult != MMSYSERR_NOERROR)
        	    nt_waveinerror("waveinprepareheader: %s\n", mmresult);
        	mmresult = waveInAddBuffer(ntsnd_indev,
        	    ntsnd_invec[i].lpWaveHdr, sizeof(WAVEHDR));
        	if (mmresult != MMSYSERR_NOERROR)
        	    nt_waveinerror("waveInAddBuffer: %s\n", mmresult);
	    }
    	    waveInStart(ntsnd_indev);
    	}
    }

    if (nt_dac)
    {
    	
    	    /* Open a waveform device for output */
	mmresult = waveOutOpen(&ntsnd_outdev, nt_whichdac,
    	    (WAVEFORMATEX *)(&form), 0L, 0L, CALLBACK_NULL);
    	if (mmresult != MMSYSERR_NOERROR)
    	{
            nt_waveouterror("waveOutOpen: %s\n", mmresult);
            nt_dac = 0;
        }
	else
	{
	    if (!nt_outalloc)
	    {
	    	for (i = 0; i < nt_naudiobuffer; i++)
    	    	    wave_prep(&ntsnd_outvec[i]);
    	    	nt_outalloc = 1;
    	    }
    	}
    }
    return (0);
}

void sys_close_audio( void)
{
    int errcode;
    if (nt_dac)
    {
	errcode = waveOutReset(ntsnd_outdev);
	if (errcode != MMSYSERR_NOERROR)
	    printf("error resetting output: %d\n", errcode);
	errcode = waveOutClose(ntsnd_outdev);
	if (errcode != MMSYSERR_NOERROR)
	    printf("error closing output: %d\n", errcode);
    	nt_dac = 0;
    }
    if (nt_adc)
    {
	errcode = waveInReset(ntsnd_indev);
	if (errcode != MMSYSERR_NOERROR)
	    printf("error resetting input: %d\n", errcode);
	errcode = waveInClose(ntsnd_indev);
	if (errcode != MMSYSERR_NOERROR)
	    printf("error closing input: %d\n", errcode);
    	nt_adc = 0;
    }
}

int glob_audio(void *dummy, t_floatarg fadc, t_floatarg fdac)
{
    int adc = fadc, dac = fdac;
    if (!dac && !adc)
    {
    	post("pd: adc %s; dac %s",
    	    (nt_adc? "on" : "off"), (nt_dac? "on" : "off"));
    }
    else
    {
    	sys_close_audio();
    	nt_open_audio(adc, dac);
    }
}

static int nt_fill = 0;

int sys_send_dacs(void) 
{
    HMMIO       hmmio; 
    UINT        mmresult; 
    HANDLE      hFormat; 
    int i;
    short *outbufto, *inbuffrom, *sp;
    WAVEHDR *outwavehdr, *inwavehdr;
    float *fp1, *fp2;
    int nextfill, doxfer = 0;
    
    if (!nt_dac && !nt_adc) return (0);
    if (nt_dac)
    {
    	outbufto = (short *)(ntsnd_outvec[ntsnd_outphase].lpData);
    	outwavehdr = ntsnd_outvec[ntsnd_outphase].lpWaveHdr;
    	if (!(outwavehdr->dwFlags & WHDR_DONE))
    	{
    	    /* post("n\n"); */
    	    return (0);
    	}
    	else
    	{
#if 0
    	    static int count;
    	    post("%d", count++);
#endif
    	}
    }
    if (nt_adc)
    {
    	inbuffrom = (short *)(ntsnd_invec[ntsnd_inphase].lpData);
    	inwavehdr = ntsnd_invec[ntsnd_inphase].lpWaveHdr;
    	if (!(inwavehdr->dwFlags & WHDR_DONE))
    	{
#if 0
    	    post("n");
#endif
    	    return (0);
    	}
    	else
    	{
#if 0
    	    static int count;
    	    post("%d", count++);
#endif
    	}
    }
    nextfill = nt_fill + DACBLKSIZE;
    if (nextfill == REALDACBLKSIZE)
    {
    	nextfill = 0;
    	doxfer = 1;
    }
    	/* prepare and queue up the output buffer */ 
    if (nt_dac)
    {
    	if (outwavehdr->dwFlags & WHDR_PREPARED)
	    waveOutUnprepareHeader(ntsnd_outdev, outwavehdr, sizeof(WAVEHDR)); 
	for (i = 0, fp1 = sys_soundout, fp2 = fp1 + DACBLKSIZE,
	    sp = outbufto + 2 * nt_fill;
	    	i < DACBLKSIZE; i++, fp1++, fp2++, sp += 2)
    	{
    	    int x1 = 32767.f * *fp1;
    	    int x2 = 32767.f * *fp2;
    	    if (x1 > 32767) x1 = 32767;
    	    else if (x1 < -32767) x1 = -32767;
    	    if (x2 > 32767) x2 = 32767;
    	    else if (x2 < -32767) x2 = -32767;
    	    sp[0] = x1;  
    	    sp[1] = x2;  
	}
	if (doxfer)
	{
    	    outwavehdr->dwFlags = 0L;
	    waveOutPrepareHeader(ntsnd_outdev, outwavehdr, sizeof(WAVEHDR)); 
    	    /* fprintf(stderr, "xfer %x %x\n", ntsnd_outdev, outwavehdr); */
	    mmresult = waveOutWrite(ntsnd_outdev, outwavehdr, sizeof(WAVEHDR)); 
            if (mmresult != MMSYSERR_NOERROR)
        	nt_waveouterror("waveOutWrite: %s\n", mmresult);
	    ntsnd_outphase++;
	    if (ntsnd_outphase == nt_naudiobuffer) ntsnd_outphase = 0;
	}
    	memset(sys_soundout, 0, 2*DACBLKSIZE*sizeof(t_sample));
    }

    	/* ditto the input buffer */ 

    if (nt_adc)
    {
    	if (inwavehdr->dwFlags & WHDR_PREPARED)
    	    waveInUnprepareHeader(ntsnd_indev, inwavehdr, sizeof(WAVEHDR)); 
    	for (i = 0, fp1 = sys_soundin, fp2 = fp1 + DACBLKSIZE,
	    sp = inbuffrom + 2 * nt_fill;
	    	i < DACBLKSIZE; i++, fp1++, fp2++, sp += 2)
    	{
    	    *fp1 = (1.f/32767.f) * (float)(sp[0]);    
    	    *fp2 = (1.f/32767.f) * (float)(sp[1]);    
    	}
    	if (doxfer)
    	{
    	    inwavehdr->dwFlags = 0L;
    	    waveInPrepareHeader(ntsnd_indev, inwavehdr, sizeof(WAVEHDR)); 
    	    mmresult = waveInAddBuffer(ntsnd_indev, inwavehdr, sizeof(WAVEHDR)); 
    	    if (mmresult != MMSYSERR_NOERROR)
            	nt_waveinerror("waveInAddBuffer: %s\n", mmresult);
    	    ntsnd_inphase++;
    	    if (ntsnd_inphase == nt_naudiobuffer) ntsnd_inphase = 0;
    	}
    }

    nt_fill = nextfill;
    return (1);
}

static void nt_setchsr(int ch, int sr)
{
    sys_ch = ch;
    sys_dacsr = sr;
    sys_advance = (sys_schedadvance * (float)sys_dacsr) / (1000000. * DACBLKSIZE);
    memset(sys_soundout, 0, sys_ch * (DACBLKSIZE*sizeof(float)));
    memset(sys_soundin, 0, sys_ch * (DACBLKSIZE*sizeof(float)));
}


/* ------------------------- MIDI output -------------------------- */


static void nt_midiouterror(char *s, int err)
{
    char t[256];
    midiOutGetErrorText(err, t, 256);
    fprintf(stderr, s, t);
}

static int nt_midiout;	    	    /* true if open for output */
static int nt_whichmidiout = MIDI_MAPPER;

static HMIDIOUT hMidiOut;	    /* output device */

static void nt_open_midiout(void)
{
    UINT result;
    int i;
    result = midiOutOpen(&hMidiOut, nt_whichmidiout, 0, 0, CALLBACK_NULL);
    if (result != MMSYSERR_NOERROR)
    	nt_midiouterror("midiOutOpen: %s\n", result);
    else nt_midiout = 1;
}

void sys_putmidimess(int portno, int a, int b, int c)
{
    DWORD foo;
    MMRESULT res;
    if (!nt_midiout) return;
    foo = (a & 0xff) | ((b & 0xff) << 8) | ((c & 0xff) << 16);
    res = midiOutShortMsg(hMidiOut, foo);
    /* post("msg out %d %d %d", a, b, c); */
}

/* -------------------------- MIDI input ---------------------------- */

#define MAX_NUM_DEVICES   10
#define INPUT_BUFFER_SIZE 200     // size of input buffer in events

static void nt_midiinerror(char *s, int err)
{
    char t[256];
    midiInGetErrorText(err, t, 256);
    fprintf(stderr, s, t);
}

static int nt_midiin;	    	    /* true if open for input */

/* Structure to represent a single MIDI event.
 */

#define EVNT_F_ERROR    0x00000001L

typedef struct event_tag
{
    DWORD fdwEvent;
    DWORD dwDevice;
    DWORD timestamp;
    DWORD data;
} EVENT;
typedef EVENT FAR *LPEVENT;

/* Structure to manage the circular input buffer.
 */
typedef struct circularBuffer_tag
{
    HANDLE  hSelf;          /* handle to this structure */
    HANDLE  hBuffer;        /* buffer handle */
    WORD    wError;         /* error flags */
    DWORD   dwSize;         /* buffer size (in EVENTS) */
    DWORD   dwCount;        /* byte count (in EVENTS) */
    LPEVENT lpStart;        /* ptr to start of buffer */
    LPEVENT lpEnd;          /* ptr to end of buffer (last byte + 1) */
    LPEVENT lpHead;         /* ptr to head (next location to fill) */
    LPEVENT lpTail;         /* ptr to tail (next location to empty) */
} CIRCULARBUFFER;
typedef CIRCULARBUFFER FAR *LPCIRCULARBUFFER;


/* Structure to pass instance data from the application
   to the low-level callback function.
 */
typedef struct callbackInstance_tag
{
    HANDLE              hSelf;  
    DWORD               dwDevice;
    LPCIRCULARBUFFER    lpBuf;
} CALLBACKINSTANCEDATA;
typedef CALLBACKINSTANCEDATA FAR *LPCALLBACKINSTANCEDATA;

/* Function prototypes
 */
LPCALLBACKINSTANCEDATA FAR PASCAL AllocCallbackInstanceData(void);
void FAR PASCAL FreeCallbackInstanceData(LPCALLBACKINSTANCEDATA lpBuf);

LPCIRCULARBUFFER AllocCircularBuffer(DWORD dwSize);
void FreeCircularBuffer(LPCIRCULARBUFFER lpBuf);
WORD FAR PASCAL GetEvent(LPCIRCULARBUFFER lpBuf, LPEVENT lpEvent);

// Callback instance data pointers
LPCALLBACKINSTANCEDATA lpCallbackInstanceData[MAX_NUM_DEVICES];

UINT wNumDevices = 0;			 // Number of MIDI input devices opened
BOOL bRecordingEnabled = 1;             // Enable/disable recording flag
int  nNumBufferLines = 0;		 // Number of lines in display buffer
RECT rectScrollClip;                    // Clipping rectangle for scrolling

LPCIRCULARBUFFER lpInputBuffer;         // Input buffer structure
EVENT incomingEvent;                    // Incoming MIDI event structure

MIDIINCAPS midiInCaps[MAX_NUM_DEVICES]; // Device capabilities structures
HMIDIIN hMidiIn[MAX_NUM_DEVICES];       // MIDI input device handles


/* AllocCallbackInstanceData - Allocates a CALLBACKINSTANCEDATA
 *      structure.  This structure is used to pass information to the
 *      low-level callback function, each time it receives a message.
 *
 *      Because this structure is accessed by the low-level callback
 *      function, it must be allocated using GlobalAlloc() with the 
 *      GMEM_SHARE and GMEM_MOVEABLE flags and page-locked with
 *      GlobalPageLock().
 *
 * Params:  void
 *
 * Return:  A pointer to the allocated CALLBACKINSTANCE data structure.
 */
LPCALLBACKINSTANCEDATA FAR PASCAL AllocCallbackInstanceData(void)
{
    HANDLE hMem;
    LPCALLBACKINSTANCEDATA lpBuf;
    
    /* Allocate and lock global memory.
     */
    hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE,
                       (DWORD)sizeof(CALLBACKINSTANCEDATA));
    if(hMem == NULL)
        return NULL;
    
    lpBuf = (LPCALLBACKINSTANCEDATA)GlobalLock(hMem);
    if(lpBuf == NULL){
        GlobalFree(hMem);
        return NULL;
    }
    
    /* Page lock the memory.
     */
    //GlobalPageLock((HGLOBAL)HIWORD(lpBuf));

    /* Save the handle.
     */
    lpBuf->hSelf = hMem;

    return lpBuf;
}

/* FreeCallbackInstanceData - Frees the given CALLBACKINSTANCEDATA structure.
 *
 * Params:  lpBuf - Points to the CALLBACKINSTANCEDATA structure to be freed.
 *
 * Return:  void
 */
void FAR PASCAL FreeCallbackInstanceData(LPCALLBACKINSTANCEDATA lpBuf)
{
    HANDLE hMem;

    /* Save the handle until we're through here.
     */
    hMem = lpBuf->hSelf;

    /* Free the structure.
     */
    //GlobalPageUnlock((HGLOBAL)HIWORD(lpBuf));
    GlobalUnlock(hMem);
    GlobalFree(hMem);
}


/*
 * AllocCircularBuffer -    Allocates memory for a CIRCULARBUFFER structure 
 * and a buffer of the specified size.  Each memory block is allocated 
 * with GlobalAlloc() using GMEM_SHARE and GMEM_MOVEABLE flags, locked 
 * with GlobalLock(), and page-locked with GlobalPageLock().
 *
 * Params:  dwSize - The size of the buffer, in events.
 *
 * Return:  A pointer to a CIRCULARBUFFER structure identifying the 
 *      allocated display buffer.  NULL if the buffer could not be allocated.
 */
 
 
LPCIRCULARBUFFER AllocCircularBuffer(DWORD dwSize)
{
    HANDLE hMem;
    LPCIRCULARBUFFER lpBuf;
    LPEVENT lpMem;
    
    /* Allocate and lock a CIRCULARBUFFER structure.
     */
    hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE,
                       (DWORD)sizeof(CIRCULARBUFFER));
    if(hMem == NULL)
        return NULL;

    lpBuf = (LPCIRCULARBUFFER)GlobalLock(hMem);
    if(lpBuf == NULL)
    {
        GlobalFree(hMem);
        return NULL;
    }
    
    /* Page lock the memory.  Global memory blocks accessed by
     * low-level callback functions must be page locked.
     */
#ifndef _WIN32
    GlobalSmartPageLock((HGLOBAL)HIWORD(lpBuf));
#endif

    /* Save the memory handle.
     */
    lpBuf->hSelf = hMem;
    
    /* Allocate and lock memory for the actual buffer.
     */
    hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE, dwSize * sizeof(EVENT));
    if(hMem == NULL)
    {
#ifndef _WIN32
        GlobalSmartPageUnlock((HGLOBAL)HIWORD(lpBuf));
#endif
        GlobalUnlock(lpBuf->hSelf);
        GlobalFree(lpBuf->hSelf);
        return NULL;
    }
    
    lpMem = (LPEVENT)GlobalLock(hMem);
    if(lpMem == NULL)
    {
        GlobalFree(hMem);
#ifndef _WIN32
        GlobalSmartPageUnlock((HGLOBAL)HIWORD(lpBuf));
#endif
        GlobalUnlock(lpBuf->hSelf);
        GlobalFree(lpBuf->hSelf);
        return NULL;
    }
    
    /* Page lock the memory.  Global memory blocks accessed by
     * low-level callback functions must be page locked.
     */
#ifndef _WIN32
    GlobalSmartPageLock((HGLOBAL)HIWORD(lpMem));
#endif
    
    /* Set up the CIRCULARBUFFER structure.
     */
    lpBuf->hBuffer = hMem;
    lpBuf->wError = 0;
    lpBuf->dwSize = dwSize;
    lpBuf->dwCount = 0L;
    lpBuf->lpStart = lpMem;
    lpBuf->lpEnd = lpMem + dwSize;
    lpBuf->lpTail = lpMem;
    lpBuf->lpHead = lpMem;
        
    return lpBuf;
}

/* FreeCircularBuffer - Frees the memory for the given CIRCULARBUFFER 
 * structure and the memory for the buffer it references.
 *
 * Params:  lpBuf - Points to the CIRCULARBUFFER to be freed.
 *
 * Return:  void
 */
void FreeCircularBuffer(LPCIRCULARBUFFER lpBuf)
{
    HANDLE hMem;
    
    /* Free the buffer itself.
     */
#ifndef _WIN32
    GlobalSmartPageUnlock((HGLOBAL)HIWORD(lpBuf->lpStart));
#endif
    GlobalUnlock(lpBuf->hBuffer);
    GlobalFree(lpBuf->hBuffer);
    
    /* Free the CIRCULARBUFFER structure.
     */
    hMem = lpBuf->hSelf;
#ifndef _WIN32
    GlobalSmartPageUnlock((HGLOBAL)HIWORD(lpBuf));
#endif
    GlobalUnlock(hMem);
    GlobalFree(hMem);
}

/* GetEvent - Gets a MIDI event from the circular input buffer.  Events
 *  are removed from the buffer.  The corresponding PutEvent() function
 *  is called by the low-level callback function, so it must reside in
 *  the callback DLL.  PutEvent() is defined in the CALLBACK.C module.
 *
 * Params:  lpBuf - Points to the circular buffer.
 *          lpEvent - Points to an EVENT structure that is filled with the
 *              retrieved event.
 *
 * Return:  Returns non-zero if successful, zero if there are no 
 *   events to get.
 */
WORD FAR PASCAL GetEvent(LPCIRCULARBUFFER lpBuf, LPEVENT lpEvent)
{
    	/* If no event available, return */
    if (!nt_midiin || lpBuf->dwCount <= 0) return (0);
    
    /* Get the event.
     */
    *lpEvent = *lpBuf->lpTail;
    
    /* Decrement the byte count, bump the tail pointer.
     */
    --lpBuf->dwCount;
    ++lpBuf->lpTail;
    
    /* Wrap the tail pointer, if necessary.
     */
    if(lpBuf->lpTail >= lpBuf->lpEnd)
        lpBuf->lpTail = lpBuf->lpStart;

    return 1;
}

/* PutEvent - Puts an EVENT in a CIRCULARBUFFER.  If the buffer is full, 
 *      it sets the wError element of the CIRCULARBUFFER structure 
 *      to be non-zero.
 *
 * Params:  lpBuf - Points to the CIRCULARBUFFER.
 *          lpEvent - Points to the EVENT.
 *
 * Return:  void
*/

void FAR PASCAL PutEvent(LPCIRCULARBUFFER lpBuf, LPEVENT lpEvent)
{
    /* If the buffer is full, set an error and return. 
     */
    if(lpBuf->dwCount >= lpBuf->dwSize){
        lpBuf->wError = 1;
        return;
    }
    
    /* Put the event in the buffer, bump the head pointer and the byte count.
     */
    *lpBuf->lpHead = *lpEvent;
    
    ++lpBuf->lpHead;
    ++lpBuf->dwCount;

    /* Wrap the head pointer, if necessary.
     */
    if(lpBuf->lpHead >= lpBuf->lpEnd)
        lpBuf->lpHead = lpBuf->lpStart;
}


/* midiInputHandler - Low-level callback function to handle MIDI input.
 *      Installed by midiInOpen().  The input handler takes incoming
 *      MIDI events and places them in the circular input buffer.  It then
 *      notifies the application by posting a MM_MIDIINPUT message.
 *
 *      This function is accessed at interrupt time, so it should be as 
 *      fast and efficient as possible.  You can't make any
 *      Windows calls here, except PostMessage().  The only Multimedia
 *      Windows call you can make are timeGetSystemTime(), midiOutShortMsg().
 *      
 *
 * Param:   hMidiIn - Handle for the associated input device.
 *          wMsg - One of the MIM_***** messages.
 *          dwInstance - Points to CALLBACKINSTANCEDATA structure.
 *          dwParam1 - MIDI data.
 *          dwParam2 - Timestamp (in milliseconds)
 *
 * Return:  void
 */     
void FAR PASCAL midiInputHandler(
HMIDIIN hMidiIn, 
WORD wMsg, 
DWORD dwInstance, 
DWORD dwParam1, 
DWORD dwParam2)
{
    EVENT event;
    
    switch(wMsg)
    {
        case MIM_OPEN:
            break;

        /* The only error possible is invalid MIDI data, so just pass
         * the invalid data on so we'll see it.
         */
        case MIM_ERROR:
        case MIM_DATA:
            event.fdwEvent = (wMsg == MIM_ERROR) ? EVNT_F_ERROR : 0;
            event.dwDevice = ((LPCALLBACKINSTANCEDATA)dwInstance)->dwDevice;
            event.data = dwParam1;
            event.timestamp = dwParam2;
            
            /* Put the MIDI event in the circular input buffer.
             */

            PutEvent(((LPCALLBACKINSTANCEDATA)dwInstance)->lpBuf,
                       (LPEVENT) &event); 

            break;

        default:
            break;
    }
}

int nt_open_midiin(void)
{
    UINT  wRtn;
    char szErrorText[256];
    unsigned int i;

    /* Get the number of MIDI input devices.  Then get the capabilities of
     * each device.  We don't use the capabilities information right now,
     * but we could use it to report the name of the device that received
     * each MIDI event.
     */
    wNumDevices = midiInGetNumDevs();
    if (!wNumDevices) {
        printf("There are no MIDI input devices.\n");
        return 1;
    }
    for (i = 0; i < wNumDevices; i++)
    {
    	wRtn = midiInGetDevCaps(0, (LPMIDIINCAPS) &midiInCaps[i],
            sizeof(MIDIINCAPS));
        if (wRtn) nt_midiinerror("midiingetdevcaps: %s\n", wRtn);

    }

    /* Allocate a circular buffer for low-level MIDI input.  This buffer
     * is filled by the low-level callback function and emptied by the
     * application.
     */
    lpInputBuffer = AllocCircularBuffer((DWORD)(INPUT_BUFFER_SIZE));
    if (lpInputBuffer == NULL) {
        printf("Not enough memory available for input buffer.\n");
        return 1;
    }

    /* Open all MIDI input devices after allocating and setting up
     * instance data for each device.  The instance data is used to
     * pass buffer management information between the application and
     * the low-level callback function.  It also includes a device ID,
     * a handle to the MIDI Mapper, and a handle to the application's
     * display window, so the callback can notify the window when input
     * data is available.  A single callback function is used to service
     * all opened input devices.
     */
    for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++)
    {
        if ((lpCallbackInstanceData[i] = AllocCallbackInstanceData()) == NULL)
        {
            printf("Not enough memory available.\n");
            FreeCircularBuffer(lpInputBuffer);
            return 1;
        }
        lpCallbackInstanceData[i]->dwDevice = i;
        lpCallbackInstanceData[i]->lpBuf = lpInputBuffer;
        
        wRtn = midiInOpen((LPHMIDIIN)&hMidiIn[i],
                          i,
                          (DWORD)midiInputHandler,
                          (DWORD)lpCallbackInstanceData[i],
                          CALLBACK_FUNCTION);
        if(wRtn)
        {
            FreeCallbackInstanceData(lpCallbackInstanceData[i]);
            nt_midiinerror("midiInOpen: %s\n", wRtn);
        }
    }

    /* Start MIDI input.
     */
    for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
        if (hMidiIn[i])
            midiInStart(hMidiIn[i]);
    }
    nt_midiin = 1;
}

void nt_closemidi(void)
{
    unsigned int i;
    /* Stop, reset, close MIDI input.  Free callback instance data.
     */

    nt_midiin = 0;
    for (i=0; (i<wNumDevices) && (i<MAX_NUM_DEVICES); i++) {
        if (hMidiIn[i]) {
            midiInStop(hMidiIn[i]);
            midiInReset(hMidiIn[i]);
            midiInClose(hMidiIn[i]);
            FreeCallbackInstanceData(lpCallbackInstanceData[i]);
        }
    }
    
    /* Free input buffer.
     */
    FreeCircularBuffer(lpInputBuffer);
}

void inmidi_noteon(int portno, int channel, int pitch, int velo);
void inmidi_controlchange(int portno, int channel, int ctlnumber, int value);
void inmidi_programchange(int portno, int channel, int value);
void inmidi_pitchbend(int portno, int channel, int value);
void inmidi_aftertouch(int portno, int channel, int value);
void inmidi_polyaftertouch(int portno, int channel, int pitch, int value);

void sys_poll_midi(void)
{
    EVENT foo;
    if (GetEvent(lpInputBuffer, &foo))
    {
    	int msgtype = ((foo.data & 0xf0) >> 4) - 8;
    	int chan = foo.data & 0xf;
    	int byte1 = (foo.data >> 8) & 0xff;
    	int byte2 = (foo.data >> 16) & 0xff;
    	switch (msgtype)
    	{
    	    case 0: inmidi_noteon(0, chan, byte1, 0); break; 
    	    case 1: inmidi_noteon(0, chan, byte1, byte2); break; 
    	    case 2: inmidi_polyaftertouch(0, chan, byte1, byte2); break; 
    	    case 3: inmidi_controlchange(0, chan, byte1, byte2); break; 
    	    case 4: inmidi_programchange(0, chan, byte1); break; 
    	    case 6: inmidi_aftertouch(0, chan, byte1); break; 
    	    case 5: inmidi_pitchbend(0, chan, byte1<<7 + byte2 - 8192); break; 
    	}
    	/* printf("event %x\n", foo.data); */
    }
}

/* ------------------- public routines -------------------------- */

int sys_init_sysdep(char *config, int srate)
{
    int dac = 1, adc = 1;
    char c = (config ? *config : 0);
    if (c == 'n') adc = dac = 0;
    else if (c == 'a') adc = 1, dac = 0;
    else if (c == 'd') adc = 0, dac = 1;
    else if (c == 'b') adc = dac = 1;
    nt_setchsr(2, srate);
    if (nt_open_audio(adc, dac)) return (1);
    nt_open_midiout();
    nt_open_midiin();
    return (0);
}


float sys_getsr(void)
{
    return (sys_dacsr);
}

int sys_getch(void)
{
    return (sys_ch);
}

void sys_audiobuf(int n)
{
    	/* set the size, in msec, of the audio FIFO */
    int nbuf = n * (44100/64000);
    if (nbuf >= MAXBUFFER)
    {
    	fprintf(stderr, "pd: audio buffering maxed out to %d\n", MAXBUFFER);
    	nbuf = MAXBUFFER;
    }
    else if (nbuf < 3) nbuf = 3;
    nt_naudiobuffer = nbuf;
}

/* ----------- public routines which are only defined for MSW/NT ---------- */

/* list the audio and MIDI device names */
void nt_listdevs(void)
{
    UINT  wRtn, wNumDevices;
    unsigned int i;

    /* for MIDI and audio in and out, get the number of devices.
    	Then get the capabilities of each device and print its description. */

    wNumDevices = midiInGetNumDevs();
    for (i = 0; i < wNumDevices; i++)
    {
    	MIDIINCAPS micap;
    	wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) &micap,
            sizeof(micap));
        if (wRtn) nt_midiinerror("midiInGetDevCaps: %s\n", wRtn);
    	else fprintf(stderr,
    	    "MIDI input device #%d: %s\n", i+1, micap.szPname);
    }

    wNumDevices = midiOutGetNumDevs();
    for (i = 0; i < wNumDevices; i++)
    {
    	MIDIOUTCAPS mocap;
    	wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) &mocap,
            sizeof(mocap));
        if (wRtn) nt_midiouterror("midiOutGetDevCaps: %s\n", wRtn);
    	else fprintf(stderr,
    	    "MIDI output device #%d: %s\n", i+1, mocap.szPname);
    }

    wNumDevices = waveInGetNumDevs();
    for (i = 0; i < wNumDevices; i++)
    {
    	WAVEINCAPS wicap;
    	wRtn = waveInGetDevCaps(i, (LPWAVEINCAPS) &wicap,
            sizeof(wicap));
        if (wRtn) nt_waveinerror("waveInGetDevCaps: %s\n", wRtn);
    	else fprintf(stderr,
    	    "audio input device #%d: %s\n", i+1, wicap.szPname);
    }

    wNumDevices = waveOutGetNumDevs();
    for (i = 0; i < wNumDevices; i++)
    {
    	WAVEOUTCAPS wocap;
    	wRtn = waveOutGetDevCaps(i, (LPWAVEOUTCAPS) &wocap,
            sizeof(wocap));
        if (wRtn) nt_waveouterror("waveOutGetDevCaps: %s\n", wRtn);
    	else fprintf(stderr,
    	    "audio output device #%d: %s\n", i+1, wocap.szPname);
    }
}

void nt_soundindev(int which)
{
    nt_whichadc = which;
}

void nt_soundoutdev(int which)
{
    nt_whichdac = which;
}

void nt_midiindev(int which)
{
    fprintf(stderr, "pd: midi input selection unsupported\n");
}

void nt_midioutdev(int which)
{
    nt_whichmidiout = which;
}
