

#include "tsound.h"

#ifndef TNZCORE_LIGHT
#include "tthread.h"
#endif
#include "tsop.h"
#include <set>
#include "tsystem.h"

#include <mmsystem.h>

//=========================================================

// forward declarations
class TSoundOutputDeviceImp;
class TSoundInputDeviceImp;

//=========================================================

namespace {
void CALLBACK recordCB(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance,
                       DWORD_PTR dwParam1, DWORD dwParam2);

bool setRecordLine(TSoundInputDevice::Source typeInput);

MMRESULT getLineInfo(HMIXEROBJ hMixer, DWORD dwComponentType, MIXERLINE &mxl);

MMRESULT getLineControl(MIXERCONTROL &mxc, HMIXEROBJ hMixer, DWORD dwLineID,
                        DWORD dwControlType);

MMRESULT setControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue);

MMRESULT getControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue);

MMRESULT isaFormatSupported(int sampleRate, int channelCount, int bitPerSample,
                            int sampleType, bool input);

DWORD WINAPI MyWaveOutCallbackThread(LPVOID lpParameter);
void getAmplitude(int &amplitude, const TSoundTrackP st, TINT32 sample);
}  // namespace

//==============================================================================
//     Class to send the message that a playback is completed
//==============================================================================
#ifndef TNZCORE_LIGHT
class EndPlayMsg final : public TThread::Message {
public:
  EndPlayMsg(TSoundOutputDeviceListener *notifier) { m_listener = notifier; }

  TThread::Message *clone() const override { return new EndPlayMsg(*this); }

  void onDeliver() override { m_listener->onPlayCompleted(); }

private:
  TSoundOutputDeviceListener *m_listener;
};
#endif

/*
static int makeDWORD(const short lo, const short hi) {
  int dw = hi << 16;
  dw |= lo;
  return dw;
}
*/

//==============================================================================
class WavehdrQueue;

class TSoundOutputDeviceImp {
public:
  HWAVEOUT m_wout;
  WavehdrQueue *m_whdrQueue;
  TSoundTrackFormat m_currentFormat;
  std::set<int> m_supportedRate;

  TThread::Mutex m_mutex;

  bool m_stopped;
  bool m_isPlaying;
  bool m_looped;
  bool m_scrubbing;

  std::set<TSoundOutputDeviceListener *> m_listeners;
  DWORD m_notifyThreadId;

  HANDLE m_closeDevice;

  TSoundOutputDeviceImp();
  ~TSoundOutputDeviceImp();

  bool doOpenDevice(const TSoundTrackFormat &format);
  bool doPlay(WAVEHDR *whdr, const TSoundTrackFormat format);
  bool doCloseDevice();

  bool verifyRate();
  void insertAllRate();
};

//==============================================================================

class WavehdrQueue {
public:
  WavehdrQueue(TSoundOutputDeviceImp *devImp, int slotCount)
      : m_devImp(devImp)
      , m_items()
      , m_queuedItems()
      , m_slotCount(slotCount)
      , m_mutex()
      , m_lastOffset(0) {}

  ~WavehdrQueue() {}

  void put(TSoundTrackP &subTrack);
  WAVEHDR *get();
  bool popFront(int count);
  void pushBack(WAVEHDR *whdr, TSoundTrackP st);
  int size();
  void clear();
  bool isAllQueuedItemsPlayed();

private:
  std::list<std::pair<WAVEHDR *, TSoundTrackP>> m_items;
  std::list<WAVEHDR *> m_queuedItems;

  TThread::Mutex m_mutex;
  int m_slotCount;
  int m_lastOffset;
  TSoundOutputDeviceImp *m_devImp;
  TSoundTrackP m_lastTrack;
};

//==============================================================================

static WAVEHDR *prepareWaveHeader(HWAVEOUT wout, const TSoundTrackP &subTrack,
                                  ULONG &count) {
  WAVEHDR *whdr = new WAVEHDR;
  memset(whdr, 0, sizeof(WAVEHDR));
  whdr->dwBufferLength = subTrack->getSampleSize() * subTrack->getSampleCount();
  whdr->lpData         = new char[whdr->dwBufferLength];
  whdr->dwFlags        = 0;
  whdr->dwUser         = count;
  memcpy(whdr->lpData, subTrack->getRawData(), whdr->dwBufferLength);

  MMRESULT ret = waveOutPrepareHeader(wout, whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) {
    delete[] whdr->lpData;
    delete whdr;
    return 0;
  }
  ++count;
  return whdr;
}

//==============================================================================

void WavehdrQueue::put(TSoundTrackP &subTrack) {
  assert(subTrack->getRawData());
  static ULONG count = 1;

  // codice messo per tab: facendo il play al rilascio del mouse e su piu'
  // colonne in cui le traccie potrebbe avere diversi formati siccome qui in
  // alcune situazioni si fa subito waveOutWrite c'e' bisogno di controllare
  // se il formato con cui e' stato aperto in precedenza il device e' uguale
  // a quello della traccia
  if (m_devImp->m_wout && m_devImp->m_currentFormat != subTrack->getFormat()) {
    m_devImp->doCloseDevice();
    TSystem::sleep(300);
    m_devImp->doOpenDevice(subTrack->getFormat());
  }

  TThread::MutexLocker sl(&m_mutex);
  if (!m_devImp->m_scrubbing) {
    WAVEHDR *whdr2 = 0;

    // traccia
    whdr2 = prepareWaveHeader(m_devImp->m_wout, subTrack, count);
    getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);

    MMRESULT ret = MMSYSERR_NOERROR;

    if (whdr2 && whdr2->dwFlags & WHDR_PREPARED) {
      ret = waveOutWrite(m_devImp->m_wout, whdr2, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr2, subTrack);
        getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);
        TThread::MutexLocker sl(&m_devImp->m_mutex);
        m_devImp->m_isPlaying = true;
        m_devImp->m_stopped   = false;
      }
    }
    return;
  }

  if (m_queuedItems.size() == 0) {
    WAVEHDR *whdr1 = 0;
    WAVEHDR *whdr2 = 0;
    WAVEHDR *whdr3 = 0;

    MMRESULT ret;
    TSoundTrackP riseTrack, decayTrack;
    int sampleSize = subTrack->getSampleSize();

    // cresce
    riseTrack = TSop::fadeIn(subTrack, 0.9);
    whdr1     = prepareWaveHeader(m_devImp->m_wout, riseTrack, count);

    // traccia
    whdr2 = prepareWaveHeader(m_devImp->m_wout, subTrack, count);

    getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);

    // decresce
    decayTrack = 0;
    if (m_lastOffset) {
      decayTrack = TSop::fadeOut(subTrack, 0.9);
      whdr3      = prepareWaveHeader(m_devImp->m_wout, decayTrack, count);
    }

    if (whdr1 && (whdr1->dwFlags & WHDR_PREPARED)) {
      ret = waveOutWrite(m_devImp->m_wout, whdr1, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr1, riseTrack);
        getAmplitude(m_lastOffset, riseTrack, riseTrack->getSampleCount() - 1L);
      }
    }
    if (whdr2 && (whdr2->dwFlags & WHDR_PREPARED)) {
      ret = waveOutWrite(m_devImp->m_wout, whdr2, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr2, subTrack);
        getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);
        TThread::MutexLocker sl(&m_devImp->m_mutex);
        m_devImp->m_isPlaying = true;
        m_devImp->m_stopped   = false;
      }
    }
    if (whdr3 && (whdr3->dwFlags & WHDR_PREPARED)) {
      ret = waveOutWrite(m_devImp->m_wout, whdr3, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr3, decayTrack);
        if (decayTrack->isSampleSigned())
          m_lastOffset = 0;
        else
          m_lastOffset = 127;
      }
    }
    return;
  }

  if (m_queuedItems.size() < 10) {
    WAVEHDR *whdr1 = 0;
    WAVEHDR *whdr2 = 0;
    WAVEHDR *whdr  = new WAVEHDR;
    memset(whdr, 0, sizeof(WAVEHDR));
    whdr->dwBufferLength =
        subTrack->getSampleSize() * subTrack->getSampleCount();
    whdr->lpData  = new char[whdr->dwBufferLength];
    whdr->dwFlags = 0;
    memcpy(whdr->lpData, subTrack->getRawData(), whdr->dwBufferLength);
    int sampleSize         = subTrack->getSampleSize();
    TSoundTrackP riseTrack = 0;

    if (m_lastOffset)  /// devo fare ilcross fade
    {
      int offset;
      getAmplitude(offset, subTrack, 0L);

      offset = m_lastOffset - offset;
      if (offset) {
        TSoundTrackP st = TSop::crossFade(m_lastTrack, subTrack, 0.3);
        memcpy(whdr->lpData, st->getRawData(),
               st->getSampleCount() * sampleSize);
      }
    } else  // e' zero ma ne metto uno che cresce faccio il fadeIn
    {
      riseTrack = TSop::fadeIn(subTrack, 0.3);
      whdr1     = prepareWaveHeader(m_devImp->m_wout, riseTrack, count);
    }

    whdr->dwUser = count;
    ++count;
    MMRESULT ret =
        waveOutPrepareHeader(m_devImp->m_wout, whdr, sizeof(WAVEHDR));
    if (ret != MMSYSERR_NOERROR) {
      delete[] whdr->lpData;
      delete whdr;
      return;
    }

    getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);

    TSoundTrackP decayTrack = 0;
    if (m_queuedItems.size() <= 7) {
      if (m_lastOffset)  // devo fare il fadeOut
      {
        decayTrack = TSop::fadeOut(subTrack, 0.3);
        whdr2      = prepareWaveHeader(m_devImp->m_wout, decayTrack, count);
      }
    }

    if (whdr1 && whdr1->dwFlags & WHDR_PREPARED) {
      ret = waveOutWrite(m_devImp->m_wout, whdr1, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr1, riseTrack);
        getAmplitude(m_lastOffset, riseTrack, riseTrack->getSampleCount() - 1L);
      }
    }

    if (whdr && whdr->dwFlags & WHDR_PREPARED) {
      ret = waveOutWrite(m_devImp->m_wout, whdr, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr, subTrack);
        getAmplitude(m_lastOffset, subTrack, subTrack->getSampleCount() - 1L);
        {
          TThread::MutexLocker sl(&m_devImp->m_mutex);
          m_devImp->m_isPlaying = true;
          m_devImp->m_stopped   = false;
        }
      }
    }

    if (whdr2 && whdr2->dwFlags & WHDR_PREPARED) {
      ret = waveOutWrite(m_devImp->m_wout, whdr2, sizeof(WAVEHDR));
      if (ret == MMSYSERR_NOERROR) {
        pushBack(whdr2, decayTrack);
        if (decayTrack->isSampleSigned())
          m_lastOffset = 0;
        else
          m_lastOffset = 127;
      }
    }
    return;
  }

  if ((int)m_items.size() == m_slotCount) {
    WAVEHDR *item = m_items.front().first;
    MMRESULT ret =
        waveOutUnprepareHeader(m_devImp->m_wout, item, sizeof(WAVEHDR));

    if (ret == MMSYSERR_NOERROR) {
      delete[] item->lpData;
      delete item;
    }

    m_items.pop_front();

    if (m_queuedItems.size() != 0) {
      WAVEHDR *item  = m_items.front().first;
      int sampleSize = m_items.front().second->getSampleSize();
      int offset;
      getAmplitude(offset, m_items.front().second, 0L);

      offset = m_lastOffset - offset;
      if (offset) {
        TSoundTrackP st =
            TSop::crossFade(m_lastTrack, m_items.front().second, 0.3);
        memcpy(item->lpData, st->getRawData(),
               st->getSampleCount() * sampleSize);
      }
    }
  }

  WAVEHDR *whdr = prepareWaveHeader(m_devImp->m_wout, subTrack, count);

  assert(whdr && whdr->dwFlags & WHDR_PREPARED);
  m_items.push_back(std::make_pair(whdr, subTrack));
  assert((int)m_items.size() <= m_slotCount);
}

//----------------------------------------------------------------------------

// restituisce il piu' vecchio WAVEHDR il cui stato e' prepared && !done
WAVEHDR *WavehdrQueue::get() {
  TThread::MutexLocker sl(&m_mutex);
  if (m_items.size() == 0) return 0;

  WAVEHDR *whdr = m_items.front().first;
  assert(whdr->dwFlags & WHDR_PREPARED);

  pushBack(whdr, m_items.front().second);
  getAmplitude(m_lastOffset, m_items.front().second,
               m_items.front().second->getSampleCount() - 1L);
  m_items.pop_front();

  return whdr;
}

//-----------------------------------------------------------------------------

// rimuove dalla coda il piu' vecchio WAVEHDR il cui stato e' done
bool WavehdrQueue::popFront(int count) {
  TThread::MutexLocker sl(&m_mutex);
  // assert(m_queuedItems.size() > 0);
  if (m_queuedItems.size() <= 0) return false;
  WAVEHDR *whdr = m_queuedItems.front();
  // controllo introdotto pr via che su win2k si perde alcune
  // notifiche di WHDR_DONE
  while ((DWORD)count > whdr->dwUser) {
    MMRESULT ret =
        waveOutUnprepareHeader(m_devImp->m_wout, whdr, sizeof(WAVEHDR));
    if (ret == MMSYSERR_NOERROR) {
      m_queuedItems.pop_front();
      delete[] whdr->lpData;
      delete whdr;
      whdr = m_queuedItems.front();
    }
  }
  assert(whdr->dwFlags & WHDR_DONE);
  m_queuedItems.pop_front();
  delete[] whdr->lpData;
  delete whdr;
  return true;
}

//-----------------------------------------------------------------------------

void WavehdrQueue::pushBack(WAVEHDR *whdr, TSoundTrackP st) {
  TThread::MutexLocker sl(&m_mutex);
  m_queuedItems.push_back(whdr);
  m_lastTrack = st;
}

//-----------------------------------------------------------------------------

int WavehdrQueue::size() {
  TThread::MutexLocker sl(&m_mutex);
  int size = m_queuedItems.size();
  return size;
}

//-----------------------------------------------------------------------------

void WavehdrQueue::clear() {
  TThread::MutexLocker sl(&m_mutex);
  m_items.clear();
  m_lastTrack = TSoundTrackP();
  std::list<WAVEHDR *>::iterator it;
  for (it = m_queuedItems.begin(); it != m_queuedItems.end(); it++) {
    WAVEHDR *wvhdr = *it;
    delete[] wvhdr->lpData;
    delete wvhdr;
  }
  m_queuedItems.clear();
}

//-----------------------------------------------------------------------------

bool WavehdrQueue::isAllQueuedItemsPlayed() {
  std::list<WAVEHDR *>::iterator it;
  bool finished = true;
  for (it = m_queuedItems.begin(); it != m_queuedItems.end(); it++) {
    WAVEHDR *wvhdr = *it;
    finished       = finished && (wvhdr->dwFlags & WHDR_DONE);
  }
  return finished;
}

//==============================================================================

TSoundOutputDeviceImp::TSoundOutputDeviceImp()
    : m_stopped(true)
    , m_isPlaying(false)
    , m_looped(false)
    , m_scrubbing(false)
    , m_wout(0) {
  m_whdrQueue = new WavehdrQueue(this, 4);

  insertAllRate();
  if (!verifyRate())
    throw TSoundDeviceException(TSoundDeviceException::FailedInit,
                                "Unable to verify supported rates");

  m_closeDevice = CreateEvent(NULL,   // no security attributes
                              FALSE,  // auto-reset event
                              FALSE,  // initial state is not signaled
                              NULL);  // object not named
}

//----------------------------------------------------------------------------

TSoundOutputDeviceImp::~TSoundOutputDeviceImp() { delete m_whdrQueue; }

//----------------------------------------------------------------------------

bool TSoundOutputDeviceImp::doOpenDevice(const TSoundTrackFormat &format) {
  WAVEFORMATEX wf;
  wf.wFormatTag      = format.m_sampleType & TSound::WMASK;
  wf.nChannels       = format.m_channelCount;
  wf.nSamplesPerSec  = format.m_sampleRate;
  wf.wBitsPerSample  = format.m_bitPerSample;
  wf.nBlockAlign     = (wf.nChannels * wf.wBitsPerSample) >> 3;
  wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
  wf.cbSize          = 0;

  TThread::MutexLocker sl(&m_mutex);
  CloseHandle(CreateThread(NULL, 0, MyWaveOutCallbackThread, (LPVOID)this, 0,
                           &m_notifyThreadId));

  MMRESULT ret;
  if ((ret = waveOutOpen(&m_wout, WAVE_MAPPER, &wf, (DWORD_PTR)m_notifyThreadId,
                         (DWORD_PTR)this, CALLBACK_THREAD)) !=
      MMSYSERR_NOERROR) {
    while (!PostThreadMessage(m_notifyThreadId, WM_QUIT, 0, 0))
      ;
  }
  if (ret != MMSYSERR_NOERROR) return false;

  if (ret != MMSYSERR_NOERROR) return false;
  m_currentFormat = format;
  return (ret == MMSYSERR_NOERROR);
}

//----------------------------------------------------------------------------

bool TSoundOutputDeviceImp::doPlay(WAVEHDR *whdr,
                                   const TSoundTrackFormat format) {
  TThread::MutexLocker sl(&m_mutex);
  if (!m_wout || (m_wout && m_currentFormat != format)) doOpenDevice(format);

  MMRESULT ret;
  ret = waveOutWrite(m_wout, whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) return false;
  m_stopped   = false;
  m_isPlaying = true;

  return true;
}

//----------------------------------------------------------------------------

bool TSoundOutputDeviceImp::doCloseDevice() {
  TThread::MutexLocker sl(&m_mutex);
  if (m_wout) {
    MMRESULT ret = waveOutReset(m_wout);
    if (ret != MMSYSERR_NOERROR) return false;
    ret = waveOutClose(m_wout);
    if (ret != MMSYSERR_NOERROR) return false;
    m_wout      = 0;
    m_stopped   = true;
    m_isPlaying = false;
    m_looped    = false;
  }

  PostThreadMessage(m_notifyThreadId, WM_QUIT, 0, 0);
  return true;
}

//----------------------------------------------------------------------------

void TSoundOutputDeviceImp::insertAllRate() {
  m_supportedRate.insert(8000);
  m_supportedRate.insert(11025);
  m_supportedRate.insert(16000);
  m_supportedRate.insert(22050);
  m_supportedRate.insert(32000);
  m_supportedRate.insert(44100);
  m_supportedRate.insert(48000);
  m_supportedRate.insert(96000);
  m_supportedRate.insert(192000);
}

//----------------------------------------------------------------------------

bool TSoundOutputDeviceImp::verifyRate() {
  std::set<int>::iterator it;

  for (it = m_supportedRate.begin(); it != m_supportedRate.end();) {
    MMRESULT ret;
    WAVEFORMATEX wf;

    wf.wFormatTag      = WAVE_FORMAT_PCM;
    wf.nChannels       = 1;
    wf.nSamplesPerSec  = *it;
    wf.wBitsPerSample  = 8;
    wf.nBlockAlign     = (wf.nChannels * wf.wBitsPerSample) >> 3;
    wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
    wf.cbSize          = 0;

    ret = waveOutOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY);

    if (ret == MMSYSERR_NOERROR) {
      ++it;
      continue;
    }
    if (ret == WAVERR_BADFORMAT)
      it = m_supportedRate.erase(it);
    else
      return false;
  }
  if (m_supportedRate.end() == m_supportedRate.begin()) return false;

  return true;
}

//==============================================================================

TSoundOutputDevice::TSoundOutputDevice() : m_imp(new TSoundOutputDeviceImp()) {}

//------------------------------------------------------------------------------

TSoundOutputDevice::~TSoundOutputDevice() {
  close();
  WaitForSingleObject(m_imp->m_closeDevice, INFINITE);
  CloseHandle(m_imp->m_closeDevice);
}

//------------------------------------------------------------------------------

namespace {
DWORD WINAPI MyWaveOutCallbackThread(LPVOID lpParameter) {
  TSoundOutputDeviceImp *devImp = (TSoundOutputDeviceImp *)lpParameter;
  if (!devImp) return 0;

  MSG msg;
  BOOL bRet;
  while (bRet = GetMessage(&msg, NULL, 0, 0)) {
    if (bRet == -1) {
      // si e' verificato un errore
      break;
    }

    switch (msg.message) {
    case MM_WOM_DONE: {
      WAVEHDR *pWaveHdr = ((WAVEHDR *)msg.lParam);
      {
        TThread::MutexLocker sl(&devImp->m_mutex);
        if (devImp->m_looped) {
          devImp->doPlay(pWaveHdr, devImp->m_currentFormat);
          continue;
        }
      }

      WAVEHDR *whdr = 0;
      if (devImp->m_whdrQueue->popFront(pWaveHdr->dwUser)) {
        whdr = devImp->m_whdrQueue->get();
        if (whdr) devImp->doPlay(whdr, devImp->m_currentFormat);

        WaitForSingleObject(devImp->m_closeDevice, INFINITE);
        CloseHandle(devImp->m_closeDevice);

        MMRESULT ret =
            waveOutUnprepareHeader(devImp->m_wout, pWaveHdr, sizeof(WAVEHDR));
        if (ret == MMSYSERR_NOERROR) {
          delete pWaveHdr->lpData;
          delete pWaveHdr;
        }
      }

      if (!whdr && devImp->m_whdrQueue->size() == 0) {
        std::set<TSoundOutputDeviceListener *>::iterator it =
            devImp->m_listeners.begin();
        for (; it != devImp->m_listeners.end(); ++it) {
#ifndef TNZCORE_LIGHT
          EndPlayMsg *event = new EndPlayMsg(*it);
          event->send();
#else
          assert(false);
#endif
        }
        devImp->doCloseDevice();
      }

      continue;
    }

    case MM_WOM_CLOSE:
      break;

    default:
      continue;
    }
  }

  SetEvent(devImp->m_closeDevice);
  return 0;
}

void getAmplitude(int &amplitude, const TSoundTrackP st, TINT32 sample) {
  TSoundTrackP snd = st;
  amplitude        = 0;
  int k            = 0;
  for (k = 0; k < snd->getChannelCount(); ++k)
    amplitude += (int)snd->getPressure(sample, k);
  amplitude /= k;
}
}  // namespace

//------------------------------------------------------------------------------

bool TSoundOutputDevice::installed() {
  int ndev = waveOutGetNumDevs();
  if (ndev <= 0) return false;
  return true;
}

//------------------------------------------------------------------------------

bool TSoundOutputDevice::open(const TSoundTrackP &st) {
  return m_imp->doOpenDevice(st->getFormat());
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::attach(TSoundOutputDeviceListener *listener) {
  m_imp->m_listeners.insert(listener);
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::detach(TSoundOutputDeviceListener *listener) {
  m_imp->m_listeners.erase(listener);
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::play(const TSoundTrackP &st, TINT32 s0, TINT32 s1,
                              bool loop, bool scrubbing) {
  assert((scrubbing && !loop) || !scrubbing);
  if (!st->getSampleCount()) return;
  TSoundTrackFormat format;
  TSoundTrackP subTrack;

  {
    TThread::MutexLocker sl(&m_imp->m_mutex);
    if (m_imp->m_looped)
      throw TSoundDeviceException(
          TSoundDeviceException::Busy,
          "Unable to queue another playback when the sound player is looping");

    format = st->getFormat();
    try {
      TSoundTrackFormat fmt = getPreferredFormat(format);
      if (fmt != format) {
        throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                    "Unsupported Format");
      }
    } catch (TSoundDeviceException &e) {
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  e.getMessage());
    }

    assert(s1 >= s0);
    subTrack           = st->extract(s0, s1);
    m_imp->m_looped    = loop;
    m_imp->m_scrubbing = scrubbing;
  }

  if (!m_imp->m_wout) m_imp->doOpenDevice(format);

  m_imp->m_whdrQueue->put(subTrack);
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::stop() {
  if ((m_imp->m_wout) && m_imp->m_isPlaying) {
    MMRESULT ret = waveOutReset(m_imp->m_wout);
    if (ret != MMSYSERR_NOERROR) return;

    TThread::MutexLocker sl(&m_imp->m_mutex);
    m_imp->m_looped    = false;
    m_imp->m_stopped   = true;
    m_imp->m_isPlaying = false;
    m_imp->m_whdrQueue->clear();
  }
}

//------------------------------------------------------------------------------

bool TSoundOutputDevice::close() { return m_imp->doCloseDevice(); }

//------------------------------------------------------------------------------

bool TSoundOutputDevice::isPlaying() const { return m_imp->m_isPlaying; }

//------------------------------------------------------------------------------

bool TSoundOutputDevice::isAllQueuedItemsPlayed() {
  return m_imp->m_whdrQueue->isAllQueuedItemsPlayed();
}

//------------------------------------------------------------------------------

bool TSoundOutputDevice::isLooping() {
  TThread::MutexLocker sl(&m_imp->m_mutex);
  return m_imp->m_looped;
}

//------------------------------------------------------------------------------

void TSoundOutputDevice::setLooping(bool loop) {
  TThread::MutexLocker sl(&m_imp->m_mutex);
  m_imp->m_looped = loop;
}

//------------------------------------------------------------------------------

TSoundTrackFormat TSoundOutputDevice::getPreferredFormat(TUINT32 sampleRate,
                                                         int channelCount,
                                                         int bitPerSample) {
  TSoundTrackFormat fmt;

  // avvvicinarsi al sample rate => dovrebbe esser OK avendo selezionato i piu'
  // vicini
  std::set<int>::iterator it = m_imp->m_supportedRate.lower_bound(sampleRate);
  if (it == m_imp->m_supportedRate.end()) {
    it = std::max_element(m_imp->m_supportedRate.begin(),
                          m_imp->m_supportedRate.end());
    if (it != m_imp->m_supportedRate.end())
      sampleRate = *(m_imp->m_supportedRate.rbegin());
    else
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  "There isn't a supported format");
  } else
    sampleRate = *it;

  // Normalize bit sample and set preferred sample type
  if (bitPerSample <= 8) {
    bitPerSample     = 8;
    fmt.m_sampleType = TSound::UINT;
  } else if (bitPerSample <= 16) {
    bitPerSample     = 16;
    fmt.m_sampleType = TSound::INT;
  } else if (bitPerSample <= 24) {
    bitPerSample     = 24;
    fmt.m_sampleType = TSound::INT;
  } else if (bitPerSample <= 32) {
    bitPerSample     = 32;
    fmt.m_sampleType = TSound::FLOAT;
  }

  // switch mono/stereo
  if (channelCount <= 1)
    channelCount = 1;
  else
    channelCount = 2;

  fmt.m_bitPerSample = bitPerSample;
  fmt.m_channelCount = channelCount;
  fmt.m_sampleRate   = sampleRate;

  return fmt;
}

//------------------------------------------------------------------------------

TSoundTrackFormat TSoundOutputDevice::getPreferredFormat(
    const TSoundTrackFormat &format) {
  try {
    return getPreferredFormat(format.m_sampleRate, format.m_channelCount,
                              format.m_bitPerSample);
  } catch (TSoundDeviceException &e) {
    throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                e.getMessage());
  }
}

//==============================================================================
//==============================================================================
// Classi per la gestione della registrazione
//==============================================================================
//==============================================================================

//==============================================================================

class WaveFormat final : public WAVEFORMATEX {
public:
  WaveFormat(){};
  WaveFormat(unsigned char channelCount, TUINT32 sampleRate,
             unsigned char bitPerSample, int sampleFormat);

  ~WaveFormat(){};
};

WaveFormat::WaveFormat(unsigned char channelCount, TUINT32 sampleRate,
                       unsigned char bitPerSample, int sampleFormat) {
  wFormatTag      = sampleFormat & TSound::WMASK;
  nChannels       = channelCount;
  nSamplesPerSec  = sampleRate;
  wBitsPerSample  = bitPerSample;
  nBlockAlign     = (channelCount * bitPerSample) >> 3;
  nAvgBytesPerSec = nBlockAlign * sampleRate;
  cbSize          = 0;
}

//==============================================================================

class WinSoundInputDevice {
public:
  WinSoundInputDevice();
  ~WinSoundInputDevice();

  void open(const WaveFormat &wf);
  void close();

  void prepareHeader(char *sampleBuffer, TUINT32 sampleBufferSize,
                     WAVEHDR &whdr);

  void unprepareHeader(WAVEHDR &whdr);

  void addBlock(WAVEHDR &whdr);

  void start();

  void reset();
  void stop();

  HANDLE m_hBlockDone;

private:
  HWAVEIN m_hWaveIn;
};

//--------------------------------------------------------------------

WinSoundInputDevice::WinSoundInputDevice() : m_hWaveIn(0) {
  m_hBlockDone = CreateEvent(NULL,   // no security attributes
                             FALSE,  // auto-reset event
                             FALSE,  // initial state is not signaled
                             NULL);  // object not named
}

//--------------------------------------------------------------------

WinSoundInputDevice::~WinSoundInputDevice() { CloseHandle(m_hBlockDone); }

//--------------------------------------------------------------------

void WinSoundInputDevice::open(const WaveFormat &wf) {
  if (m_hWaveIn) close();

  MMRESULT ret = waveInOpen(&m_hWaveIn, WAVE_MAPPER, &wf, (DWORD_PTR)recordCB,
                            (DWORD_PTR)m_hBlockDone, CALLBACK_FUNCTION);

  if (ret != MMSYSERR_NOERROR) {
    throw TException("Error to open the input device");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::close() {
  if (!m_hWaveIn) return;

  MMRESULT ret = waveInClose(m_hWaveIn);

  if (ret != MMSYSERR_NOERROR) {
    throw TException("Error to close the input device");
  }
  m_hWaveIn = 0;
}

//--------------------------------------------------------------------

void WinSoundInputDevice::prepareHeader(char *sampleBuffer,
                                        TUINT32 sampleBufferSize,
                                        WAVEHDR &whdr) {
  whdr.lpData         = sampleBuffer;
  whdr.dwBufferLength = sampleBufferSize;  // numero di byte
  whdr.dwFlags        = 0;
  whdr.dwLoops        = 0;

  MMRESULT ret = waveInPrepareHeader(m_hWaveIn, &whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to prepare a wave header");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::unprepareHeader(WAVEHDR &whdr) {
  MMRESULT ret = waveInUnprepareHeader(m_hWaveIn, &whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to unprepare a wave header");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::addBlock(WAVEHDR &whdr) {
  MMRESULT ret = waveInAddBuffer(m_hWaveIn, &whdr, sizeof(WAVEHDR));
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to add a waveheader");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::start() {
  int ret = waveInStart(m_hWaveIn);
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to add a waveheader");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::reset() {
  if (!m_hWaveIn) return;

  MMRESULT ret = waveInReset(m_hWaveIn);
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to add a waveheader");
  }
}

//--------------------------------------------------------------------

void WinSoundInputDevice::stop() {
  MMRESULT ret = waveInStop(m_hWaveIn);
  if (ret != MMSYSERR_NOERROR) {
    throw TException("Unable to add a waveheader");
  }
}

//====================================================================

#ifndef TNZCORE_LIGHT
class RecordTask final : public TThread::Runnable {
public:
  RecordTask(std::shared_ptr<TSoundInputDeviceImp> dev)
      : Runnable(), m_dev(std::move(dev)) {}

  ~RecordTask() {}

  void run() override;

  std::shared_ptr<TSoundInputDeviceImp> m_dev;
};

#endif

//====================================================================

class TSoundInputDeviceImp final : public WinSoundInputDevice {
public:
  bool m_allocateBuff;
  bool m_isRecording;
  bool m_supportVolume;
  int m_index;
  TINT32 m_byteRecorded;

  TSoundTrackP m_st;
  TSoundTrackFormat m_format;

#ifndef TNZCORE_LIGHT
  TThread::Executor m_executor;
#endif
  std::vector<WAVEHDR> m_whdr;
  std::vector<char *> m_recordedBlocks;
  std::set<int> m_supportedRate;

  HANDLE m_hLastBlockDone;

  TSoundInputDeviceImp();
  ~TSoundInputDeviceImp();

  void insertAllRate();
  bool verifyRate();
};

//------------------------------------------------------------------------------

TSoundInputDeviceImp::TSoundInputDeviceImp()
    : m_allocateBuff(false)
    , m_isRecording(false)
    , m_supportVolume(false)
    , m_index(0)
    , m_byteRecorded(0)
    , m_format()
    , m_whdr(3)
    , m_recordedBlocks()
    , m_supportedRate() {
  m_hLastBlockDone = CreateEvent(NULL,   // no security attributes
                                 FALSE,  // is manual-reset event?
                                 FALSE,  // is signaled?
                                 NULL);  // object not named
}

//------------------------------------------------------------------------------

TSoundInputDeviceImp::~TSoundInputDeviceImp() {
  if (m_isRecording) {
    try {
      reset();
      WaitForSingleObject(m_hLastBlockDone, INFINITE);
      int i;
      for (i = 0; i < (int)m_recordedBlocks.size(); ++i)
        delete[] m_recordedBlocks[i];
      close();
    } catch (TException &) {
    }
  }
  CloseHandle(m_hLastBlockDone);
}

//----------------------------------------------------------------------------

void TSoundInputDeviceImp::insertAllRate() {
  m_supportedRate.insert(8000);
  m_supportedRate.insert(11025);
  m_supportedRate.insert(16000);
  m_supportedRate.insert(22050);
  m_supportedRate.insert(32000);
  m_supportedRate.insert(44100);
  m_supportedRate.insert(48000);
  m_supportedRate.insert(96000);
  m_supportedRate.insert(192000);
}

//----------------------------------------------------------------------------

bool TSoundInputDeviceImp::verifyRate() {
  std::set<int>::iterator it;

  for (it = m_supportedRate.begin(); it != m_supportedRate.end();) {
    MMRESULT ret;
    WAVEFORMATEX wf;

    wf.wFormatTag      = WAVE_FORMAT_PCM;
    wf.nChannels       = 1;
    wf.nSamplesPerSec  = *it;
    wf.wBitsPerSample  = 8;
    wf.nBlockAlign     = (wf.nChannels * wf.wBitsPerSample) >> 3;
    wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
    wf.cbSize          = 0;

    ret = waveInOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY);

    if (ret == MMSYSERR_NOERROR) {
      ++it;
      continue;
    }
    if (ret == WAVERR_BADFORMAT)
      it = m_supportedRate.erase(it);
    else
      return false;
  }
  if (m_supportedRate.end() == m_supportedRate.begin()) return false;

  return true;
}

//====================================================================
namespace {
void CALLBACK recordCB(HWAVEIN hwi, UINT uMsg, DWORD_PTR dwInstance,
                       DWORD_PTR dwParam1, DWORD dwParam2) {
  WAVEHDR *whdr     = (WAVEHDR *)dwParam1;
  HANDLE *blockDone = (HANDLE *)dwInstance;

  if (uMsg != MM_WIM_DATA) return;
  SetEvent(blockDone);
}
}  // namespace

//==============================================================================

TSoundInputDevice::TSoundInputDevice() : m_imp(new TSoundInputDeviceImp()) {
  m_imp->insertAllRate();
  if (!m_imp->verifyRate())
    throw TSoundDeviceException(TSoundDeviceException::FailedInit,
                                "Unable to verify supported rates");
  if (supportsVolume()) m_imp->m_supportVolume = true;
}

//------------------------------------------------------------------------------

TSoundInputDevice::~TSoundInputDevice() {}

//------------------------------------------------------------------------------

bool TSoundInputDevice::installed() {
  int ndev = waveInGetNumDevs();
  if (ndev <= 0) return false;
  return true;
}

//------------------------------------------------------------------------------

#ifndef TNZCORE_LIGHT

void TSoundInputDevice::record(const TSoundTrackFormat &format,
                               Source devtype) {
  if (m_imp->m_isRecording)
    throw TSoundDeviceException(TSoundDeviceException::Busy,
                                "Just another recoding is in progress");

  /*if ((format.m_bitPerSample == 8 && format.m_signedSample) ||
(format.m_bitPerSample == 24))
throw TException("This format is not supported for recording");*/
  try {
    TSoundTrackFormat fmt = getPreferredFormat(format);
    if (fmt != format) {
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  "Unsupported Format");
    }
  } catch (TSoundDeviceException &e) {
    throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                e.getMessage());
  }

  if (!setRecordLine(devtype))
    throw TSoundDeviceException(TSoundDeviceException::UnableSetDevice,
                                "Problems to set input source line to record");

  m_imp->m_format = format;
  m_imp->m_st     = 0;
  ResetEvent(m_imp->m_hLastBlockDone);
  ResetEvent(m_imp->m_hBlockDone);
  m_imp->m_allocateBuff = true;
  m_imp->m_isRecording  = true;
  m_imp->m_index        = 0;
  m_imp->m_recordedBlocks.clear();
  m_imp->m_byteRecorded = 0;
  TINT32 bytePerSec     = format.m_sampleRate *
                      ((format.m_bitPerSample * format.m_channelCount) >> 3);

  try {
    WaveFormat wf(m_imp->m_format.m_channelCount, m_imp->m_format.m_sampleRate,
                  m_imp->m_format.m_bitPerSample, m_imp->m_format.m_sampleType);

    m_imp->open(wf);
  } catch (TException &e) {
    m_imp->m_isRecording = false;
    throw TSoundDeviceException(TSoundDeviceException::UnableOpenDevice,
                                e.getMessage());
  }
  for (; m_imp->m_index < (int)(m_imp->m_whdr.size() - 1); ++m_imp->m_index) {
    try {
      m_imp->prepareHeader(new char[bytePerSec], bytePerSec,
                           m_imp->m_whdr[m_imp->m_index]);
      m_imp->addBlock(m_imp->m_whdr[m_imp->m_index]);
    } catch (TException &e) {
      m_imp->m_isRecording = false;
      for (int j = 0; j < (int)(m_imp->m_whdr.size() - 1); ++j) {
        if (m_imp->m_whdr[j].dwFlags & WHDR_PREPARED) {
          try {
            m_imp->unprepareHeader(m_imp->m_whdr[j]);
          } catch (TException &e) {
            throw TSoundDeviceException(
                TSoundDeviceException::UnableCloseDevice, e.getMessage());
          }
          delete[] m_imp->m_whdr[j].lpData;
        } else if (j == m_imp->m_index)
          delete[] m_imp->m_whdr[j].lpData;
      }
      throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
                                  e.getMessage());
    }
  }

  m_imp->m_executor.addTask(new RecordTask(m_imp));
}

//------------------------------------------------------------------------------

void TSoundInputDevice::record(const TSoundTrackP &st, Source devtype) {
  if (m_imp->m_isRecording)
    throw TSoundDeviceException(TSoundDeviceException::Busy,
                                "Just another recoding is in progress");

  m_imp->m_format = st->getFormat();
  /*if ((m_imp->m_format.m_bitPerSample == 8 && m_imp->m_format.m_signedSample)
||
(m_imp->m_format.m_bitPerSample == 24))
throw TException("This format is not supported for recording");*/
  try {
    TSoundTrackFormat fmt = getPreferredFormat(st->getFormat());
    if (fmt != st->getFormat()) {
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  "Unsupported Format");
    }
  } catch (TSoundDeviceException &e) {
    throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                e.getMessage());
  }

  if (!setRecordLine(devtype))
    throw TSoundDeviceException(TSoundDeviceException::UnableSetDevice,
                                "Problems to set input source line to record");

  m_imp->m_st           = st;
  m_imp->m_allocateBuff = false;
  m_imp->m_isRecording  = true;
  ResetEvent(m_imp->m_hLastBlockDone);
  ResetEvent(m_imp->m_hBlockDone);
  m_imp->m_index = 0;
  m_imp->m_recordedBlocks.clear();
  m_imp->m_byteRecorded = 0;
  try {
    WaveFormat wf(m_imp->m_format.m_channelCount, m_imp->m_format.m_sampleRate,
                  m_imp->m_format.m_bitPerSample, m_imp->m_format.m_sampleType);

    m_imp->open(wf);
    m_imp->prepareHeader(
        (char *)st->getRawData(),
        st->getSampleCount() *
            ((st->getBitPerSample() * st->getChannelCount()) >> 3),
        m_imp->m_whdr[m_imp->m_index]);
    m_imp->addBlock(m_imp->m_whdr[m_imp->m_index]);
  } catch (TException &e) {
    m_imp->m_isRecording = false;
    if (m_imp->m_whdr[m_imp->m_index].dwFlags & WHDR_PREPARED)
      m_imp->unprepareHeader(m_imp->m_whdr[m_imp->m_index]);

    throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
                                e.getMessage());
  }

  m_imp->m_executor.addTask(new RecordTask(m_imp));
}
#endif

//------------------------------------------------------------------------------

TSoundTrackP TSoundInputDevice::stop() {
  if (!m_imp->m_isRecording) return 0;

  m_imp->m_isRecording = false;
  try {
    m_imp->reset();
  } catch (TException &e) {
    for (int j = 0; j < (int)m_imp->m_whdr.size(); ++j) {
      if (m_imp->m_whdr[j].dwFlags & WHDR_PREPARED) {
        try {
          m_imp->unprepareHeader(m_imp->m_whdr[j]);
        } catch (TException &e) {
          throw TSoundDeviceException(TSoundDeviceException::UnablePrepare,
                                      e.getMessage());
        }
        delete[] m_imp->m_whdr[j].lpData;
      }
    }
    throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice,
                                e.getMessage());
  }

  if (m_imp->m_allocateBuff) {
    WaitForSingleObject(m_imp->m_hLastBlockDone, INFINITE);

    TSoundTrackP st = TSoundTrack::create(
        m_imp->m_format,
        m_imp->m_byteRecorded / ((m_imp->m_format.m_bitPerSample *
                                  m_imp->m_format.m_channelCount) >>
                                 3));

    TINT32 bytePerSec =
        m_imp->m_format.m_sampleRate *
        ((m_imp->m_format.m_bitPerSample * m_imp->m_format.m_channelCount) >>
         3);

    int i;
    for (i = 0; i < (int)(m_imp->m_recordedBlocks.size() - 1); ++i) {
      memcpy((void *)(st->getRawData() + bytePerSec * i),
             m_imp->m_recordedBlocks[i], bytePerSec);
      delete[] m_imp->m_recordedBlocks[i];
    }

    TINT32 lastBlockSize = m_imp->m_byteRecorded - (bytePerSec * i);

    if (lastBlockSize != 0) {
      memcpy((void *)(st->getRawData() + bytePerSec * i),
             m_imp->m_recordedBlocks[i], lastBlockSize);
      delete[] m_imp->m_recordedBlocks[i];
    }

    try {
      m_imp->close();
    } catch (TException &e) {
      throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice,
                                  e.getMessage());
    }
    return st;
  } else {
    WaitForSingleObject(m_imp->m_hLastBlockDone, INFINITE);
    try {
      m_imp->close();
    } catch (TException &e) {
      throw TSoundDeviceException(TSoundDeviceException::UnableCloseDevice,
                                  e.getMessage());
    }
    return m_imp->m_st;
  }
}

//------------------------------------------------------------------------------
#ifndef TNZCORE_LIGHT

void RecordTask::run() {
  m_dev->start();

  if (m_dev->m_allocateBuff) {
    TINT32 bytePerSec =
        m_dev->m_format.m_sampleRate *
        ((m_dev->m_format.m_bitPerSample * m_dev->m_format.m_channelCount) >>
         3);

    while (m_dev->m_whdr[(m_dev->m_index + 1) % m_dev->m_whdr.size()].dwFlags &
           WHDR_PREPARED) {
      if (m_dev->m_isRecording)
        WaitForSingleObject(m_dev->m_hBlockDone, INFINITE);
      int indexToPrepare = m_dev->m_index;

      // calcolo l'indice successivo per far l'unprepare
      m_dev->m_index = (m_dev->m_index + 1) % m_dev->m_whdr.size();
      if (m_dev->m_whdr[m_dev->m_index].dwFlags & WHDR_DONE) {
        TINT32 byteRecorded = m_dev->m_whdr[m_dev->m_index].dwBytesRecorded;
        if (byteRecorded) {
          m_dev->m_recordedBlocks.push_back(
              m_dev->m_whdr[m_dev->m_index].lpData);
          m_dev->m_byteRecorded += byteRecorded;
        }

        try {
          m_dev->unprepareHeader(m_dev->m_whdr[m_dev->m_index]);
        } catch (TException &) {
          for (int i = 0; i < (int)m_dev->m_recordedBlocks.size(); ++i)
            delete[] m_dev->m_recordedBlocks[i];
          return;
        }

        if (byteRecorded == 0) {
          delete[] m_dev->m_whdr[m_dev->m_index].lpData;
        }

        // con questo controllo si evita che vengano accodati nuovi blocchi
        // dopo che e' stata chiamata la waveInReset
        if (m_dev->m_isRecording) {
          try {
            m_dev->prepareHeader(new char[bytePerSec], bytePerSec,
                                 m_dev->m_whdr[indexToPrepare]);

            m_dev->addBlock(m_dev->m_whdr[indexToPrepare]);
          } catch (TException &) {
            m_dev->m_isRecording = false;
            for (int i = 0; i < (int)m_dev->m_recordedBlocks.size(); ++i)
              delete[] m_dev->m_recordedBlocks[i];
            return;
          }
        }

      } else
        m_dev->m_index = indexToPrepare;
    }
  } else {
    if (m_dev->m_isRecording)
      WaitForSingleObject(m_dev->m_hBlockDone, INFINITE);
    try {
      m_dev->unprepareHeader(m_dev->m_whdr[m_dev->m_index]);
      m_dev->m_isRecording = false;
    } catch (TException &) {
      m_dev->m_isRecording = false;
      return;
    }
  }

  SetEvent(m_dev->m_hLastBlockDone);
  return;
}

//------------------------------------------------------------------------------
#endif

double TSoundInputDevice::getVolume() {
  DWORD dwVolumeControlID;

  UINT nNumMixers;
  MMRESULT ret;
  MIXERLINE mxl;

  nNumMixers = mixerGetNumDevs();

  if (nNumMixers == 0)
    throw TSoundDeviceException(TSoundDeviceException::NoMixer,
                                "Doesn't exist a audio mixer device");

  // get dwLineID
  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  // get dwControlID
  MIXERCONTROL mxc;
  ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                       MIXERCONTROL_CONTROLTYPE_VOLUME);
  if (ret == MIXERR_INVALCONTROL)
    throw TSoundDeviceException(
        TSoundDeviceException::UnableVolume,
        "Is not possible to obtain info of volume by mixer");

  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  dwVolumeControlID = mxc.dwControlID;

  MIXERCONTROLDETAILS_UNSIGNED mxcdVolume;

  ret = getControlDetails((HMIXEROBJ)0, dwVolumeControlID, mxc.cMultipleItems,
                          &mxcdVolume);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  DWORD dwVal = mxcdVolume.dwValue;

  return (double)dwVal * 10.0 / (double)mxc.Bounds.dwMaximum;
}

//------------------------------------------------------------------------------

bool TSoundInputDevice::setVolume(double value) {
  DWORD dwVolumeControlID, dwMaximum;

  UINT nNumMixers;
  MMRESULT ret;
  MIXERLINE mxl;

  nNumMixers = mixerGetNumDevs();

  if (nNumMixers == 0)
    throw TSoundDeviceException(TSoundDeviceException::NoMixer,
                                "Doesn't exist a audio mixer device");

  // get dwLineID
  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    throw TException("Error to obtain info by mixer");

  // get dwControlID
  MIXERCONTROL mxc;
  ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                       MIXERCONTROL_CONTROLTYPE_VOLUME);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  dwMaximum         = mxc.Bounds.dwMaximum;
  dwVolumeControlID = mxc.dwControlID;

  int newValue;
  double fattProp = ((double)(mxc.Metrics.cSteps - 1) * value) / 10;
  double delta    = (double)(dwMaximum / (mxc.Metrics.cSteps - 1));
  newValue        = (int)(tround(fattProp) * delta);

  MIXERCONTROLDETAILS_UNSIGNED mxcdVolume = {(DWORD)newValue};
  ret = setControlDetails((HMIXEROBJ)0, dwVolumeControlID, mxc.cMultipleItems,
                          &mxcdVolume);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");
  return true;
}

//------------------------------------------------------------------------------

bool TSoundInputDevice::supportsVolume() {
  UINT nNumMixers;
  MMRESULT ret;
  MIXERLINE mxl;

  nNumMixers = mixerGetNumDevs();

  if (nNumMixers == 0)
    throw TSoundDeviceException(TSoundDeviceException::NoMixer,
                                "Doesn't exist a audio mixer device");

  // get dwLineID
  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  // get dwControlID
  MIXERCONTROL mxc;
  ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                       MIXERCONTROL_CONTROLTYPE_VOLUME);
  if (ret == MIXERR_INVALCONTROL) return false;

  if (ret != MMSYSERR_NOERROR)
    throw TSoundDeviceException(TSoundDeviceException::UnableVolume,
                                "Error to obtain info by mixer");

  return true;
}

//------------------------------------------------------------------------------

bool TSoundInputDevice::isRecording() { return m_imp->m_isRecording; }

//------------------------------------------------------------------------------

/*TSoundTrackFormat TSoundInputDevice::getPreferredFormat(
      ULONG sampleRate, int channelCount, int bitPerSample)
{
  MMRESULT ret;
  TSoundTrackFormat fmt;

  ret = isaFormatSupported(sampleRate, channelCount, bitPerSample, true);

  if (ret == MMSYSERR_NOERROR)
  {
    fmt.m_bitPerSample = bitPerSample;
    fmt.m_channelCount = channelCount;
    fmt.m_sampleRate = sampleRate;

    if (bitPerSample >= 16)
      fmt.m_signedSample = true;
    else
      fmt.m_signedSample = false;
    return fmt;
  }
  if (ret == WAVERR_BADFORMAT)
  {
    //avvvicinarsi al sample rate => dovrebbe esser OK avendo selezionato i piu'
vicini
    std::set<int>::iterator it = m_imp->m_supportedRate.lower_bound(sampleRate);
    if (it == m_imp->m_supportedRate.end())
    {
                  it = std::max_element(m_imp->m_supportedRate.begin(),
                                         m_imp->m_supportedRate.end());
                        if (it != m_imp->m_supportedRate.end())
                  sampleRate = *(m_imp->m_supportedRate.rbegin());
      else
        throw TSoundDeviceException(
          TSoundDeviceException::UnsupportedFormat,
          "There isn't a supported format");
    }
    else
      sampleRate = *it;
    ret = isaFormatSupported(sampleRate, channelCount, bitPerSample, true);
    if (ret == MMSYSERR_NOERROR)
    {
      fmt.m_bitPerSample = bitPerSample;
      fmt.m_channelCount = channelCount;
      fmt.m_sampleRate = sampleRate;

      if (bitPerSample >= 16)
        fmt.m_signedSample = true;
      else
        fmt.m_signedSample = false;
      return fmt;
    }
    if (ret == WAVERR_BADFORMAT)
    {
      //cambiare bps
      if (bitPerSample <= 8)
        bitPerSample = 8;
      else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16)
        bitPerSample = 16;

      ret = isaFormatSupported(sampleRate, channelCount, bitPerSample, true);
      if (ret == MMSYSERR_NOERROR)
      {
        fmt.m_bitPerSample = bitPerSample;
        fmt.m_channelCount = channelCount;
        fmt.m_sampleRate = sampleRate;

        if (bitPerSample >= 16)
          fmt.m_signedSample = true;
        else
          fmt.m_signedSample = false;
        return fmt;
      }
      if (ret == WAVERR_BADFORMAT)
      {
        //switch mono/stereo
        if (channelCount <= 1)
          channelCount = 1;
        else
          channelCount = 2;

        ret = isaFormatSupported(sampleRate, channelCount, bitPerSample, true);
        if (ret == MMSYSERR_NOERROR)
        {
          fmt.m_bitPerSample = bitPerSample;
          fmt.m_channelCount = channelCount;
          fmt.m_sampleRate = sampleRate;

          if (bitPerSample >= 16)
            fmt.m_signedSample = true;
          else
            fmt.m_signedSample = false;
          return fmt;
        }
        if (ret == WAVERR_BADFORMAT)
        {
          throw TSoundDeviceException(
            TSoundDeviceException::UnsupportedFormat,
            "Doesn't exist a preferred format");
        }
      }
    }
  }
  throw TSoundDeviceException(
    TSoundDeviceException::UnsupportedFormat,
    "Error to query supported format");
}
*/
TSoundTrackFormat TSoundInputDevice::getPreferredFormat(TUINT32 sampleRate,
                                                        int channelCount,
                                                        int bitPerSample) {
  TSoundTrackFormat fmt;

  // avvvicinarsi al sample rate => dovrebbe esser OK avendo selezionato i piu'
  // vicini
  std::set<int>::iterator it = m_imp->m_supportedRate.lower_bound(sampleRate);
  if (it == m_imp->m_supportedRate.end()) {
    it = std::max_element(m_imp->m_supportedRate.begin(),
                          m_imp->m_supportedRate.end());
    if (it != m_imp->m_supportedRate.end())
      sampleRate = *(m_imp->m_supportedRate.rbegin());
    else
      throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                  "There isn't a supported format");
  } else
    sampleRate = *it;

  // Normalize bit sample and set preferred sample type
  if (bitPerSample <= 8) {
    bitPerSample     = 8;
    fmt.m_sampleType = TSound::UINT;
  } else if (bitPerSample <= 16) {
    bitPerSample     = 16;
    fmt.m_sampleType = TSound::INT;
  } else if (bitPerSample <= 24) {
    bitPerSample     = 24;
    fmt.m_sampleType = TSound::INT;
  } else if (bitPerSample <= 32) {
    bitPerSample     = 32;
    fmt.m_sampleType = TSound::FLOAT;
  }

  // switch mono/stereo
  if (channelCount <= 1)
    channelCount = 1;
  else
    channelCount = 2;

  fmt.m_bitPerSample = bitPerSample;
  fmt.m_channelCount = channelCount;
  fmt.m_sampleRate   = sampleRate;

  return fmt;
}

//------------------------------------------------------------------------------

TSoundTrackFormat TSoundInputDevice::getPreferredFormat(
    const TSoundTrackFormat &format) {
  try {
    return getPreferredFormat(format.m_sampleRate, format.m_channelCount,
                              format.m_bitPerSample);
  } catch (TSoundDeviceException &e) {
    throw TSoundDeviceException(TSoundDeviceException::UnsupportedFormat,
                                e.getMessage());
  }
}

//==============================================================================
//==============================================================================
// Funzioni per l'interazione con il mixer device
//==============================================================================
//==============================================================================

namespace {

// restituisce dentro la struttura mxc le informazioni relative
// al controllo di tipo dwControlType associato alla linea
// identificata da dwLineID
MMRESULT getLineControl(MIXERCONTROL &mxc, HMIXEROBJ hMixer, DWORD dwLineID,
                        DWORD dwControlType) {
  MIXERLINECONTROLS mxlc;
  mxlc.cbStruct      = sizeof(MIXERLINECONTROLS);
  mxlc.dwLineID      = dwLineID;
  mxlc.dwControlType = dwControlType;
  mxlc.cControls     = 1;
  mxlc.cbmxctrl      = sizeof(MIXERCONTROL);
  mxlc.pamxctrl      = &mxc;
  MMRESULT ret       = mixerGetLineControls(
      (HMIXEROBJ)hMixer, &mxlc,
      MIXER_OBJECTF_HMIXER | MIXER_GETLINECONTROLSF_ONEBYTYPE);
  return ret;
}

//------------------------------------------------------------------------------

// restituisce nella struttura mxl le informazioni relative alla linea
// sorgente individuata dagli estremi destination e source
MMRESULT getLineInfo(HMIXEROBJ hMixer, MIXERLINE &mxl, DWORD destination,
                     DWORD source) {
  MMRESULT ret;

  mxl.cbStruct      = sizeof(mxl);
  mxl.dwDestination = destination;
  mxl.dwSource      = source;
  ret               = mixerGetLineInfo(0, &mxl,
                                       MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_SOURCE);
  return ret;
}

//------------------------------------------------------------------------------

// restituisce nella struttura mxl le informazioni relative alla linea
// individuata da dwLineID
MMRESULT getLineInfo(HMIXEROBJ hMixer, MIXERLINE &mxl, DWORD dwLineID) {
  MMRESULT ret;

  mxl.cbStruct = sizeof(mxl);
  mxl.dwLineID = dwLineID;
  ret          = mixerGetLineInfo((HMIXEROBJ)hMixer, &mxl,
                                  MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_LINEID);
  return ret;
}

//------------------------------------------------------------------------------

// restituisce nella struttura mxl le informazioni relative alla linea
// individuata dal tipo specificato in dwComponentType
MMRESULT getLineInfo(HMIXEROBJ hMixer, DWORD dwComponentType, MIXERLINE &mxl) {
  MMRESULT ret;

  mxl.cbStruct        = sizeof(MIXERLINE);
  mxl.dwComponentType = dwComponentType;
  ret =
      mixerGetLineInfo((HMIXEROBJ)hMixer, &mxl,
                       MIXER_OBJECTF_HMIXER | MIXER_GETLINEINFOF_COMPONENTTYPE);
  return ret;
}

//------------------------------------------------------------------------------

// consente di settare il valore booleano specificato in mxcdSelectValue
// relativo al controllo specificato in dwSelectControlID
MMRESULT setControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_BOOLEAN *mxcdSelectValue) {
  MMRESULT ret;
  MIXERCONTROLDETAILS mxcd;
  mxcd.cbStruct       = sizeof(MIXERCONTROLDETAILS);
  mxcd.dwControlID    = dwSelectControlID;
  mxcd.cChannels      = 1;
  mxcd.cMultipleItems = dwMultipleItems;
  mxcd.cbDetails      = sizeof(MIXERCONTROLDETAILS_BOOLEAN);
  mxcd.paDetails      = mxcdSelectValue;
  ret                 = mixerSetControlDetails(
      (HMIXEROBJ)hMixer, &mxcd,
      MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
  return ret;
}

//------------------------------------------------------------------------------

// consente di settare il valore UNSIGNED specificato in mxcdSelectValue
// relativo al controllo specificato in dwSelectControlID
MMRESULT setControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue) {
  MMRESULT ret;
  MIXERCONTROLDETAILS mxcd;
  mxcd.cbStruct       = sizeof(MIXERCONTROLDETAILS);
  mxcd.dwControlID    = dwSelectControlID;
  mxcd.cChannels      = 1;
  mxcd.cMultipleItems = dwMultipleItems;
  mxcd.cbDetails      = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
  mxcd.paDetails      = mxcdSelectValue;
  ret                 = mixerSetControlDetails(
      (HMIXEROBJ)hMixer, &mxcd,
      MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
  return ret;
}

//------------------------------------------------------------------------------

// consente di ottenere il valore UNSIGNED specificato in mxcdSelectValue
// relativo al controllo specificato in dwSelectControlID
MMRESULT getControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_UNSIGNED *mxcdSelectValue) {
  MMRESULT ret;
  MIXERCONTROLDETAILS mxcd;
  mxcd.cbStruct       = sizeof(MIXERCONTROLDETAILS);
  mxcd.dwControlID    = dwSelectControlID;
  mxcd.cChannels      = 1;
  mxcd.cMultipleItems = dwMultipleItems;
  mxcd.cbDetails      = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
  mxcd.paDetails      = mxcdSelectValue;
  ret                 = mixerGetControlDetails(
      (HMIXEROBJ)hMixer, &mxcd,
      MIXER_OBJECTF_HMIXER | MIXER_SETCONTROLDETAILSF_VALUE);
  return ret;
}
//------------------------------------------------------------------------------

// consente di ottenere la lista di informazioni in pmxcdSelectText
// relativo al controllo specificato in dwSelectControlID
MMRESULT getControlDetails(HMIXEROBJ hMixer, DWORD dwSelectControlID,
                           DWORD dwMultipleItems,
                           MIXERCONTROLDETAILS_LISTTEXT *pmxcdSelectText) {
  MMRESULT ret;
  MIXERCONTROLDETAILS mxcd;

  mxcd.cbStruct       = sizeof(MIXERCONTROLDETAILS);
  mxcd.dwControlID    = dwSelectControlID;
  mxcd.cChannels      = 1;
  mxcd.cMultipleItems = dwMultipleItems;
  mxcd.cbDetails      = sizeof(MIXERCONTROLDETAILS_LISTTEXT);
  mxcd.paDetails      = pmxcdSelectText;
  ret                 = mixerGetControlDetails((HMIXEROBJ)0, &mxcd,
                                               MIXER_GETCONTROLDETAILSF_LISTTEXT);
  return ret;
}

//------------------------------------------------------------------------------

// restituiscei l nome della linea identificata da lineID
std::string getMixerLineName(DWORD lineID) {
  MIXERLINE mxl;
  MMRESULT ret;

  ret = getLineInfo((HMIXEROBJ)0, mxl, lineID);
#ifdef TNZCORE_LIGHT
  assert(false);
  return "";
#else
  return QString::fromWCharArray(mxl.szName).toStdString();
#endif
}

//------------------------------------------------------------------------------

// restituisce la lista degli identificativi delle linee sorgente associate
// alla destinazione di tipo dstComponentType
std::list<DWORD> getMixerSrcLines(DWORD dstComponentType) {
  std::list<DWORD> srcList;
  MMRESULT ret;
  MIXERLINE mxl;

  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    // forse bisognerebbe lanciare un'eccezione
    return srcList;  // non ha linea di dst per la registrazione

  int v;
  for (v = 0; v < (int)mxl.cConnections; v++) {
    MIXERLINE mxl1;

    ret = getLineInfo((HMIXEROBJ)0, mxl1, mxl.dwDestination, v);
    if (ret == MMSYSERR_NOERROR) srcList.push_back(mxl1.dwLineID);
  }
  return srcList;
}

//------------------------------------------------------------------------------

// restituisce la lista degli identificativi delle linee sorgente di tipo
// srcComponentType associate alla destinazione di tipo dstComponentType
std::list<DWORD> getMixerSrcLines(DWORD dstComponentType,
                                  DWORD srcComponentType) {
  std::list<DWORD> srcList;
  MMRESULT ret;
  MIXERLINE mxl;

  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    // forse bisognerebbe lanciare un'eccezione
    return srcList;  // non ha linea di dst per la registrazione

  int v;
  for (v = 0; v < (int)mxl.cConnections; v++) {
    MIXERLINE mxl1;

    ret = getLineInfo((HMIXEROBJ)0, mxl1, mxl.dwDestination, v);
    if (ret == MMSYSERR_NOERROR)
      if (mxl1.dwComponentType == srcComponentType)
        srcList.push_back(mxl1.dwLineID);
  }
  return srcList;
}

//------------------------------------------------------------------------------

// restituisce true sse la linea destinazione di tipo dstComponentType
// supporta una linea sorgente di tipo srcComponentType
bool isSrcLineSupported(DWORD dstComponentType, DWORD srcComponentType) {
  // ci possono essere piu' linee sorgente dello stesso tipo in
  // corrispondenza di una data linea destinazione ?
  MMRESULT ret;
  MIXERLINE mxl;

  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    return false;  // non ha linea di dst per la registrazione

  int v;
  for (v = 0; v < (int)mxl.cConnections; v++) {
    MIXERLINE mxl1;

    ret = getLineInfo((HMIXEROBJ)0, mxl1, mxl.dwDestination, v);
    if (ret == MMSYSERR_NOERROR)
      if (mxl1.dwComponentType == srcComponentType) return true;
  }

  return false;
}

//------------------------------------------------------------------------------

bool activateSrcLine(const MIXERLINE &mxlDst, DWORD componentTypeSrc) {
  if (!isSrcLineSupported(mxlDst.dwComponentType, componentTypeSrc))
    return false;

  bool bRetVal = true;

  for (DWORD v = 0; v < mxlDst.cConnections; v++) {
    MIXERLINE mxlSrc;
    MMRESULT ret = getLineInfo((HMIXEROBJ)0, mxlSrc, mxlDst.dwDestination, v);

    if (ret == MMSYSERR_NOERROR) {
      // chiedo il controllo di tipo MUTE della linea sorgente
      MIXERCONTROL mxc;
      ret = getLineControl(mxc, (HMIXEROBJ)0, mxlSrc.dwLineID,
                           MIXERCONTROL_CONTROLTYPE_MUTE);

      if (ret == MMSYSERR_NOERROR) {
        MIXERCONTROLDETAILS_BOOLEAN mxcdSelectValue;
        mxcdSelectValue.fValue =
            mxlSrc.dwComponentType == componentTypeSrc ? 0L : 1L;

        ret = setControlDetails((HMIXEROBJ)0, mxc.dwControlID,
                                mxc.cMultipleItems, &mxcdSelectValue);
        if (ret != MMSYSERR_NOERROR) bRetVal = false;
      }
    }
  }
  return bRetVal;
}

//------------------------------------------------------------------------------

bool setSrcMixMuxControl(MIXERCONTROL mxc, DWORD componentTypeSrc) {
  MMRESULT ret;
  DWORD dwIndexLine;
  bool found = false;

  // mantengo nota del ID del controllo dsst individuato e
  // del numero di linee src ad esso associate
  DWORD dwSelectControlID = mxc.dwControlID;
  DWORD dwMultipleItems   = mxc.cMultipleItems;

  if (dwMultipleItems == 0) return false;

  // determino l'indice dell'item corrispondente alla linea sorgente
  // di tipo componentTypeSrc

  std::unique_ptr<MIXERCONTROLDETAILS_LISTTEXT[]> pmxcdSelectText(
      new MIXERCONTROLDETAILS_LISTTEXT[dwMultipleItems]);

  if (pmxcdSelectText) {
    // estraggo le info su tutte le linee associate al controllo
    ret = getControlDetails((HMIXEROBJ)0, dwSelectControlID, dwMultipleItems,
                            pmxcdSelectText.get());

    if (ret == MMSYSERR_NOERROR) {
      for (DWORD dwi = 0; dwi < dwMultipleItems; dwi++) {
        // prendo le info su ogni linea e verifico se e' del giusto tipo
        MIXERLINE mxl;
        ret = getLineInfo((HMIXEROBJ)0, mxl, pmxcdSelectText[dwi].dwParam1);
        if (ret == MMSYSERR_NOERROR &&
            mxl.dwComponentType == componentTypeSrc) {
          dwIndexLine = dwi;
          found       = true;
          break;
        }
      }
    }

    if (!found) return false;
  }

  if (dwIndexLine >= dwMultipleItems) return false;

  bool bRetVal = false;

  std::unique_ptr<MIXERCONTROLDETAILS_BOOLEAN[]> pmxcdSelectValue(
      new MIXERCONTROLDETAILS_BOOLEAN[dwMultipleItems]);

  if (pmxcdSelectValue) {
    ::ZeroMemory(pmxcdSelectValue.get(),
                 dwMultipleItems * sizeof(MIXERCONTROLDETAILS_BOOLEAN));

    // impostazione del valore
    pmxcdSelectValue[dwIndexLine].fValue =
        (TINT32)1;  // lVal; //dovrebbe esser uno

    ret = setControlDetails((HMIXEROBJ)0, dwSelectControlID, dwMultipleItems,
                            pmxcdSelectValue.get());
    if (ret == MMSYSERR_NOERROR) bRetVal = true;
  }
  return bRetVal;
}

//------------------------------------------------------------------------------

bool setRecordLine(TSoundInputDevice::Source typeInput) {
  DWORD dwComponentTypeSrc;

  UINT nNumMixers;
  MMRESULT ret;
  MIXERLINE mxl = {0};

  switch (typeInput) {
  case TSoundInputDevice::LineIn:
    dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_LINE /*|
                             MIXERLINE_COMPONENTTYPE_SRC_AUXILIARY |
                             MIXERLINE_COMPONENTTYPE_SRC_ANALOG*/
        ;
    break;
  case TSoundInputDevice::DigitalIn:
    dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_DIGITAL;
    break;
  case TSoundInputDevice::CdAudio:
    dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_COMPACTDISC;
    break;
  default:
    dwComponentTypeSrc = MIXERLINE_COMPONENTTYPE_SRC_MICROPHONE;
  }

  nNumMixers = mixerGetNumDevs();

  if (nNumMixers == 0) return false;

  // utilizziamo il MIXER di default identificato dall'indice 0
  // vedo se il device ha una linea dst per il wave_input
  ret = getLineInfo((HMIXEROBJ)0, MIXERLINE_COMPONENTTYPE_DST_WAVEIN, mxl);
  if (ret != MMSYSERR_NOERROR)
    return false;  // non ha linea di dst per la registrazione

  // vediamo che tipo controllo ha questa linea dst

  // sara' un MIXER?
  MIXERCONTROL mxc = {0};

  ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                       MIXERCONTROL_CONTROLTYPE_MIXER);

  if (ret != MMSYSERR_NOERROR) {
    // no mixer, try MUX
    ret = getLineControl(mxc, (HMIXEROBJ)0, mxl.dwLineID,
                         MIXERCONTROL_CONTROLTYPE_MUX);

    if (ret != MMSYSERR_NOERROR) {
      // vediamo se e' uno di quei device ne' MIXER ne' MUX
      return activateSrcLine(mxl, dwComponentTypeSrc);
    } else {
      // la linea ha un controllo di tipo MUX
      return setSrcMixMuxControl(mxc, dwComponentTypeSrc);
    }
  } else {
    // la linea ha un controllo di tipo MIXER
    return setSrcMixMuxControl(mxc, dwComponentTypeSrc);
  }
}

//------------------------------------------------------------------------------

MMRESULT isaFormatSupported(int sampleRate, int channelCount, int bitPerSample,
                            int sampleType, bool input) {
  WAVEFORMATEX wf;
  MMRESULT ret;

  wf.wFormatTag      = sampleType & TSound::WMASK;
  wf.nChannels       = channelCount;
  wf.nSamplesPerSec  = sampleRate;
  wf.wBitsPerSample  = bitPerSample;
  wf.nBlockAlign     = (wf.nChannels * wf.wBitsPerSample) >> 3;
  wf.nAvgBytesPerSec = wf.nSamplesPerSec * wf.nBlockAlign;
  wf.cbSize          = 0;

  if (input)
    ret = waveInOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY);
  else
    ret = waveOutOpen(NULL, WAVE_MAPPER, &wf, NULL, NULL, WAVE_FORMAT_QUERY);
  return ret;
}
}  // namespace
