text: /* public domain */
  
  #include "qemu-common.h"
  #include "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 locl 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:
      qemu_free (wave->pcm_buf);
   err3:
      qemu_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;
      }
  
      qemu_free (wave->pcm_buf);
      wave->pcm_buf = NULL;
  
      qemu_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;
              }
              if (wave->paused) {
                  mr = waveOutRestart (wave->hwo);
                  if (mr != MMSYSERR_NOERROR) {
                      winwave_logerr (mr, "waveOutRestart");
                  }
                  wave->paused = 0;
              }
          }
          return 0;
  
      case VOICE_DISABLE:
          if (!wave->paused) {
              mr = waveOutPause (wave->hwo);
              if (mr != MMSYSERR_NOERROR) {
                  winwave_logerr (mr, "waveOutPause");
              }
              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:
      qemu_free (wave->pcm_buf);
   err3:
      qemu_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;
      }
  
      qemu_free (wave->pcm_buf);
      wave->pcm_buf = NULL;
  
      qemu_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,
                    &nominal_volume);
  
          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)
  };
  