/* 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_imp.h" #include #include #include /* ------------------------- audio -------------------------- */ static void nt_close_midiin(void); static void postflags(void); #define NAPORTS 16 /* wini hack for multiple ADDA devices */ #define NT_MAXCH (2 * NAPORTS) #define CHANNELS_PER_DEVICE 2 #define DEFAULTCHANS 2 #define DEFAULTSRATE 44100 #define SAMPSIZE 2 #define REALDACBLKSIZE (4 * DACBLKSIZE) /* 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_advance_samples; 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 */ static int nt_blocksize = 0; /* audio I/O block size in sample frames */ 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[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 */ int sys_hipriority = 0; 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) (CHANNELS_PER_DEVICE * REALDACBLKSIZE * SAMPSIZE)))) 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 * REALDACBLKSIZE, sp = (short *)bp->lpData; i--; ) *sp++ = 0; wh->lpData = bp->lpData; wh->dwBufferLength = (CHANNELS_PER_DEVICE * REALDACBLKSIZE * SAMPSIZE); wh->dwFlags = 0; wh->dwLoops = 0L; wh->lpNext = 0; wh->reserved = 0; } static int nt_inalloc[NAPORTS], nt_outalloc[NAPORTS]; static UINT nt_whichdac = WAVE_MAPPER, nt_whichadc = WAVE_MAPPER; int mmio_open_audio(void) { PCMWAVEFORMAT form; int i; UINT mmresult; int nad, nda; 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; 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 { if (!nt_inalloc[nad]) { for (i = 0; i < nt_naudiobuffer; i++) wave_prep(&ntsnd_invec[nad][i]); nt_inalloc[nad] = 1; } 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; } else { if (!(nt_outalloc[nda])) { for (i = 0; i < nt_naudiobuffer; i++) { wave_prep(&ntsnd_outvec[nda][i]); /* set DONE flag as if we had queued them */ ntsnd_outvec[nda][i].lpWaveHdr->dwFlags = WHDR_DONE; } nt_outalloc[nda] = 1; } } } 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) * 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; 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 * REALDACBLKSIZE * SAMPSIZE)); 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 * DACBLKSIZE, 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 * DACBLKSIZE, 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 += DACBLKSIZE, sp1++) { for (j = 0, fp2 = fp1, sp2 = sp1; j < DACBLKSIZE; 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, (DACBLKSIZE*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 += DACBLKSIZE, sp1++) { for (j = 0, fp2 = fp1, sp2 = sp1; j < DACBLKSIZE; j++, fp2++, sp2 += CHANNELS_PER_DEVICE) { *fp2 = ((float)(1./32767.)) * (float)(*sp2); } } } nt_fill = nt_fill + DACBLKSIZE; if (nt_fill == 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); } static void nt_setchsr(int inchannels, int outchannels, int sr) { int inbytes = inchannels * (DACBLKSIZE*sizeof(float)); int outbytes = outchannels * (DACBLKSIZE*sizeof(float)); if (nt_nwavein) free(sys_soundin); if (nt_nwaveout) free(sys_soundout); nt_nwavein = inchannels/CHANNELS_PER_DEVICE; nt_nwaveout = outchannels/CHANNELS_PER_DEVICE; sys_dacsr = sr; sys_soundin = (t_float *)malloc(inbytes); memset(sys_soundin, 0, inbytes); sys_soundout = (t_float *)malloc(outbytes); memset(sys_soundout, 0, outbytes); nt_advance_samples = (sys_schedadvance * sys_dacsr) / (1000000.); if (nt_advance_samples < 3 * DACBLKSIZE) nt_advance_samples = 3 * DACBLKSIZE; } /* ------------------------- MIDI output -------------------------- */ static void nt_midiouterror(char *s, int err) { char t[256]; midiOutGetErrorText(err, t, 256); fprintf(stderr, s, t); } static HMIDIOUT hMidiOut[MAXMIDIOUTDEV]; /* output device */ static int nt_nmidiout; /* number of devices */ static void nt_open_midiout(int nmidiout, int *midioutvec) { UINT result, wRtn; int i; int dev; MIDIOUTCAPS midioutcaps; if (nmidiout > MAXMIDIOUTDEV) nmidiout = MAXMIDIOUTDEV; dev = 0; for (i = 0; i < nmidiout; i++) { MIDIOUTCAPS mocap; result = midiOutOpen(&hMidiOut[dev], midioutvec[i]-1, 0, 0, CALLBACK_NULL); wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) &mocap, sizeof(mocap)); if (result != MMSYSERR_NOERROR) { fprintf(stderr,"midiOutOpen: %s\n",midioutcaps.szPname); nt_midiouterror("midiOutOpen: %s\n", result); } else { if (sys_verbose) fprintf(stderr,"midiOutOpen: Open %s as Port %d\n", midioutcaps.szPname, dev); dev++; } } nt_nmidiout = dev; } void sys_putmidimess(int portno, int a, int b, int c) { DWORD foo; MMRESULT res; if (portno >= 0 && portno < nt_nmidiout) { foo = (a & 0xff) | ((b & 0xff) << 8) | ((c & 0xff) << 16); res = midiOutShortMsg(hMidiOut[portno], foo); if (res != MMSYSERR_NOERROR) post("MIDI out error %d", res); } } void sys_putmidibyte(int portno, int byte) { MMRESULT res; if (portno >= 0 && portno < nt_nmidiout) { res = midiOutShortMsg(hMidiOut[portno], byte); if (res != MMSYSERR_NOERROR) post("MIDI out error %d", res); } } static void nt_close_midiout(void) { int i; for (i = 0; i < nt_nmidiout; i++) { midiOutReset(hMidiOut[i]); midiOutClose(hMidiOut[i]); } nt_nmidiout = 0; } /* -------------------------- MIDI input ---------------------------- */ #define INPUT_BUFFER_SIZE 1000 // 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); } /* Structure to represent a single MIDI event. */ #define EVNT_F_ERROR 0x00000001L typedef struct event_tag { DWORD fdwEvent; DWORD dwDevice; LARGE_INTEGER 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[MAXMIDIINDEV]; 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[MAXMIDIINDEV]; // Device capabilities structures HMIDIIN hMidiIn[MAXMIDIINDEV]; // 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 (!wNumDevices || 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; #ifdef MIDI_TIMESTAMP event.timestamp = timeGetSystemTime(); #endif /* Put the MIDI event in the circular input buffer. */ PutEvent(((LPCALLBACKINSTANCEDATA)dwInstance)->lpBuf, (LPEVENT) &event); break; default: break; } } void nt_open_midiin(int nmidiin, int *midiinvec) { UINT wRtn; char szErrorText[256]; unsigned int i; unsigned int ndev = 0; /* 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; } /* 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<(unsigned)nmidiin) && (idwDevice = i; lpCallbackInstanceData[i]->lpBuf = lpInputBuffer; wRtn = midiInOpen((LPHMIDIIN)&hMidiIn[ndev], midiinvec[i] - 1, (DWORD)midiInputHandler, (DWORD)lpCallbackInstanceData[ndev], CALLBACK_FUNCTION); if (wRtn) { FreeCallbackInstanceData(lpCallbackInstanceData[ndev]); nt_midiinerror("midiInOpen: %s\n", wRtn); } else ndev++; } /* Start MIDI input. */ for (i=0; i= nt_nexteventtime) #endif { int msgtype = ((nt_nextevent.data & 0xf0) >> 4) - 8; int commandbyte = nt_nextevent.data & 0xff; int byte1 = (nt_nextevent.data >> 8) & 0xff; int byte2 = (nt_nextevent.data >> 16) & 0xff; int portno = nt_nextevent.dwDevice; switch (msgtype) { case 0: case 1: case 2: case 3: case 6: sys_midibytein(portno, commandbyte); sys_midibytein(portno, byte1); sys_midibytein(portno, byte2); break; case 4: case 5: sys_midibytein(portno, commandbyte); sys_midibytein(portno, byte1); break; case 7: sys_midibytein(portno, commandbyte); break; } nt_isnextevent = 0; } } } /* ------------------- public routines -------------------------- */ void sys_open_audio(int naudioindev, int *audioindev, int nchindev, int *chindev, int naudiooutdev, int *audiooutdev, int nchoutdev, int *choutdev, int rate) /* IOhannes */ { int inchans, outchans; if (nchindev < 0) inchans = (nchindev < 1 ? -1 : chindev[0]); else { int i = nchindev; int *l = chindev; inchans = 0; while (i--) inchans += *l++; } if (nchoutdev<0) outchans = (nchoutdev < 1 ? -1 : choutdev[0]); else { int i = nchoutdev; int *l = choutdev; outchans = 0; while (i--) outchans += *l++; } if (inchans < 0) inchans = DEFAULTCHANS; if (outchans < 0) outchans = DEFAULTCHANS; if (inchans & 1) { post("input channels rounded up to even number"); inchans += 1; } if (outchans & 1) { post("output channels rounded up to even number"); outchans += 1; } if (inchans > NT_MAXCH) inchans = NT_MAXCH; if (outchans > NT_MAXCH) outchans = NT_MAXCH; if (sys_verbose) post("channels in %d, out %d", inchans, outchans); if (rate < 1) rate = DEFAULTSRATE; nt_setchsr(inchans, outchans, rate); if (nt_whichapi == API_PORTAUDIO) { int blocksize = (nt_blocksize ? nt_blocksize : 256); if (blocksize != (1 << ilog2(blocksize))) post("warning: blocksize adjusted to power of 2: %d", (blocksize = (1 << ilog2(blocksize)))); pa_open_audio(inchans, outchans, rate, sys_soundin, sys_soundout, blocksize, nt_advance_samples/blocksize, (naudioindev < 1 ? -1 : audioindev[0]), (naudiooutdev < 1 ? -1 : audiooutdev[0])); } else { nt_nwavein = inchans / 2; nt_nwaveout = outchans / 2; nt_whichdac = (naudiooutdev < 1 ? (nt_nwaveout > 1 ? 0 : -1) : audiooutdev[0] - 1); nt_whichadc = (naudioindev < 1 ? (nt_nwavein > 1 ? 0 : -1) : audioindev[0] - 1); if (naudiooutdev > 1 || naudioindev > 1) post("separate audio device choice not supported; using sequential devices."); if (nt_blocksize) post("warning: blocksize not settable for MMIO, just ASIO"); mmio_open_audio(); } } void sys_open_midi(int nmidiin, int *midiinvec, int nmidiout, int *midioutvec) { if (nmidiout) nt_open_midiout(nmidiout, midioutvec); if (nmidiin) { post( "midi input enabled; warning, don't close the DOS window directly!"); nt_open_midiin(nmidiin, midiinvec); } else post("not using MIDI input (use 'pd -midiindev 1' to override)"); } float sys_getsr(void) { return (sys_dacsr); } int sys_get_inchannels(void) { return (2 * nt_nwavein); } int sys_get_outchannels(void) { return (2 * nt_nwaveout); } void sys_audiobuf(int n) { /* set the size, in msec, of the audio FIFO. It's incorrect to calculate this on the basis of 44100 sample rate; really, the work should have been done in nt_setchsr(). */ int nbuf = n * (44100./(REALDACBLKSIZE * 1000.)); if (nbuf >= MAXBUFFER) { fprintf(stderr, "pd: audio buffering maxed out to %d\n", (int)(MAXBUFFER * ((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; sys_schedadvance = 1000 * n; } void sys_getmeters(float *inmax, float *outmax) { if (inmax) { nt_meters = 1; *inmax = nt_inmax; *outmax = nt_outmax; } else nt_meters = 0; nt_inmax = nt_outmax = 0; } void sys_reportidle(void) { } int sys_send_dacs(void) { if (nt_whichapi == API_PORTAUDIO) return (pa_send_dacs()); else return (mmio_send_dacs()); } void sys_close_audio( void) { if (nt_whichapi == API_PORTAUDIO) pa_close_audio(); else mmio_close_audio(); } void sys_close_midi( void) { nt_close_midiin(); nt_close_midiout(); } void sys_setblocksize(int n) { if (n < 1) n = 1; nt_blocksize = n; } /* ----------- public routines which are only defined for MSW/NT ---------- */ /* select between MMIO and ASIO audio APIs */ void nt_set_sound_api(int which) { nt_whichapi = which; if (sys_verbose) post("nt_whichapi %d", nt_whichapi); } /* list the audio and MIDI device names */ void sys_listdevs(void) { UINT wRtn, ndevices; 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. */ ndevices = midiInGetNumDevs(); for (i = 0; i < ndevices; 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); } ndevices = midiOutGetNumDevs(); for (i = 0; i < ndevices; 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); } if (nt_whichapi == API_PORTAUDIO) { pa_listdevs(); return; } 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); } } void nt_soundindev(int which) { nt_whichadc = which - 1; } void nt_soundoutdev(int which) { nt_whichdac = which - 1; } void glob_audio(void *dummy, t_floatarg fadc, t_floatarg fdac) { int adc = fadc, dac = fdac; if (!dac && !adc) post("%d channels in, %d channels out", 2 * nt_nwavein, 2 * nt_nwaveout); else { sys_close_audio(); sys_open_audio(1, 0, 1, 0, /* dummy parameters */ 1, &adc, 1, &dac, sys_dacsr); } }