/* 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 "s_utf8.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 SAMPSIZE 2
#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;
static int nt_whichapi = API_MMIO;
static int nt_meters; /* true if we're metering */
static t_sample nt_inmax; /* max input amplitude */
static t_sample nt_outmax; /* max output amplitude */
static int nt_nwavein, nt_nwaveout; /* number of WAVE devices in and out */
int nt_blocksize;
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 mmio_allocbufs(t_sbuf bp[][MAXBUFFER],
int ndevs, int nbufs, int blocksize, int setdone)
{
short *sp;
int i, devno, bufno;
for (devno = 0; devno < ndevs; devno++)
for (bufno = 0; bufno < nbufs; bufno++)
{
WAVEHDR *wh;
/*
* 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[devno][bufno].hData =
GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE,
(DWORD) (CHANNELS_PER_DEVICE * SAMPSIZE * blocksize))))
fprintf(stderr, "alloc 1 failed\n");
if (!(bp[devno][bufno].lpData =
(HPSTR) GlobalLock(bp[devno][bufno].hData)))
printf("lock 1 failed\n");
/* Allocate and lock memory for the header. */
if (!(bp[devno][bufno].hWaveHdr =
GlobalAlloc(GMEM_MOVEABLE | GMEM_SHARE, (DWORD) sizeof(WAVEHDR))))
printf("alloc 2 failed\n");
if (!(wh = bp[devno][bufno].lpWaveHdr =
(WAVEHDR *) GlobalLock(bp[devno][bufno].hWaveHdr)))
printf("lock 2 failed\n");
for (i = CHANNELS_PER_DEVICE * blocksize,
sp = (short *)bp[devno][bufno].lpData; i--; )
*sp++ = 0;
wh->lpData = bp[devno][bufno].lpData;
wh->dwBufferLength =
(CHANNELS_PER_DEVICE * SAMPSIZE * blocksize);
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 void mmio_freebufs(t_sbuf bp[][MAXBUFFER],
int ndevs, int nbufs)
{
short *sp;
int i, devno, bufno;
for (devno = 0; devno < ndevs; devno++)
for (bufno = 0; bufno < nbufs; bufno++)
{
/* unlock and free memory for the waveform data. */
GlobalUnlock(bp[devno][bufno].lpData);
GlobalFree(bp[devno][bufno].hData);
/* unlock and free memory for the header. */
GlobalUnlock(bp[devno][bufno].lpWaveHdr);
GlobalFree(bp[devno][bufno].hWaveHdr);
}
}
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;
logpost(NULL, PD_VERBOSE, "%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 = STUFF->st_dacsr;
form.wf.nAvgBytesPerSec = STUFF->st_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();
mmio_allocbufs(ntsnd_invec, nt_nwavein, nt_naudiobuffer, nt_blocksize, 0);
mmio_allocbufs(ntsnd_outvec, nt_nwaveout, nt_naudiobuffer, nt_blocksize, 1);
for (nad = 0; nad < nt_nwavein; nad++)
{
/* Open waveform device(s), successively numbered, for input */
mmresult = waveInOpen(&ntsnd_indev[nad], nt_whichadc+nad,
(WAVEFORMATEX *)(&form), 0L, 0L, CALLBACK_NULL);
if (sys_verbose)
fprintf(stderr, "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 waveform device(s), successively numbered, for output */
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;
logpost(NULL, PD_VERBOSE, "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);
}
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);
}
mmio_freebufs(ntsnd_invec, nt_nwavein, nt_naudiobuffer);
mmio_freebufs(ntsnd_outvec, nt_nwaveout, nt_naudiobuffer);
nt_nwavein = nt_nwaveout = 0;
logpost(NULL, PD_VERBOSE, "done closing audio...");
}
#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_getlogicaltime();
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_blocksize / STUFF->st_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_blocksize));
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 */
int mmio_send_dacs(void)
{
HMMIO hmmio;
UINT mmresult;
HANDLE hFormat;
int i, j;
short *sp1, *sp2;
t_sample *fp1, *fp2;
int nextfill, doxfer = 0;
int nda, nad;
if (!nt_nwavein && !nt_nwaveout) return (0);
if (nt_meters)
{
int i, n;
t_sample maxsamp;
for (i = 0, n = 2 * nt_nwavein * DEFDACBLKSIZE, maxsamp = nt_inmax;
i < n; i++)
{
t_sample f = STUFF->st_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++)
{
t_sample f = STUFF->st_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 = STUFF->st_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(STUFF->st_soundout, 0,
(DEFDACBLKSIZE *sizeof(t_sample)*CHANNELS_PER_DEVICE)*nt_nwaveout);
/* vice versa for the input buffer */
for (nad = 0, fp1 = STUFF->st_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 = ((t_sample)(1./32767.)) * (t_sample)(*sp2);
}
}
}
nt_fill = nt_fill + DEFDACBLKSIZE;
if (nt_fill == nt_blocksize)
{
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 -------------------------- */
int mmio_open_audio(int naudioindev, int *audioindev,
int nchindev, int *chindev, int naudiooutdev, int *audiooutdev,
int nchoutdev, int *choutdev, int rate, int blocksize)
{
int nbuf;
logpost(NULL, PD_VERBOSE, "opening audio...");
nt_blocksize = (blocksize ? blocksize : DEFREALDACBLKSIZE);
nbuf = (double)sys_schedadvance / 1000000. * rate / nt_blocksize;
if (nbuf >= MAXBUFFER)
{
fprintf(stderr, "pd: audio buffering maxed out to %d\n",
(int)(MAXBUFFER * ((nt_blocksize * 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 = STUFF->st_inchannels / 2;
nt_nwaveout = STUFF->st_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.");
return (mmio_do_open_audio());
logpost(NULL, PD_VERBOSE, "done opening 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;
char utf8device[MAXPDSTRING];
*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));
if (!wRtn)
u8_nativetoutf8(utf8device, MAXPDSTRING, wicap.szPname, -1);
pd_snprintf(indevlist + i * devdescsize, devdescsize, "%s",
(wRtn ? "???" : utf8device));
indevlist[(i+1) * devdescsize - 1] = 0;
}
ndev = waveOutGetNumDevs();
if (ndev > maxndev)
ndev = maxndev;
*noutdevs = ndev;
for (i = 0; i < ndev; i++)
{
WAVEOUTCAPS wocap;
wRtn = waveOutGetDevCaps(i, (LPWAVEOUTCAPS) &wocap, sizeof(wocap));
if (!wRtn)
u8_nativetoutf8(utf8device, MAXPDSTRING, wocap.szPname, -1);
pd_snprintf(outdevlist + i * devdescsize, devdescsize, "%s",
(wRtn ? "???" : utf8device));
outdevlist[(i+1) * devdescsize - 1] = 0;
}
}