From 4d84d14ac1aa13958eaa2971b03f7f929a519105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Fri, 8 Feb 2008 13:00:32 +0000 Subject: reorganized svn path=/trunk/; revision=9400 --- desiredata/src/s_audio_mmio.c | 571 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 571 insertions(+) create mode 100644 desiredata/src/s_audio_mmio.c (limited to 'desiredata/src/s_audio_mmio.c') diff --git a/desiredata/src/s_audio_mmio.c b/desiredata/src/s_audio_mmio.c new file mode 100644 index 00000000..9165ee93 --- /dev/null +++ b/desiredata/src/s_audio_mmio.c @@ -0,0 +1,571 @@ +/* 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 +#include +#include + +/* ------------------------- audio -------------------------- */ + +static void nt_close_midiin(); +static void nt_noresync(); +static void postflags(); + +#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; +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(const char *s, int err) { + char t[256]; + waveInGetErrorText(err, t, 256); + error(s,t); +} + +static void nt_waveouterror(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; + 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() { + 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", 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", mmresult); + mmresult = waveInAddBuffer( ntsnd_indev[nad], ntsnd_invec[nad][i].lpWaveHdr, sizeof(WAVEHDR)); + if (mmresult != MMSYSERR_NOERROR) nt_waveinerror("waveInAddBuffer: %s", 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) post("opened dac device %d, with return %d", nt_whichdac +nda, mmresult); + if (mmresult != MMSYSERR_NOERROR) { + post("Wave out open device %d + %d",nt_whichdac,nda); + nt_waveouterror("waveOutOpen device: %s", mmresult); + nt_nwaveout = nda; + } + } + return 0; +} + +void mmio_close_audio() { + 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", nda, errcode); + errcode = waveOutClose(ntsnd_outdev[nda]); + if (errcode != MMSYSERR_NOERROR) printf("error closing output %d: %d",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", errcode); + errcode = waveInClose(ntsnd_indev[nad]); + if (errcode != MMSYSERR_NOERROR) printf("error closing input: %d", 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() { + 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() { + 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() { + 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() { + nt_resync_cancelled = 1; +} + +static void nt_resyncaudio() { + 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", 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", 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() { + HMMIO hmmio; + UINT mmresult; + HANDLE hFormat; + int i, j; + short *sp1, *sp2; + float *fp1, *fp2; + int nextfill, doxfer = 0; + if (!nt_nwavein && !nt_nwaveout) return 0; + if (nt_meters) { + int i, n; + float maxsamp; + for (i = 0, n = 2 * nt_nwavein * sys_dacblocksize, 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 * sys_dacblocksize, 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 (int nad=0; naddwFlags & WHDR_DONE)) goto idle; + } + for (int nda=0; ndadwFlags & WHDR_DONE)) goto idle; + } + for (int nad=0; naddwFlags & WHDR_PREPARED) waveInUnprepareHeader(ntsnd_indev[nad], inwavehdr, sizeof(WAVEHDR)); + } + for (int nda=0; ndadwFlags & WHDR_PREPARED) waveOutUnprepareHeader(ntsnd_outdev[nda], outwavehdr, sizeof(WAVEHDR)); + } + } + /* Convert audio output to fixed-point and put it in the output buffer. */ + fp1 = sys_soundout; + for (int nda=0; nda 32767) x1 = 32767; + else if (x1 < -32767) x1 = -32767; + *sp2 = x1; + } + } + } + memset(sys_soundout, 0, (sys_dacblocksize *sizeof(t_sample)*CHANNELS_PER_DEVICE)*nt_nwaveout); + /* vice versa for the input buffer */ + fp1 = sys_soundin; + for (int nad=0; naddwFlags & WHDR_DONE) goto late; + } + for (int nda=0; ndadwFlags & 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 nad=0; naddwFlags & WHDR_DONE) {nt_resyncaudio(); return 0;} + } + /* test dac sync the same way */ + for (int nda=0; ndadwFlags & 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) { + 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; + 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(); +} + +#if 0 +/* list the audio and MIDI device names */ +void mmio_listdevs() { + 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", wRtn); + else post("audio input device #%d: %s", 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", wRtn); + else post("audio output device #%d: %s", 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)); + } +} + +struct t_audioapi api_mmio = { + mmio_open_audio, + mmio_close_audio, + mmio_send_dacs, + mmio_getdevs, +}; -- cgit v1.2.1