/* Copyright (c) 1997-1999 Miller Puckette.
* For information on usage and redistribution, and for a DISCLAIMER OF ALL
* WARRANTIES, see the file, "LICENSE.txt," in this distribution.  */

/* modified 2/98 by Winfried Ritsch to deal with up to 4 synchronized
"wave" devices, which is how ADAT boards appear to the WAVE API. */

#include "m_pd.h"
#include "s_stuff.h"
#include <stdio.h>

#include <windows.h>

#include <MMSYSTEM.H>

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

static void nt_close_midiin(void);
static void nt_noresync( void);

static void postflags(void);

#define NAPORTS 16   /* wini hack for multiple ADDA devices  */
#define CHANNELS_PER_DEVICE 2
#define DEFAULTCHANS 2
#define DEFAULTSRATE 44100
#define SAMPSIZE 2

int nt_realdacblksize;
#define DEFREALDACBLKSIZE (4 * DEFDACBLKSIZE) /* larger underlying bufsize */

#define MAXBUFFER 100   /* number of buffers in use at maximum advance */
#define DEFBUFFER 30	/* default is about 30x6 = 180 msec! */
static int nt_naudiobuffer = DEFBUFFER;
float sys_dacsr = DEFAULTSRATE;

static int nt_whichapi = API_MMIO;
static int nt_meters;        /* true if we're metering */
static float nt_inmax;       /* max input amplitude */
static float nt_outmax;      /* max output amplitude */
static int nt_nwavein, nt_nwaveout; 	/* number of WAVE devices in and out */

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[NAPORTS][MAXBUFFER];    /* circular buffer array */
HWAVEOUT ntsnd_outdev[NAPORTS];  	    /* output device */
static int ntsnd_outphase[NAPORTS]; 	    /* index of next buffer to send */

t_sbuf ntsnd_invec[NAPORTS][MAXBUFFER];	    /* circular buffer array */
HWAVEIN ntsnd_indev[NAPORTS];  	    	    /* input device */
static int ntsnd_inphase[NAPORTS]; 	    /* 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, int setdone)
{
    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) (CHANNELS_PER_DEVICE * SAMPSIZE * nt_realdacblksize)))) 
	    	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 = CHANNELS_PER_DEVICE * nt_realdacblksize,
    	sp = (short *)bp->lpData; i--; )
    	    *sp++ = 0;

    wh->lpData = bp->lpData;
    wh->dwBufferLength = (CHANNELS_PER_DEVICE * SAMPSIZE * nt_realdacblksize);
    wh->dwFlags = 0;
    wh->dwLoops = 0L;
    wh->lpNext = 0;
    wh->reserved = 0;
    	/* optionally (for writing) set DONE flag as if we had queued them */
    if (setdone)
    	wh->dwFlags = WHDR_DONE;
}

static UINT nt_whichdac = WAVE_MAPPER, nt_whichadc = WAVE_MAPPER;

int mmio_do_open_audio(void)
{ 
    PCMWAVEFORMAT  form; 
    int i, j;
    UINT mmresult;
    int nad, nda;
    static int naudioprepped = 0, nindevsprepped = 0, noutdevsprepped = 0;
    if (sys_verbose)
    	post("%d devices in, %d devices out",
    	    nt_nwavein, nt_nwaveout);

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

    if (nt_nwavein <= 1 && nt_nwaveout <= 1)
    	nt_noresync();

    if (nindevsprepped < nt_nwavein)
    {
    	for (i = nindevsprepped; i < nt_nwavein; i++)
    	    for (j = 0; j < naudioprepped; j++)
	    	wave_prep(&ntsnd_invec[i][j], 0);
    	nindevsprepped = nt_nwavein;
    }
    if (noutdevsprepped < nt_nwaveout)
    {
    	for (i = noutdevsprepped; i < nt_nwaveout; i++)
    	    for (j = 0; j < naudioprepped; j++)
	    	wave_prep(&ntsnd_outvec[i][j], 1);
    	noutdevsprepped = nt_nwaveout;
    }
    if (naudioprepped < nt_naudiobuffer)
    {
    	for (j = naudioprepped; j < nt_naudiobuffer; j++)
	{
    	    for (i = 0; i < nt_nwavein; i++)
	    	wave_prep(&ntsnd_invec[i][j], 0);
    	    for (i = 0; i < nt_nwaveout; i++)
	    	wave_prep(&ntsnd_outvec[i][j], 1);
    	}
	naudioprepped = nt_naudiobuffer;
    }
    for (nad=0; nad < nt_nwavein; nad++)
    {
	/* Open waveform device(s), sucessively numbered, for input */

	mmresult = waveInOpen(&ntsnd_indev[nad], nt_whichadc+nad,
     		(WAVEFORMATEX *)(&form), 0L, 0L, CALLBACK_NULL);

	if (sys_verbose)
    	    printf("opened adc device %d with return %d\n",
    		nt_whichadc+nad,mmresult);

	if (mmresult != MMSYSERR_NOERROR) 
	{
	    nt_waveinerror("waveInOpen: %s\n", mmresult);
	    nt_nwavein = nad; /* nt_nwavein = 0 wini */
	} 
	else 
	{
	    for (i = 0; i < nt_naudiobuffer; i++)
	    {
		mmresult = waveInPrepareHeader(ntsnd_indev[nad],
	    	    ntsnd_invec[nad][i].lpWaveHdr, sizeof(WAVEHDR));
		if (mmresult != MMSYSERR_NOERROR)
	    	    nt_waveinerror("waveinprepareheader: %s\n", mmresult);
		mmresult = waveInAddBuffer(ntsnd_indev[nad],
	    	    ntsnd_invec[nad][i].lpWaveHdr, sizeof(WAVEHDR));
		if (mmresult != MMSYSERR_NOERROR)
	    	    nt_waveinerror("waveInAddBuffer: %s\n", mmresult);
	    }
	}
    }
    	/* quickly start them all together */
    for (nad = 0; nad < nt_nwavein; nad++)
    	waveInStart(ntsnd_indev[nad]);

    for (nda = 0; nda < nt_nwaveout; nda++)
    {	
    	    /* Open a waveform device for output in sucessiv device numbering*/
    	mmresult = waveOutOpen(&ntsnd_outdev[nda], nt_whichdac + nda,
    	    (WAVEFORMATEX *)(&form), 0L, 0L, CALLBACK_NULL);

    	if (sys_verbose)
	    fprintf(stderr,"opened dac device %d, with return %d\n",
	    	nt_whichdac +nda, mmresult);

    	if (mmresult != MMSYSERR_NOERROR)
    	{
	    fprintf(stderr,"Wave out open device %d + %d\n",nt_whichdac,nda);
            nt_waveouterror("waveOutOpen device: %s\n",  mmresult);
            nt_nwaveout = nda;
    	}
    }

    return (0);
}

void mmio_close_audio( void)
{
    int errcode;
    int nda, nad;
    if (sys_verbose)
    	post("closing audio...");

    for (nda=0; nda < nt_nwaveout; nda++) /*if (nt_nwaveout) wini */
    {
       errcode = waveOutReset(ntsnd_outdev[nda]);
       if (errcode != MMSYSERR_NOERROR)
	   printf("error resetting output %d: %d\n", nda, errcode);
       errcode = waveOutClose(ntsnd_outdev[nda]);
       if (errcode != MMSYSERR_NOERROR)
	   printf("error closing output %d: %d\n",nda , errcode);	   
    }
    nt_nwaveout = 0;

    for(nad=0; nad < nt_nwavein;nad++) /* if (nt_nwavein) wini */
    {
	errcode = waveInReset(ntsnd_indev[nad]);
	if (errcode != MMSYSERR_NOERROR)
	    printf("error resetting input: %d\n", errcode);
	errcode = waveInClose(ntsnd_indev[nad]);
	if (errcode != MMSYSERR_NOERROR)
	    printf("error closing input: %d\n", errcode);
    }
    nt_nwavein = 0;
}


#define ADCJITTER 10	/* We tolerate X buffers of jitter by default */
#define DACJITTER 10

static int nt_adcjitterbufsallowed = ADCJITTER;
static int nt_dacjitterbufsallowed = DACJITTER;

    /* ------------- MIDI time stamping from audio clock ------------ */

#ifdef MIDI_TIMESTAMP

static double nt_hibuftime;
static double initsystime = -1;

    /* call this whenever we reset audio */
static void nt_resetmidisync(void)
{
    initsystime = clock_getsystime();
    nt_hibuftime = sys_getrealtime();
}

    /* call this whenever we're idled waiting for audio to be ready. 
    The routine maintains a high and low water point for the difference
    between real and DAC time. */

static void nt_midisync(void)
{
    double jittersec, diff;
    
    if (initsystime == -1) nt_resetmidisync();
    jittersec = (nt_dacjitterbufsallowed > nt_adcjitterbufsallowed ?
    	nt_dacjitterbufsallowed : nt_adcjitterbufsallowed)
    	    * nt_realdacblksize / sys_getsr();
    diff = sys_getrealtime() - 0.001 * clock_gettimesince(initsystime);
    if (diff > nt_hibuftime) nt_hibuftime = diff;
    if (diff < nt_hibuftime - jittersec)
    {
    	post("jitter excess %d %f", dac, diff);
    	nt_resetmidisync();
    }
}

static double nt_midigettimefor(LARGE_INTEGER timestamp)
{
    /* this is broken now... used to work when "timestamp" was derived from
    	QueryPerformanceCounter() instead of the gates approved 
	    timeGetSystemTime() call in the MIDI callback routine below. */
    return (nt_tixtotime(timestamp) - nt_hibuftime);
}
#endif	    /* MIDI_TIMESTAMP */


static int nt_fill = 0;
#define WRAPFWD(x) ((x) >= nt_naudiobuffer ? (x) - nt_naudiobuffer: (x))
#define WRAPBACK(x) ((x) < 0 ? (x) + nt_naudiobuffer: (x))
#define MAXRESYNC 500

#if 0 	    /* this is used for debugging */
static void nt_printaudiostatus(void)
{
    int nad, nda;
    for (nad = 0; nad < nt_nwavein; nad++)
    {
    	int phase = ntsnd_inphase[nad];
    	int phase2 = phase, phase3 = WRAPFWD(phase2), count, ntrans = 0;
    	int firstphasedone = -1, firstphasebusy = -1;
    	for (count = 0; count < nt_naudiobuffer; count++)
    	{
    	    int donethis =
    	    	(ntsnd_invec[nad][phase2].lpWaveHdr->dwFlags & WHDR_DONE);
    	    int donenext =
    	    	(ntsnd_invec[nad][phase3].lpWaveHdr->dwFlags & WHDR_DONE);
    	    if (donethis && !donenext)
    	    {
    	    	if (firstphasebusy >= 0) goto multipleadc;
    	    	firstphasebusy = count;
    	    }
    	    if (!donethis && donenext)
    	    {
    	    	if (firstphasedone >= 0) goto multipleadc;
    	    	firstphasedone = count;
    	    }
    	    phase2 = phase3;
    	    phase3 = WRAPFWD(phase2 + 1);
    	}
    	post("nad %d phase %d busy %d done %d", nad, phase, firstphasebusy,
    	    firstphasedone);
    	continue;
    multipleadc:
	startpost("nad %d phase %d: oops:", nad, phase);
	for (count = 0; count < nt_naudiobuffer; count++)
	{
    	    char buf[80];
    	    sprintf(buf, " %d", 
    		(ntsnd_invec[nad][count].lpWaveHdr->dwFlags & WHDR_DONE));
    	    poststring(buf);
	}
	endpost();
    }
    for (nda = 0; nda < nt_nwaveout; nda++)
    {
    	int phase = ntsnd_outphase[nad];
    	int phase2 = phase, phase3 = WRAPFWD(phase2), count, ntrans = 0;
    	int firstphasedone = -1, firstphasebusy = -1;
    	for (count = 0; count < nt_naudiobuffer; count++)
    	{
    	    int donethis =
    	    	(ntsnd_outvec[nda][phase2].lpWaveHdr->dwFlags & WHDR_DONE);
    	    int donenext =
    	    	(ntsnd_outvec[nda][phase3].lpWaveHdr->dwFlags & WHDR_DONE);
    	    if (donethis && !donenext)
    	    {
    	    	if (firstphasebusy >= 0) goto multipledac;
    	    	firstphasebusy = count;
    	    }
    	    if (!donethis && donenext)
    	    {
    	    	if (firstphasedone >= 0) goto multipledac;
    	    	firstphasedone = count;
    	    }
    	    phase2 = phase3;
    	    phase3 = WRAPFWD(phase2 + 1);
    	}
    	if (firstphasebusy < 0) post("nda %d phase %d all %d",
    	    nda, phase, (ntsnd_outvec[nad][0].lpWaveHdr->dwFlags & WHDR_DONE));
    	else post("nda %d phase %d busy %d done %d", nda, phase, firstphasebusy,
    	    firstphasedone);
    	continue;
    multipledac:
	startpost("nda %d phase %d: oops:", nda, phase);
	for (count = 0; count < nt_naudiobuffer; count++)
	{
    	    char buf[80];
    	    sprintf(buf, " %d", 
    		(ntsnd_outvec[nad][count].lpWaveHdr->dwFlags & WHDR_DONE));
    	    poststring(buf);
	}
	endpost();
    }
}
#endif /* 0 */

/* this is a hack to avoid ever resyncing audio pointers in case for whatever
reason the sync testing below gives false positives. */

static int nt_resync_cancelled;

static void nt_noresync( void)
{
    nt_resync_cancelled = 1;
}

static void nt_resyncaudio(void)
{
    UINT mmresult; 
    int nad, nda, count;
    if (nt_resync_cancelled)
    	return;
    	/* for each open input device, eat all buffers which are marked
    	    ready.  The next one will thus be "busy".  */
    post("resyncing audio");
    for (nad = 0; nad < nt_nwavein; nad++)
    {
    	int phase = ntsnd_inphase[nad];
    	for (count = 0; count < MAXRESYNC; count++)
    	{
	    WAVEHDR *inwavehdr = ntsnd_invec[nad][phase].lpWaveHdr;
	    if (!(inwavehdr->dwFlags & WHDR_DONE)) break;
	    if (inwavehdr->dwFlags & WHDR_PREPARED)
    		waveInUnprepareHeader(ntsnd_indev[nad],
    		    inwavehdr, sizeof(WAVEHDR)); 
	    inwavehdr->dwFlags = 0L;
	    waveInPrepareHeader(ntsnd_indev[nad], inwavehdr, sizeof(WAVEHDR)); 
	    mmresult = waveInAddBuffer(ntsnd_indev[nad], inwavehdr,
	    	sizeof(WAVEHDR));
	    if (mmresult != MMSYSERR_NOERROR)
        	nt_waveinerror("waveInAddBuffer: %s\n", mmresult);
	    ntsnd_inphase[nad] = phase = WRAPFWD(phase + 1);
    	}
    	if (count == MAXRESYNC) post("resync error 1");
    }
    	/* Each output buffer which is "ready" is filled with zeros and
    	queued. */
    for (nda = 0; nda < nt_nwaveout; nda++)
    {
    	int phase = ntsnd_outphase[nda];
    	for (count = 0; count < MAXRESYNC; count++)
    	{
	    WAVEHDR *outwavehdr = ntsnd_outvec[nda][phase].lpWaveHdr;
	    if (!(outwavehdr->dwFlags & WHDR_DONE)) break;
	    if (outwavehdr->dwFlags & WHDR_PREPARED)
    		waveOutUnprepareHeader(ntsnd_outdev[nda],
    		    outwavehdr, sizeof(WAVEHDR)); 
	    outwavehdr->dwFlags = 0L;
	    memset((char *)(ntsnd_outvec[nda][phase].lpData),
	    	0, (CHANNELS_PER_DEVICE * SAMPSIZE * nt_realdacblksize));
	    waveOutPrepareHeader(ntsnd_outdev[nda], outwavehdr,
	    	sizeof(WAVEHDR)); 
	    mmresult = waveOutWrite(ntsnd_outdev[nda], outwavehdr,
	    	sizeof(WAVEHDR)); 
	    if (mmresult != MMSYSERR_NOERROR)
        	nt_waveouterror("waveOutAddBuffer: %s\n", mmresult);
	    ntsnd_outphase[nda] = phase = WRAPFWD(phase + 1);
    	}
    	if (count == MAXRESYNC) post("resync error 2");
    }

#ifdef MIDI_TIMESTAMP
    nt_resetmidisync();
#endif

} 

#define LATE 0
#define RESYNC 1
#define NOTHING 2
static int nt_errorcount;
static int nt_resynccount;
static double nt_nextreporttime = -1;

void nt_logerror(int which)
{
#if 0
    post("error %d %d", count, which);
    if (which < NOTHING) nt_errorcount++;
    if (which == RESYNC) nt_resynccount++;
    if (sys_getrealtime() > nt_nextreporttime)
    {
    	post("%d audio I/O error%s", nt_errorcount,
    	    (nt_errorcount > 1 ? "s" : ""));
    	if (nt_resynccount) post("DAC/ADC sync error");
    	nt_errorcount = nt_resynccount = 0;
    	nt_nextreporttime = sys_getrealtime() - 5;
    }
#endif
}

/* system buffer with t_sample types for one tick */
t_sample *sys_soundout;
t_sample *sys_soundin;
float sys_dacsr;

int mmio_send_dacs(void)
{
    HMMIO hmmio; 
    UINT mmresult; 
    HANDLE hFormat; 
    int i, j;
    short *sp1, *sp2;
    float *fp1, *fp2;
    int nextfill, doxfer = 0;
    int nda, nad;
    if (!nt_nwavein && !nt_nwaveout) return (0);


    if (nt_meters)
    {
        int i, n;
        float maxsamp;
        for (i = 0, n = 2 * nt_nwavein * DEFDACBLKSIZE, maxsamp = nt_inmax;
            i < n; i++)
        {
            float f = sys_soundin[i];
            if (f > maxsamp) maxsamp = f;
            else if (-f > maxsamp) maxsamp = -f;
        }
        nt_inmax = maxsamp;
        for (i = 0, n = 2 * nt_nwaveout * DEFDACBLKSIZE, maxsamp = nt_outmax;
            i < n; i++)
        {
            float f = sys_soundout[i];
            if (f > maxsamp) maxsamp = f;
            else if (-f > maxsamp) maxsamp = -f;
        }
        nt_outmax = maxsamp;
    }

    	/* the "fill pointer" nt_fill controls where in the next
    	I/O buffers we will write and/or read.  If it's zero, we
    	first check whether the buffers are marked "done". */

    if (!nt_fill)
    {
	for (nad = 0; nad < nt_nwavein; nad++)
	{
    	    int phase = ntsnd_inphase[nad];
    	    WAVEHDR *inwavehdr = ntsnd_invec[nad][phase].lpWaveHdr;
    	    if (!(inwavehdr->dwFlags & WHDR_DONE)) goto idle;
	}
	for (nda = 0; nda < nt_nwaveout; nda++)
	{
    	    int phase = ntsnd_outphase[nda];
    	    WAVEHDR *outwavehdr =
    		ntsnd_outvec[nda][phase].lpWaveHdr;
    	    if (!(outwavehdr->dwFlags & WHDR_DONE)) goto idle;
	}
	for (nad = 0; nad < nt_nwavein; nad++)
	{
    	    int phase = ntsnd_inphase[nad];
    	    WAVEHDR *inwavehdr =
    		ntsnd_invec[nad][phase].lpWaveHdr;
    	    if (inwavehdr->dwFlags & WHDR_PREPARED)
    	    	waveInUnprepareHeader(ntsnd_indev[nad],
    	    	    inwavehdr, sizeof(WAVEHDR)); 
    	}
	for (nda = 0; nda < nt_nwaveout; nda++)
	{
    	    int phase = ntsnd_outphase[nda];
    	    WAVEHDR *outwavehdr = ntsnd_outvec[nda][phase].lpWaveHdr;
    	    if (outwavehdr->dwFlags & WHDR_PREPARED)
	    	waveOutUnprepareHeader(ntsnd_outdev[nda],
	    	    outwavehdr, sizeof(WAVEHDR)); 
	}
    }

    	/* Convert audio output to fixed-point and put it in the output
    	buffer. */ 
    for (nda = 0, fp1 = sys_soundout; nda < nt_nwaveout; nda++)
    {
    	int phase = ntsnd_outphase[nda];

	for (i = 0, sp1 = (short *)(ntsnd_outvec[nda][phase].lpData) +
	    CHANNELS_PER_DEVICE * nt_fill;
	    	i < 2; i++, fp1 += DEFDACBLKSIZE, sp1++)
    	{
    	    for (j = 0, fp2 = fp1, sp2 = sp1; j < DEFDACBLKSIZE;
    	    	j++, fp2++, sp2 += CHANNELS_PER_DEVICE)
    	    {
    		int x1 = 32767.f * *fp2;
    		if (x1 > 32767) x1 = 32767;
    		else if (x1 < -32767) x1 = -32767;
    		*sp2 = x1;  
	    }
    	}
    }
    memset(sys_soundout, 0, 
    	(DEFDACBLKSIZE *sizeof(t_sample)*CHANNELS_PER_DEVICE)*nt_nwaveout);

    	/* vice versa for the input buffer */ 

    for (nad = 0, fp1 = sys_soundin; nad < nt_nwavein; nad++)
    {
    	int phase = ntsnd_inphase[nad];

	for (i = 0, sp1 = (short *)(ntsnd_invec[nad][phase].lpData) +
	    CHANNELS_PER_DEVICE * nt_fill;
	    	i < 2; i++, fp1 += DEFDACBLKSIZE, sp1++)
    	{
    	    for (j = 0, fp2 = fp1, sp2 = sp1; j < DEFDACBLKSIZE;
    	    	j++, fp2++, sp2 += CHANNELS_PER_DEVICE)
    	    {
    	    	*fp2 = ((float)(1./32767.)) * (float)(*sp2);    
    	    }
    	}
    }

    nt_fill = nt_fill + DEFDACBLKSIZE;
    if (nt_fill == nt_realdacblksize)
    {
    	nt_fill = 0;

	for (nad = 0; nad < nt_nwavein; nad++)
	{
    	    int phase = ntsnd_inphase[nad];
    	    HWAVEIN device = ntsnd_indev[nad];
    	    WAVEHDR *inwavehdr = ntsnd_invec[nad][phase].lpWaveHdr;
    	    waveInPrepareHeader(device, inwavehdr, sizeof(WAVEHDR)); 
    	    mmresult = waveInAddBuffer(device, inwavehdr, sizeof(WAVEHDR)); 
    	    if (mmresult != MMSYSERR_NOERROR)
        	nt_waveinerror("waveInAddBuffer: %s\n", mmresult);
    	    ntsnd_inphase[nad] = WRAPFWD(phase + 1);
    	}
	for (nda = 0; nda < nt_nwaveout; nda++)
	{
    	    int phase = ntsnd_outphase[nda];
    	    HWAVEOUT device = ntsnd_outdev[nda];
    	    WAVEHDR *outwavehdr = ntsnd_outvec[nda][phase].lpWaveHdr;
	    waveOutPrepareHeader(device, outwavehdr, sizeof(WAVEHDR)); 
	    mmresult = waveOutWrite(device, outwavehdr, sizeof(WAVEHDR)); 
            if (mmresult != MMSYSERR_NOERROR)
        	nt_waveouterror("waveOutWrite: %s\n", mmresult);
	    ntsnd_outphase[nda] = WRAPFWD(phase + 1);
	}   	

    	    /* check for DAC underflow or ADC overflow. */
	for (nad = 0; nad < nt_nwavein; nad++)
	{
    	    int phase = WRAPBACK(ntsnd_inphase[nad] - 2);
    	    WAVEHDR *inwavehdr = ntsnd_invec[nad][phase].lpWaveHdr;
    	    if (inwavehdr->dwFlags & WHDR_DONE) goto late;
    	}
	for (nda = 0; nda < nt_nwaveout; nda++)
	{
    	    int phase = WRAPBACK(ntsnd_outphase[nda] - 2);
    	    WAVEHDR *outwavehdr = ntsnd_outvec[nda][phase].lpWaveHdr;
    	    if (outwavehdr->dwFlags & WHDR_DONE) goto late;
	}   	
    }
    return (1);

late:

    nt_logerror(LATE);
    nt_resyncaudio();
    return (1);

idle:

    /* If more than nt_adcjitterbufsallowed ADC buffers are ready
    on any input device, resynchronize */

    for (nad = 0; nad < nt_nwavein; nad++)
    {
    	int phase = ntsnd_inphase[nad];
    	WAVEHDR *inwavehdr =
    	    ntsnd_invec[nad]
    	    	[WRAPFWD(phase + nt_adcjitterbufsallowed)].lpWaveHdr;
    	if (inwavehdr->dwFlags & WHDR_DONE)
    	{
    	    nt_resyncaudio();
    	    return (0);
    	}
    }

    	/* test dac sync the same way */
    for (nda = 0; nda < nt_nwaveout; nda++)
    {
    	int phase = ntsnd_outphase[nda];
    	WAVEHDR *outwavehdr =
    	    ntsnd_outvec[nda]
    	    	[WRAPFWD(phase + nt_dacjitterbufsallowed)].lpWaveHdr;
    	if (outwavehdr->dwFlags & WHDR_DONE)
    	{
    	    nt_resyncaudio();
    	    return (0);
    	}
    }
#ifdef MIDI_TIMESTAMP
    nt_midisync();
#endif
    return (0);
}

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

void mmio_open_audio(int naudioindev, int *audioindev,
    int nchindev, int *chindev, int naudiooutdev, int *audiooutdev,
    int nchoutdev, int *choutdev, int rate) /* IOhannes */
{
    int nbuf;

    nt_realdacblksize = (sys_blocksize ? sys_blocksize : DEFREALDACBLKSIZE);
    nbuf = sys_advance_samples/nt_realdacblksize;
    if (nbuf >= MAXBUFFER)
    {
    	fprintf(stderr, "pd: audio buffering maxed out to %d\n",
    	    (int)(MAXBUFFER * ((nt_realdacblksize * 1000.)/44100.)));
    	nbuf = MAXBUFFER;
    }
    else if (nbuf < 4) nbuf = 4;
    fprintf(stderr, "%d audio buffers\n", nbuf);
    nt_naudiobuffer = nbuf;
    if (nt_adcjitterbufsallowed > nbuf - 2)
    	nt_adcjitterbufsallowed = nbuf - 2;
    if (nt_dacjitterbufsallowed > nbuf - 2)
    	nt_dacjitterbufsallowed = nbuf - 2;

    nt_nwavein = sys_inchannels / 2;
    nt_nwaveout = sys_outchannels / 2;
    nt_whichadc = (naudioindev < 1 ?
    	(nt_nwavein > 1 ? WAVE_MAPPER : -1) : audioindev[0]);
    nt_whichdac = (naudiooutdev < 1 ?
    	(nt_nwaveout > 1 ? WAVE_MAPPER : -1) : audiooutdev[0]);
    if (naudiooutdev > 1 || naudioindev > 1)
 post("separate audio device choice not supported; using sequential devices.");
    mmio_do_open_audio();
}


void mmio_reportidle(void)
{
}

#if 0
/* list the audio and MIDI device names */
void mmio_listdevs(void)
{
    UINT  wRtn, ndevices;
    unsigned int i;

    ndevices = waveInGetNumDevs();
    for (i = 0; i < ndevices; 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);
    }

    ndevices = waveOutGetNumDevs();
    for (i = 0; i < ndevices; 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);
    }
}
#endif

void mmio_getdevs(char *indevlist, int *nindevs,
    char *outdevlist, int *noutdevs, int *canmulti, 
    	int maxndev, int devdescsize)
{
    int  wRtn, ndev, i;

    *canmulti = 2;  /* supports multiple devices */
    ndev = waveInGetNumDevs();
    if (ndev > maxndev)
    	ndev = maxndev;
    *nindevs = ndev;
    for (i = 0; i < ndev; i++)
    {
    	WAVEINCAPS wicap;
    	wRtn = waveInGetDevCaps(i, (LPWAVEINCAPS) &wicap, sizeof(wicap));
	sprintf(indevlist + i * devdescsize, (wRtn ? "???" : wicap.szPname));
    }

    ndev = waveOutGetNumDevs();
    if (ndev > maxndev)
    	ndev = maxndev;
    *noutdevs = ndev;
    for (i = 0; i < ndev; i++)
    {
    	WAVEOUTCAPS wocap;
    	wRtn = waveOutGetDevCaps(i, (LPWAVEOUTCAPS) &wocap, sizeof(wocap));
	sprintf(outdevlist + i * devdescsize, (wRtn ? "???" : wocap.szPname));
    }
}

