|  | /* public domain */ | 
|  |  | 
|  | #include "qemu-common.h" | 
|  | #include "sysemu/sysemu.h" | 
|  | #include "audio.h" | 
|  |  | 
|  | #define AUDIO_CAP "winwave" | 
|  | #include "audio_int.h" | 
|  |  | 
|  | #include <windows.h> | 
|  | #include <mmsystem.h> | 
|  |  | 
|  | #include "audio_win_int.h" | 
|  |  | 
|  | static struct { | 
|  | int dac_headers; | 
|  | int dac_samples; | 
|  | int adc_headers; | 
|  | int adc_samples; | 
|  | } conf = { | 
|  | .dac_headers = 4, | 
|  | .dac_samples = 1024, | 
|  | .adc_headers = 4, | 
|  | .adc_samples = 1024 | 
|  | }; | 
|  |  | 
|  | typedef struct { | 
|  | HWVoiceOut hw; | 
|  | HWAVEOUT hwo; | 
|  | WAVEHDR *hdrs; | 
|  | HANDLE event; | 
|  | void *pcm_buf; | 
|  | int avail; | 
|  | int pending; | 
|  | int curhdr; | 
|  | int paused; | 
|  | CRITICAL_SECTION crit_sect; | 
|  | } WaveVoiceOut; | 
|  |  | 
|  | typedef struct { | 
|  | HWVoiceIn hw; | 
|  | HWAVEIN hwi; | 
|  | WAVEHDR *hdrs; | 
|  | HANDLE event; | 
|  | void *pcm_buf; | 
|  | int curhdr; | 
|  | int paused; | 
|  | int rpos; | 
|  | int avail; | 
|  | CRITICAL_SECTION crit_sect; | 
|  | } WaveVoiceIn; | 
|  |  | 
|  | static void winwave_log_mmresult (MMRESULT mr) | 
|  | { | 
|  | const char *str = "BUG"; | 
|  |  | 
|  | switch (mr) { | 
|  | case MMSYSERR_NOERROR: | 
|  | str = "Success"; | 
|  | break; | 
|  |  | 
|  | case MMSYSERR_INVALHANDLE: | 
|  | str = "Specified device handle is invalid"; | 
|  | break; | 
|  |  | 
|  | case MMSYSERR_BADDEVICEID: | 
|  | str = "Specified device id is out of range"; | 
|  | break; | 
|  |  | 
|  | case MMSYSERR_NODRIVER: | 
|  | str = "No device driver is present"; | 
|  | break; | 
|  |  | 
|  | case MMSYSERR_NOMEM: | 
|  | str = "Unable to allocate or lock memory"; | 
|  | break; | 
|  |  | 
|  | case WAVERR_SYNC: | 
|  | str = "Device is synchronous but waveOutOpen was called " | 
|  | "without using the WINWAVE_ALLOWSYNC flag"; | 
|  | break; | 
|  |  | 
|  | case WAVERR_UNPREPARED: | 
|  | str = "The data block pointed to by the pwh parameter " | 
|  | "hasn't been prepared"; | 
|  | break; | 
|  |  | 
|  | case WAVERR_STILLPLAYING: | 
|  | str = "There are still buffers in the queue"; | 
|  | break; | 
|  |  | 
|  | default: | 
|  | dolog ("Reason: Unknown (MMRESULT %#x)\n", mr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | dolog ("Reason: %s\n", str); | 
|  | } | 
|  |  | 
|  | static void GCC_FMT_ATTR (2, 3) winwave_logerr ( | 
|  | MMRESULT mr, | 
|  | const char *fmt, | 
|  | ... | 
|  | ) | 
|  | { | 
|  | va_list ap; | 
|  |  | 
|  | va_start (ap, fmt); | 
|  | AUD_vlog (AUDIO_CAP, fmt, ap); | 
|  | va_end (ap); | 
|  |  | 
|  | AUD_log (NULL, " failed\n"); | 
|  | winwave_log_mmresult (mr); | 
|  | } | 
|  |  | 
|  | static void winwave_anal_close_out (WaveVoiceOut *wave) | 
|  | { | 
|  | MMRESULT mr; | 
|  |  | 
|  | mr = waveOutClose (wave->hwo); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveOutClose"); | 
|  | } | 
|  | wave->hwo = NULL; | 
|  | } | 
|  |  | 
|  | static void CALLBACK winwave_callback_out ( | 
|  | HWAVEOUT hwo, | 
|  | UINT msg, | 
|  | DWORD_PTR dwInstance, | 
|  | DWORD_PTR dwParam1, | 
|  | DWORD_PTR dwParam2 | 
|  | ) | 
|  | { | 
|  | WaveVoiceOut *wave = (WaveVoiceOut *) dwInstance; | 
|  |  | 
|  | switch (msg) { | 
|  | case WOM_DONE: | 
|  | { | 
|  | WAVEHDR *h = (WAVEHDR *) dwParam1; | 
|  | if (!h->dwUser) { | 
|  | h->dwUser = 1; | 
|  | EnterCriticalSection (&wave->crit_sect); | 
|  | { | 
|  | wave->avail += conf.dac_samples; | 
|  | } | 
|  | LeaveCriticalSection (&wave->crit_sect); | 
|  | if (wave->hw.poll_mode) { | 
|  | if (!SetEvent (wave->event)) { | 
|  | dolog ("DAC SetEvent failed %lx\n", GetLastError ()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case WOM_CLOSE: | 
|  | case WOM_OPEN: | 
|  | break; | 
|  |  | 
|  | default: | 
|  | dolog ("unknown wave out callback msg %x\n", msg); | 
|  | } | 
|  | } | 
|  |  | 
|  | static int winwave_init_out (HWVoiceOut *hw, struct audsettings *as) | 
|  | { | 
|  | int i; | 
|  | int err; | 
|  | MMRESULT mr; | 
|  | WAVEFORMATEX wfx; | 
|  | WaveVoiceOut *wave; | 
|  |  | 
|  | wave = (WaveVoiceOut *) hw; | 
|  |  | 
|  | InitializeCriticalSection (&wave->crit_sect); | 
|  |  | 
|  | err = waveformat_from_audio_settings (&wfx, as); | 
|  | if (err) { | 
|  | goto err0; | 
|  | } | 
|  |  | 
|  | mr = waveOutOpen (&wave->hwo, WAVE_MAPPER, &wfx, | 
|  | (DWORD_PTR) winwave_callback_out, | 
|  | (DWORD_PTR) wave, CALLBACK_FUNCTION); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveOutOpen"); | 
|  | goto err1; | 
|  | } | 
|  |  | 
|  | wave->hdrs = audio_calloc (AUDIO_FUNC, conf.dac_headers, | 
|  | sizeof (*wave->hdrs)); | 
|  | if (!wave->hdrs) { | 
|  | goto err2; | 
|  | } | 
|  |  | 
|  | audio_pcm_init_info (&hw->info, as); | 
|  | hw->samples = conf.dac_samples * conf.dac_headers; | 
|  | wave->avail = hw->samples; | 
|  |  | 
|  | wave->pcm_buf = audio_calloc (AUDIO_FUNC, conf.dac_samples, | 
|  | conf.dac_headers << hw->info.shift); | 
|  | if (!wave->pcm_buf) { | 
|  | goto err3; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < conf.dac_headers; ++i) { | 
|  | WAVEHDR *h = &wave->hdrs[i]; | 
|  |  | 
|  | h->dwUser = 0; | 
|  | h->dwBufferLength = conf.dac_samples << hw->info.shift; | 
|  | h->lpData = advance (wave->pcm_buf, i * h->dwBufferLength); | 
|  | h->dwFlags = 0; | 
|  |  | 
|  | mr = waveOutPrepareHeader (wave->hwo, h, sizeof (*h)); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveOutPrepareHeader(%d)", i); | 
|  | goto err4; | 
|  | } | 
|  | } | 
|  |  | 
|  | return 0; | 
|  |  | 
|  | err4: | 
|  | g_free (wave->pcm_buf); | 
|  | err3: | 
|  | g_free (wave->hdrs); | 
|  | err2: | 
|  | winwave_anal_close_out (wave); | 
|  | err1: | 
|  | err0: | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static int winwave_write (SWVoiceOut *sw, void *buf, int len) | 
|  | { | 
|  | return audio_pcm_sw_write (sw, buf, len); | 
|  | } | 
|  |  | 
|  | static int winwave_run_out (HWVoiceOut *hw, int live) | 
|  | { | 
|  | WaveVoiceOut *wave = (WaveVoiceOut *) hw; | 
|  | int decr; | 
|  | int doreset; | 
|  |  | 
|  | EnterCriticalSection (&wave->crit_sect); | 
|  | { | 
|  | decr = audio_MIN (live, wave->avail); | 
|  | decr = audio_pcm_hw_clip_out (hw, wave->pcm_buf, decr, wave->pending); | 
|  | wave->pending += decr; | 
|  | wave->avail -= decr; | 
|  | } | 
|  | LeaveCriticalSection (&wave->crit_sect); | 
|  |  | 
|  | doreset = hw->poll_mode && (wave->pending >= conf.dac_samples); | 
|  | if (doreset && !ResetEvent (wave->event)) { | 
|  | dolog ("DAC ResetEvent failed %lx\n", GetLastError ()); | 
|  | } | 
|  |  | 
|  | while (wave->pending >= conf.dac_samples) { | 
|  | MMRESULT mr; | 
|  | WAVEHDR *h = &wave->hdrs[wave->curhdr]; | 
|  |  | 
|  | h->dwUser = 0; | 
|  | mr = waveOutWrite (wave->hwo, h, sizeof (*h)); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveOutWrite(%d)", wave->curhdr); | 
|  | break; | 
|  | } | 
|  |  | 
|  | wave->pending -= conf.dac_samples; | 
|  | wave->curhdr = (wave->curhdr + 1) % conf.dac_headers; | 
|  | } | 
|  |  | 
|  | return decr; | 
|  | } | 
|  |  | 
|  | static void winwave_poll (void *opaque) | 
|  | { | 
|  | (void) opaque; | 
|  | audio_run ("winwave_poll"); | 
|  | } | 
|  |  | 
|  | static void winwave_fini_out (HWVoiceOut *hw) | 
|  | { | 
|  | int i; | 
|  | MMRESULT mr; | 
|  | WaveVoiceOut *wave = (WaveVoiceOut *) hw; | 
|  |  | 
|  | mr = waveOutReset (wave->hwo); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveOutReset"); | 
|  | } | 
|  |  | 
|  | for (i = 0; i < conf.dac_headers; ++i) { | 
|  | mr = waveOutUnprepareHeader (wave->hwo, &wave->hdrs[i], | 
|  | sizeof (wave->hdrs[i])); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveOutUnprepareHeader(%d)", i); | 
|  | } | 
|  | } | 
|  |  | 
|  | winwave_anal_close_out (wave); | 
|  |  | 
|  | if (wave->event) { | 
|  | qemu_del_wait_object (wave->event, winwave_poll, wave); | 
|  | if (!CloseHandle (wave->event)) { | 
|  | dolog ("DAC CloseHandle failed %lx\n", GetLastError ()); | 
|  | } | 
|  | wave->event = NULL; | 
|  | } | 
|  |  | 
|  | g_free (wave->pcm_buf); | 
|  | wave->pcm_buf = NULL; | 
|  |  | 
|  | g_free (wave->hdrs); | 
|  | wave->hdrs = NULL; | 
|  | } | 
|  |  | 
|  | static int winwave_ctl_out (HWVoiceOut *hw, int cmd, ...) | 
|  | { | 
|  | MMRESULT mr; | 
|  | WaveVoiceOut *wave = (WaveVoiceOut *) hw; | 
|  |  | 
|  | switch (cmd) { | 
|  | case VOICE_ENABLE: | 
|  | { | 
|  | va_list ap; | 
|  | int poll_mode; | 
|  |  | 
|  | va_start (ap, cmd); | 
|  | poll_mode = va_arg (ap, int); | 
|  | va_end (ap); | 
|  |  | 
|  | if (poll_mode && !wave->event) { | 
|  | wave->event = CreateEvent (NULL, TRUE, TRUE, NULL); | 
|  | if (!wave->event) { | 
|  | dolog ("DAC CreateEvent: %lx, poll mode will be disabled\n", | 
|  | GetLastError ()); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (wave->event) { | 
|  | int ret; | 
|  |  | 
|  | ret = qemu_add_wait_object (wave->event, winwave_poll, wave); | 
|  | hw->poll_mode = (ret == 0); | 
|  | } | 
|  | else { | 
|  | hw->poll_mode = 0; | 
|  | } | 
|  | wave->paused = 0; | 
|  | } | 
|  | return 0; | 
|  |  | 
|  | case VOICE_DISABLE: | 
|  | if (!wave->paused) { | 
|  | mr = waveOutReset (wave->hwo); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveOutReset"); | 
|  | } | 
|  | else { | 
|  | wave->paused = 1; | 
|  | } | 
|  | } | 
|  | if (wave->event) { | 
|  | qemu_del_wait_object (wave->event, winwave_poll, wave); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static void winwave_anal_close_in (WaveVoiceIn *wave) | 
|  | { | 
|  | MMRESULT mr; | 
|  |  | 
|  | mr = waveInClose (wave->hwi); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveInClose"); | 
|  | } | 
|  | wave->hwi = NULL; | 
|  | } | 
|  |  | 
|  | static void CALLBACK winwave_callback_in ( | 
|  | HWAVEIN *hwi, | 
|  | UINT msg, | 
|  | DWORD_PTR dwInstance, | 
|  | DWORD_PTR dwParam1, | 
|  | DWORD_PTR dwParam2 | 
|  | ) | 
|  | { | 
|  | WaveVoiceIn *wave = (WaveVoiceIn *) dwInstance; | 
|  |  | 
|  | switch (msg) { | 
|  | case WIM_DATA: | 
|  | { | 
|  | WAVEHDR *h = (WAVEHDR *) dwParam1; | 
|  | if (!h->dwUser) { | 
|  | h->dwUser = 1; | 
|  | EnterCriticalSection (&wave->crit_sect); | 
|  | { | 
|  | wave->avail += conf.adc_samples; | 
|  | } | 
|  | LeaveCriticalSection (&wave->crit_sect); | 
|  | if (wave->hw.poll_mode) { | 
|  | if (!SetEvent (wave->event)) { | 
|  | dolog ("ADC SetEvent failed %lx\n", GetLastError ()); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | break; | 
|  |  | 
|  | case WIM_CLOSE: | 
|  | case WIM_OPEN: | 
|  | break; | 
|  |  | 
|  | default: | 
|  | dolog ("unknown wave in callback msg %x\n", msg); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void winwave_add_buffers (WaveVoiceIn *wave, int samples) | 
|  | { | 
|  | int doreset; | 
|  |  | 
|  | doreset = wave->hw.poll_mode && (samples >= conf.adc_samples); | 
|  | if (doreset && !ResetEvent (wave->event)) { | 
|  | dolog ("ADC ResetEvent failed %lx\n", GetLastError ()); | 
|  | } | 
|  |  | 
|  | while (samples >= conf.adc_samples) { | 
|  | MMRESULT mr; | 
|  | WAVEHDR *h = &wave->hdrs[wave->curhdr]; | 
|  |  | 
|  | h->dwUser = 0; | 
|  | mr = waveInAddBuffer (wave->hwi, h, sizeof (*h)); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveInAddBuffer(%d)", wave->curhdr); | 
|  | } | 
|  | wave->curhdr = (wave->curhdr + 1) % conf.adc_headers; | 
|  | samples -= conf.adc_samples; | 
|  | } | 
|  | } | 
|  |  | 
|  | static int winwave_init_in (HWVoiceIn *hw, struct audsettings *as) | 
|  | { | 
|  | int i; | 
|  | int err; | 
|  | MMRESULT mr; | 
|  | WAVEFORMATEX wfx; | 
|  | WaveVoiceIn *wave; | 
|  |  | 
|  | wave = (WaveVoiceIn *) hw; | 
|  |  | 
|  | InitializeCriticalSection (&wave->crit_sect); | 
|  |  | 
|  | err = waveformat_from_audio_settings (&wfx, as); | 
|  | if (err) { | 
|  | goto err0; | 
|  | } | 
|  |  | 
|  | mr = waveInOpen (&wave->hwi, WAVE_MAPPER, &wfx, | 
|  | (DWORD_PTR) winwave_callback_in, | 
|  | (DWORD_PTR) wave, CALLBACK_FUNCTION); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveInOpen"); | 
|  | goto err1; | 
|  | } | 
|  |  | 
|  | wave->hdrs = audio_calloc (AUDIO_FUNC, conf.dac_headers, | 
|  | sizeof (*wave->hdrs)); | 
|  | if (!wave->hdrs) { | 
|  | goto err2; | 
|  | } | 
|  |  | 
|  | audio_pcm_init_info (&hw->info, as); | 
|  | hw->samples = conf.adc_samples * conf.adc_headers; | 
|  | wave->avail = 0; | 
|  |  | 
|  | wave->pcm_buf = audio_calloc (AUDIO_FUNC, conf.adc_samples, | 
|  | conf.adc_headers << hw->info.shift); | 
|  | if (!wave->pcm_buf) { | 
|  | goto err3; | 
|  | } | 
|  |  | 
|  | for (i = 0; i < conf.adc_headers; ++i) { | 
|  | WAVEHDR *h = &wave->hdrs[i]; | 
|  |  | 
|  | h->dwUser = 0; | 
|  | h->dwBufferLength = conf.adc_samples << hw->info.shift; | 
|  | h->lpData = advance (wave->pcm_buf, i * h->dwBufferLength); | 
|  | h->dwFlags = 0; | 
|  |  | 
|  | mr = waveInPrepareHeader (wave->hwi, h, sizeof (*h)); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveInPrepareHeader(%d)", i); | 
|  | goto err4; | 
|  | } | 
|  | } | 
|  |  | 
|  | wave->paused = 1; | 
|  | winwave_add_buffers (wave, hw->samples); | 
|  | return 0; | 
|  |  | 
|  | err4: | 
|  | g_free (wave->pcm_buf); | 
|  | err3: | 
|  | g_free (wave->hdrs); | 
|  | err2: | 
|  | winwave_anal_close_in (wave); | 
|  | err1: | 
|  | err0: | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | static void winwave_fini_in (HWVoiceIn *hw) | 
|  | { | 
|  | int i; | 
|  | MMRESULT mr; | 
|  | WaveVoiceIn *wave = (WaveVoiceIn *) hw; | 
|  |  | 
|  | mr = waveInReset (wave->hwi); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveInReset"); | 
|  | } | 
|  |  | 
|  | for (i = 0; i < conf.adc_headers; ++i) { | 
|  | mr = waveInUnprepareHeader (wave->hwi, &wave->hdrs[i], | 
|  | sizeof (wave->hdrs[i])); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveInUnprepareHeader(%d)", i); | 
|  | } | 
|  | } | 
|  |  | 
|  | winwave_anal_close_in (wave); | 
|  |  | 
|  | if (wave->event) { | 
|  | qemu_del_wait_object (wave->event, winwave_poll, wave); | 
|  | if (!CloseHandle (wave->event)) { | 
|  | dolog ("ADC CloseHandle failed %lx\n", GetLastError ()); | 
|  | } | 
|  | wave->event = NULL; | 
|  | } | 
|  |  | 
|  | g_free (wave->pcm_buf); | 
|  | wave->pcm_buf = NULL; | 
|  |  | 
|  | g_free (wave->hdrs); | 
|  | wave->hdrs = NULL; | 
|  | } | 
|  |  | 
|  | static int winwave_run_in (HWVoiceIn *hw) | 
|  | { | 
|  | WaveVoiceIn *wave = (WaveVoiceIn *) hw; | 
|  | int live = audio_pcm_hw_get_live_in (hw); | 
|  | int dead = hw->samples - live; | 
|  | int decr, ret; | 
|  |  | 
|  | if (!dead) { | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | EnterCriticalSection (&wave->crit_sect); | 
|  | { | 
|  | decr = audio_MIN (dead, wave->avail); | 
|  | wave->avail -= decr; | 
|  | } | 
|  | LeaveCriticalSection (&wave->crit_sect); | 
|  |  | 
|  | ret = decr; | 
|  | while (decr) { | 
|  | int left = hw->samples - hw->wpos; | 
|  | int conv = audio_MIN (left, decr); | 
|  | hw->conv (hw->conv_buf + hw->wpos, | 
|  | advance (wave->pcm_buf, wave->rpos << hw->info.shift), | 
|  | conv); | 
|  |  | 
|  | wave->rpos = (wave->rpos + conv) % hw->samples; | 
|  | hw->wpos = (hw->wpos + conv) % hw->samples; | 
|  | decr -= conv; | 
|  | } | 
|  |  | 
|  | winwave_add_buffers (wave, ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static int winwave_read (SWVoiceIn *sw, void *buf, int size) | 
|  | { | 
|  | return audio_pcm_sw_read (sw, buf, size); | 
|  | } | 
|  |  | 
|  | static int winwave_ctl_in (HWVoiceIn *hw, int cmd, ...) | 
|  | { | 
|  | MMRESULT mr; | 
|  | WaveVoiceIn *wave = (WaveVoiceIn *) hw; | 
|  |  | 
|  | switch (cmd) { | 
|  | case VOICE_ENABLE: | 
|  | { | 
|  | va_list ap; | 
|  | int poll_mode; | 
|  |  | 
|  | va_start (ap, cmd); | 
|  | poll_mode = va_arg (ap, int); | 
|  | va_end (ap); | 
|  |  | 
|  | if (poll_mode && !wave->event) { | 
|  | wave->event = CreateEvent (NULL, TRUE, TRUE, NULL); | 
|  | if (!wave->event) { | 
|  | dolog ("ADC CreateEvent: %lx, poll mode will be disabled\n", | 
|  | GetLastError ()); | 
|  | } | 
|  | } | 
|  |  | 
|  | if (wave->event) { | 
|  | int ret; | 
|  |  | 
|  | ret = qemu_add_wait_object (wave->event, winwave_poll, wave); | 
|  | hw->poll_mode = (ret == 0); | 
|  | } | 
|  | else { | 
|  | hw->poll_mode = 0; | 
|  | } | 
|  | if (wave->paused) { | 
|  | mr = waveInStart (wave->hwi); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveInStart"); | 
|  | } | 
|  | wave->paused = 0; | 
|  | } | 
|  | } | 
|  | return 0; | 
|  |  | 
|  | case VOICE_DISABLE: | 
|  | if (!wave->paused) { | 
|  | mr = waveInStop (wave->hwi); | 
|  | if (mr != MMSYSERR_NOERROR) { | 
|  | winwave_logerr (mr, "waveInStop"); | 
|  | } | 
|  | else { | 
|  | wave->paused = 1; | 
|  | } | 
|  | } | 
|  | if (wave->event) { | 
|  | qemu_del_wait_object (wave->event, winwave_poll, wave); | 
|  | } | 
|  | return 0; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static void *winwave_audio_init (void) | 
|  | { | 
|  | return &conf; | 
|  | } | 
|  |  | 
|  | static void winwave_audio_fini (void *opaque) | 
|  | { | 
|  | (void) opaque; | 
|  | } | 
|  |  | 
|  | static struct audio_option winwave_options[] = { | 
|  | { | 
|  | .name        = "DAC_HEADERS", | 
|  | .tag         = AUD_OPT_INT, | 
|  | .valp        = &conf.dac_headers, | 
|  | .descr       = "DAC number of headers", | 
|  | }, | 
|  | { | 
|  | .name        = "DAC_SAMPLES", | 
|  | .tag         = AUD_OPT_INT, | 
|  | .valp        = &conf.dac_samples, | 
|  | .descr       = "DAC number of samples per header", | 
|  | }, | 
|  | { | 
|  | .name        = "ADC_HEADERS", | 
|  | .tag         = AUD_OPT_INT, | 
|  | .valp        = &conf.adc_headers, | 
|  | .descr       = "ADC number of headers", | 
|  | }, | 
|  | { | 
|  | .name        = "ADC_SAMPLES", | 
|  | .tag         = AUD_OPT_INT, | 
|  | .valp        = &conf.adc_samples, | 
|  | .descr       = "ADC number of samples per header", | 
|  | }, | 
|  | { /* End of list */ } | 
|  | }; | 
|  |  | 
|  | static struct audio_pcm_ops winwave_pcm_ops = { | 
|  | .init_out = winwave_init_out, | 
|  | .fini_out = winwave_fini_out, | 
|  | .run_out  = winwave_run_out, | 
|  | .write    = winwave_write, | 
|  | .ctl_out  = winwave_ctl_out, | 
|  | .init_in  = winwave_init_in, | 
|  | .fini_in  = winwave_fini_in, | 
|  | .run_in   = winwave_run_in, | 
|  | .read     = winwave_read, | 
|  | .ctl_in   = winwave_ctl_in | 
|  | }; | 
|  |  | 
|  | struct audio_driver winwave_audio_driver = { | 
|  | .name           = "winwave", | 
|  | .descr          = "Windows Waveform Audio http://msdn.microsoft.com", | 
|  | .options        = winwave_options, | 
|  | .init           = winwave_audio_init, | 
|  | .fini           = winwave_audio_fini, | 
|  | .pcm_ops        = &winwave_pcm_ops, | 
|  | .can_be_default = 1, | 
|  | .max_voices_out = INT_MAX, | 
|  | .max_voices_in  = INT_MAX, | 
|  | .voice_size_out = sizeof (WaveVoiceOut), | 
|  | .voice_size_in  = sizeof (WaveVoiceIn) | 
|  | }; |