#include "tsound.h"
#ifndef TNZCORE_LIGHT
#include "tthread.h"
#endif
#include "tsop.h"
#include <set>
#include "tsystem.h"
//=========================================================
//forward declarations
class TSoundOutputDeviceImp;
class TSoundInputDeviceImp;
//=========================================================
namespace
{
void CALLBACK recordCB(
HWAVEIN hwi,
UINT uMsg,
DWORD dwInstance,
DWORD 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, bool input);
DWORD WINAPI MyWaveOutCallbackThread(LPVOID lpParameter);
void getAmplitude(int &litude, const TSoundTrackP st, TINT32 sample);
}
//==============================================================================
// Class to send the message that a playback is completed
//==============================================================================
#ifndef TNZCORE_LIGHT
class EndPlayMsg : public TThread::Message
{
public:
EndPlayMsg(TSoundOutputDeviceListener *notifier)
{
m_listener = notifier;
}
TThread::Message *clone() const
{
return new EndPlayMsg(*this);
}
void onDeliver()
{
m_listener->onPlayCompleted();
}
private:
TSoundOutputDeviceListener *m_listener;
};
#endif
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;
};
//==============================================================================
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 = WAVE_FORMAT_PCM;
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)m_notifyThreadId, (DWORD) 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);
}
//----------------------------------------------------------------------------
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 &litude, 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;
}
}
//------------------------------------------------------------------------------
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;
if (bitPerSample <= 8)
bitPerSample = 8;
else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16)
bitPerSample = 16;
if (bitPerSample >= 16)
fmt.m_signedSample = true;
else
fmt.m_signedSample = false;
//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 : public WAVEFORMATEX
{
public:
WaveFormat(){};
WaveFormat(unsigned char channelCount,
unsigned TINT32 sampleRate,
unsigned char bitPerSample);
~WaveFormat(){};
};
WaveFormat::WaveFormat(unsigned char channelCount,
unsigned TINT32 sampleRate,
unsigned char bitPerSample)
{
wFormatTag = WAVE_FORMAT_PCM;
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,
unsigned TINT32 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)recordCB,
(DWORD)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,
unsigned TINT32 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 : public TThread::Runnable
{
public:
RecordTask(std::shared_ptr<TSoundInputDeviceImp> dev)
: Runnable(), m_dev(std::move(dev)) {}
~RecordTask() {}
void run();
std::shared_ptr<TSoundInputDeviceImp> m_dev;
};
#endif
//====================================================================
class TSoundInputDeviceImp : 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
vector<WAVEHDR> m_whdr;
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);
}
//----------------------------------------------------------------------------
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 dwInstance,
DWORD dwParam1,
DWORD dwParam2)
{
WAVEHDR *whdr = (WAVEHDR *)dwParam1;
HANDLE *blockDone = (HANDLE *)dwInstance;
if (uMsg != MM_WIM_DATA)
return;
SetEvent(blockDone);
}
}
//==============================================================================
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->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->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 = {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;
if (bitPerSample <= 8)
bitPerSample = 8;
else if ((bitPerSample > 8 && bitPerSample < 16) || bitPerSample >= 16)
bitPerSample = 16;
if (bitPerSample >= 16)
fmt.m_signedSample = true;
else
fmt.m_signedSample = false;
//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
string getMixerLineName(DWORD lineID)
{
MIXERLINE mxl;
MMRESULT ret;
ret = getLineInfo((HMIXEROBJ)0, mxl, lineID);
#ifdef TNZCORE_LIGHT
assert(false);
return "";
#else
return string(mxl.szName);
#endif
}
//------------------------------------------------------------------------------
// restituisce la lista degli identificativi delle linee sorgente associate
// alla destinazione di tipo dstComponentType
list<DWORD> getMixerSrcLines(DWORD dstComponentType)
{
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
list<DWORD> getMixerSrcLines(DWORD dstComponentType, DWORD srcComponentType)
{
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
MIXERCONTROLDETAILS_LISTTEXT *pmxcdSelectText =
new MIXERCONTROLDETAILS_LISTTEXT[dwMultipleItems];
if (pmxcdSelectText != NULL) {
// estraggo le info su tutte le linee associate al controllo
ret = getControlDetails((HMIXEROBJ)0, dwSelectControlID,
dwMultipleItems, pmxcdSelectText);
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;
}
}
}
delete[] pmxcdSelectText;
if (!found)
return false;
}
if (dwIndexLine >= dwMultipleItems)
return false;
bool bRetVal = false;
MIXERCONTROLDETAILS_BOOLEAN *pmxcdSelectValue =
new MIXERCONTROLDETAILS_BOOLEAN[dwMultipleItems];
if (pmxcdSelectValue != NULL) {
::ZeroMemory(pmxcdSelectValue, dwMultipleItems * sizeof(MIXERCONTROLDETAILS_BOOLEAN));
// impostazione del valore
pmxcdSelectValue[dwIndexLine].fValue = (TINT32)1; // lVal; //dovrebbe esser uno
ret = setControlDetails((HMIXEROBJ)0,
dwSelectControlID,
dwMultipleItems,
pmxcdSelectValue);
if (ret == MMSYSERR_NOERROR)
bRetVal = true;
delete[] pmxcdSelectValue;
}
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, bool input)
{
WAVEFORMATEX wf;
MMRESULT ret;
wf.wFormatTag = WAVE_FORMAT_PCM;
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;
}
}