2177 lines
67 KiB
C++
2177 lines
67 KiB
C++
|
|
|
|
#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 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 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 = 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 final : public WAVEFORMATEX {
|
|
public:
|
|
WaveFormat(){};
|
|
WaveFormat(unsigned char channelCount, TUINT32 sampleRate,
|
|
unsigned char bitPerSample);
|
|
|
|
~WaveFormat(){};
|
|
};
|
|
|
|
WaveFormat::WaveFormat(unsigned char channelCount, TUINT32 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, 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)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,
|
|
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);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
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
|
|
std::string getMixerLineName(DWORD lineID) {
|
|
MIXERLINE mxl;
|
|
MMRESULT ret;
|
|
|
|
ret = getLineInfo((HMIXEROBJ)0, mxl, lineID);
|
|
#ifdef TNZCORE_LIGHT
|
|
assert(false);
|
|
return "";
|
|
#else
|
|
return std::string(mxl.szName);
|
|
#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,
|
|
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;
|
|
}
|
|
}
|