/* 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 "desire.h" #include #include #include using namespace desire; /* ------------------------- audio -------------------------- */ static void nt_noresync(); #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 * sys_dacblocksize) /* 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_meters; /* true if we're metering */ static float mmi_max, mmo_max; /* max amplitude */ static int mmi_nwave, mmo_nwave; /* number of WAVE devices */ typedef struct _sbuf { HANDLE hData; HPSTR lpData; // pointer to waveform data memory HANDLE hWaveHdr; WAVEHDR *lpWaveHdr; // pointer to header structure } t_sbuf; t_sbuf mmo_vec[NAPORTS][MAXBUFFER]; /* circular buffer array */ t_sbuf mmi_vec[NAPORTS][MAXBUFFER]; /* circular buffer array */ HWAVEOUT mmo_dev[NAPORTS]; /* output device */ HWAVEIN mmi_dev[NAPORTS]; /* input device */ static int mmo_phase[NAPORTS]; /* index of next buffer to send */ static int mmi_phase[NAPORTS]; /* index of next buffer to read */ static UINT nt_whichdac = WAVE_MAPPER, nt_whichadc = WAVE_MAPPER; static void mmi_waveerror(const char *s, int err) {char t[256]; waveInGetErrorText(err, t, 256); error(s,t);} static void mmo_waveerror(const char *s, int err) {char t[256]; waveOutGetErrorText(err, t, 256); error(s,t);} static void wave_prep(t_sbuf *bp, int setdone) { 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->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"); short *sp = (short *)bp->lpData; for (int i=CHANNELS_PER_DEVICE*nt_realdacblksize; 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; } int mmio_do_open_audio() { PCMWAVEFORMAT form; UINT r; static int naudioprepped = 0, nindevsprepped = 0, noutdevsprepped = 0; if (sys_verbose) post("%d devices in, %d devices out", mmi_nwave, mmo_nwave); 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 (mmi_nwave <= 1 && mmo_nwave <= 1) nt_noresync(); if (nindevsprepped < mmi_nwave) { for (int i=nindevsprepped; i 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() { for (int n=0; ndwFlags & WHDR_DONE); int donenext = (mmi_vec[n][phase3].lpWaveHdr->dwFlags & WHDR_DONE); if (donethis && !donenext) {if (firstphasebusy >= 0) goto multipleadc; else firstphasebusy = count;} if (!donethis && donenext) {if (firstphasedone >= 0) goto multipleadc; else firstphasedone = count;} phase2 = phase3; phase3 = WRAPFWD(phase2 + 1); } post("nad %d phase %d busy %d done %d", n, phase, firstphasebusy, firstphasedone); continue; multipleadc: startpost("nad %d phase %d: oops:", n, phase); for (int count=0; countdwFlags & WHDR_DONE)); poststring(buf); } endpost(); } for (int n=0; ndwFlags & WHDR_DONE); int donenext = (mmo_vec[n][phase3].lpWaveHdr->dwFlags & WHDR_DONE); if (donethis && !donenext) {if (firstphasebusy >= 0) goto multipledac; else firstphasebusy = count;} if (!donethis && donenext) {if (firstphasedone >= 0) goto multipledac; else firstphasedone = count;} phase2 = phase3; phase3 = WRAPFWD(phase2 + 1); } if (firstphasebusy < 0) post("nda %d phase %d all %d", n, phase, (mmo_vec[n][0].lpWaveHdr->dwFlags & WHDR_DONE)); else post("nda %d phase %d busy %d done %d", n, phase, firstphasebusy, firstphasedone); continue; multipledac: startpost("nda %d phase %d: oops:", n, phase); for (count = 0; count < nt_naudiobuffer; count++) { char buf[80]; sprintf(buf, " %d", (mmo_vec[n][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() {nt_resync_cancelled = 1;} static void nt_resyncaudio() { UINT r; 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 (int n=0; ndwFlags & WHDR_DONE)) break; if (h->dwFlags & WHDR_PREPARED) waveInUnprepareHeader(mmi_dev[n], h, sizeof(WAVEHDR)); h->dwFlags = 0L; waveInPrepareHeader(mmi_dev[n], h, sizeof(WAVEHDR)); r = waveInAddBuffer(mmi_dev[n], h, sizeof(WAVEHDR)); if (r != MMSYSERR_NOERROR) mmi_waveerror("waveInAddBuffer: %s", r); mmi_phase[n] = phase = WRAPFWD(phase+1); } if (count == MAXRESYNC) post("resync error at input"); } /* Each output buffer which is "ready" is filled with zeros and queued. */ for (int n=0; ndwFlags & WHDR_DONE)) break; if (h->dwFlags & WHDR_PREPARED) waveOutUnprepareHeader(mmo_dev[n], h, sizeof(WAVEHDR)); h->dwFlags = 0L; memset((char *)(mmo_vec[n][phase].lpData), 0, (CHANNELS_PER_DEVICE * SAMPSIZE * nt_realdacblksize)); waveOutPrepareHeader(mmo_dev[n], h, sizeof(WAVEHDR)); r = waveOutWrite(mmo_dev[n], h, sizeof(WAVEHDR)); if (r != MMSYSERR_NOERROR) mmo_waveerror("waveOutAddBuffer: %s", r); mmo_phase[n] = phase = WRAPFWD(phase+1); } if (count == MAXRESYNC) post("resync error at output"); } #ifdef MIDI_TIMESTAMP nt_resetmidisync(); #endif } #define LATE 0 #define RESYNC 1 #define NOTHING 2 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 } static int mmio_send_dacs() { UINT r; if (!mmi_nwave && !mmo_nwave) return 0; if (nt_meters) { float maxsamp = mmi_max; for (int i=0, n=2*mmi_nwave*sys_dacblocksize; i maxsamp) maxsamp = f; else if (-f > maxsamp) maxsamp = -f; } mmi_max = maxsamp; maxsamp = mmo_max; for (int i=0, n=2*mmo_nwave*sys_dacblocksize; i maxsamp) maxsamp = f; else if (-f > maxsamp) maxsamp = -f; } mmo_max = 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 (int n=0; ndwFlags & WHDR_DONE)) goto idle; } for (int n=0; ndwFlags & WHDR_DONE)) goto idle; } for (int n=0; ndwFlags & WHDR_PREPARED) waveInUnprepareHeader(mmi_dev[n],h,sizeof(WAVEHDR)); } for (int n=0; ndwFlags & WHDR_PREPARED) waveOutUnprepareHeader(mmo_dev[n],h,sizeof(WAVEHDR)); } } /* Convert audio output to fixed-point and put it in the output buffer. */ short *sp1, *sp2; float *fp1, *fp2; fp1 = sys_soundout; for (int n=0; ndwFlags & WHDR_DONE) goto late; } for (int n=0; ndwFlags & 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 (int n=0; ndwFlags & WHDR_DONE) {nt_resyncaudio(); return 0;} } /* test dac sync the same way */ for (int n=0; ndwFlags & WHDR_DONE) {nt_resyncaudio(); return 0;} } #ifdef MIDI_TIMESTAMP nt_midisync(); #endif return 0; } /* ------------------- public routines -------------------------- */ static int mmio_open_audio( int naudioidev, int *audioidev, int nchidev, int *chidev, int naudioodev, int *audioodev, int nchodev, int *chodev, int rate, int dummy) /* IOhannes */ { nt_realdacblksize = (sys_blocksize ? sys_blocksize : DEFREALDACBLKSIZE); int nbuf = sys_advance_samples/nt_realdacblksize; if (nbuf >= MAXBUFFER) { post("pd: audio buffering maxed out to %d", int(MAXBUFFER * ((nt_realdacblksize * 1000.)/44100.))); nbuf = MAXBUFFER; } else if (nbuf < 4) nbuf = 4; post("%d audio buffers", nbuf); nt_naudiobuffer = nbuf; if (nt_adcjitterbufsallowed > nbuf-2) nt_adcjitterbufsallowed = nbuf-2; if (nt_dacjitterbufsallowed > nbuf-2) nt_dacjitterbufsallowed = nbuf-2; mmi_nwave = sys_inchannels / 2; mmo_nwave = sys_outchannels / 2; nt_whichadc = (naudioidev < 1 ? (mmi_nwave > 1 ? WAVE_MAPPER : -1) : audioidev[0]); nt_whichdac = (naudioodev < 1 ? (mmo_nwave > 1 ? WAVE_MAPPER : -1) : audioodev[0]); if (naudioodev>1 || naudioidev>1) post("separate audio device choice not supported; using sequential devices."); mmio_do_open_audio(); return 0; } #if 0 /* list the audio and MIDI device names */ void mmio_listdevs() { UINT ndevices = waveInGetNumDevs(); for (unsigned i=0; i