564 lines
19 KiB
C++
Executable file
564 lines
19 KiB
C++
Executable file
/******************************************************************************\
|
|
* Copyright (c) 2004-2009
|
|
*
|
|
* Author(s):
|
|
* Volker Fischer
|
|
*
|
|
* Description:
|
|
* Sound card interface for Windows operating systems
|
|
*
|
|
******************************************************************************
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation; either version 2 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
* details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
\******************************************************************************/
|
|
|
|
#include "Sound.h"
|
|
|
|
|
|
/* Implementation *************************************************************/
|
|
// external references
|
|
extern AsioDrivers* asioDrivers;
|
|
bool loadAsioDriver ( char *name );
|
|
|
|
// TODO the following variables should be in the class definition but we cannot
|
|
// do it here since we have static callback functions which cannot access the
|
|
// class members :-(((
|
|
|
|
// ASIO stuff
|
|
ASIODriverInfo driverInfo;
|
|
ASIOBufferInfo bufferInfos[2 * NUM_IN_OUT_CHANNELS]; // for input and output buffers -> "2 *"
|
|
ASIOChannelInfo channelInfos[2 * NUM_IN_OUT_CHANNELS];
|
|
bool bASIOPostOutput;
|
|
ASIOCallbacks asioCallbacks;
|
|
int iASIOBufferSizeMono;
|
|
int iASIOBufferSizeStereo;
|
|
|
|
CVector<short> vecsTmpAudioSndCrdStereo;
|
|
|
|
QMutex ASIOMutex;
|
|
|
|
// TEST
|
|
CSound* pSound;
|
|
|
|
|
|
/******************************************************************************\
|
|
* Common *
|
|
\******************************************************************************/
|
|
void CSound::SetDev ( const int iNewDev )
|
|
{
|
|
// check if an ASIO driver was already initialized
|
|
if ( lCurDev >= 0 )
|
|
{
|
|
// a device was already been initialized and is used, first clean up
|
|
// dispose ASIO buffers
|
|
ASIODisposeBuffers();
|
|
|
|
// remove old driver
|
|
ASIOExit();
|
|
asioDrivers->removeCurrentDriver();
|
|
|
|
const std::string strErrorMessage = LoadAndInitializeDriver ( iNewDev );
|
|
|
|
if ( !strErrorMessage.empty() )
|
|
{
|
|
// loading and initializing the new driver failed, go back to
|
|
// original driver and display error message
|
|
LoadAndInitializeDriver ( lCurDev );
|
|
Init ( iASIOBufferSizeStereo );
|
|
|
|
throw CGenErr ( strErrorMessage.c_str() );
|
|
}
|
|
|
|
Init ( iASIOBufferSizeStereo );
|
|
}
|
|
else
|
|
{
|
|
if ( iNewDev != INVALID_SNC_CARD_DEVICE )
|
|
{
|
|
// This is the first time a driver is to be initialized, we first
|
|
// try to load the selected driver, if this fails, we try to load
|
|
// the first available driver in the system. If this fails, too, we
|
|
// throw an error that no driver is available -> it does not make
|
|
// sense to start the llcon software if no audio hardware is
|
|
// available
|
|
if ( !LoadAndInitializeDriver ( iNewDev ).empty() )
|
|
{
|
|
// loading and initializing the new driver failed, try to find
|
|
// at least one usable driver
|
|
if ( !LoadAndInitializeFirstValidDriver() )
|
|
{
|
|
throw CGenErr ( "No usable ASIO audio device "
|
|
"(driver) found." );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// try to find one usable driver (select the first valid driver)
|
|
if ( !LoadAndInitializeFirstValidDriver() )
|
|
{
|
|
throw CGenErr ( "No usable ASIO audio device (driver) found." );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CSound::LoadAndInitializeFirstValidDriver()
|
|
{
|
|
// load and initialize first valid ASIO driver
|
|
bool bValidDriverDetected = false;
|
|
int iCurDriverIdx = 0;
|
|
|
|
// try all available drivers in the system ("lNumDevs" devices)
|
|
while ( !bValidDriverDetected && ( iCurDriverIdx < lNumDevs ) )
|
|
{
|
|
if ( LoadAndInitializeDriver ( iCurDriverIdx ).empty() )
|
|
{
|
|
// initialization was successful
|
|
bValidDriverDetected = true;
|
|
|
|
// store ID of selected driver
|
|
lCurDev = iCurDriverIdx;
|
|
}
|
|
|
|
// try next driver
|
|
iCurDriverIdx++;
|
|
}
|
|
|
|
return bValidDriverDetected;
|
|
}
|
|
|
|
std::string CSound::LoadAndInitializeDriver ( int iDriverIdx )
|
|
{
|
|
// first check and correct input parameter
|
|
if ( iDriverIdx >= lNumDevs )
|
|
{
|
|
// we assume here that at least one driver is in the system
|
|
iDriverIdx = 0;
|
|
}
|
|
|
|
// load driver
|
|
loadAsioDriver ( cDriverNames[iDriverIdx] );
|
|
if ( ASIOInit ( &driverInfo ) != ASE_OK )
|
|
{
|
|
// clean up and return error string
|
|
asioDrivers->removeCurrentDriver();
|
|
return "The audio driver could not be initialized.";
|
|
}
|
|
|
|
// check device capabilities if it fullfills our requirements
|
|
const std::string strStat = CheckDeviceCapabilities();
|
|
|
|
// store ID of selected driver if initialization was successful
|
|
if ( strStat.empty() )
|
|
{
|
|
lCurDev = iDriverIdx;
|
|
}
|
|
else
|
|
{
|
|
// driver cannot be used, clean up
|
|
asioDrivers->removeCurrentDriver();
|
|
}
|
|
|
|
return strStat;
|
|
}
|
|
|
|
std::string CSound::CheckDeviceCapabilities()
|
|
{
|
|
// This function checks if our required input/output channel
|
|
// properties are supported by the selected device. If the return
|
|
// string is empty, the device can be used, otherwise the error
|
|
// message is returned.
|
|
|
|
// check the sample rate
|
|
const ASIOError CanSaRateReturn = ASIOCanSampleRate ( SND_CRD_SAMPLE_RATE );
|
|
if ( ( CanSaRateReturn == ASE_NoClock ) ||
|
|
( CanSaRateReturn == ASE_NotPresent ) )
|
|
{
|
|
// return error string
|
|
return "The audio device does not support the "
|
|
"required sample rate.";
|
|
}
|
|
|
|
// check the number of available channels
|
|
long lNumInChan;
|
|
long lNumOutChan;
|
|
ASIOGetChannels ( &lNumInChan, &lNumOutChan );
|
|
if ( ( lNumInChan < NUM_IN_OUT_CHANNELS ) ||
|
|
( lNumOutChan < NUM_IN_OUT_CHANNELS ) )
|
|
{
|
|
// return error string
|
|
return "The audio device does not support the "
|
|
"required number of channels.";
|
|
}
|
|
|
|
// check sample format
|
|
for ( int i = 0; i < 2 * NUM_IN_OUT_CHANNELS; i++ )
|
|
{
|
|
// check all used input and output channels
|
|
channelInfos[i].channel = i % NUM_IN_OUT_CHANNELS;
|
|
if ( i < NUM_IN_OUT_CHANNELS )
|
|
{
|
|
channelInfos[i].isInput = ASIOTrue;
|
|
}
|
|
else
|
|
{
|
|
channelInfos[i].isInput = ASIOFalse;
|
|
}
|
|
ASIOGetChannelInfo ( &channelInfos[i] );
|
|
|
|
// only 16/24/32 LSB is supported
|
|
if ( ( channelInfos[i].type != ASIOSTInt16LSB ) &&
|
|
( channelInfos[i].type != ASIOSTInt24LSB ) &&
|
|
( channelInfos[i].type != ASIOSTInt32LSB ) )
|
|
{
|
|
// return error string
|
|
return "Required audio sample format not "
|
|
"available (16/24/32 bit LSB).";
|
|
}
|
|
}
|
|
|
|
// everything is ok, return empty string for "no error" case
|
|
return "";
|
|
}
|
|
|
|
int CSound::GetActualBufferSize ( const int iDesiredBufferSizeMono )
|
|
{
|
|
int iActualBufferSizeMono;
|
|
|
|
// query the usable buffer sizes
|
|
ASIOGetBufferSize ( &HWBufferInfo.lMinSize,
|
|
&HWBufferInfo.lMaxSize,
|
|
&HWBufferInfo.lPreferredSize,
|
|
&HWBufferInfo.lGranularity );
|
|
|
|
// calculate "nearest" buffer size and set internal parameter accordingly
|
|
// first check minimum and maximum values
|
|
if ( iDesiredBufferSizeMono < HWBufferInfo.lMinSize )
|
|
{
|
|
iActualBufferSizeMono = HWBufferInfo.lMinSize;
|
|
}
|
|
else
|
|
{
|
|
if ( iDesiredBufferSizeMono > HWBufferInfo.lMaxSize )
|
|
{
|
|
iActualBufferSizeMono = HWBufferInfo.lMaxSize;
|
|
}
|
|
else
|
|
{
|
|
// initialization
|
|
int iTrialBufSize = HWBufferInfo.lMinSize;
|
|
int iLastTrialBufSize = HWBufferInfo.lMinSize;
|
|
bool bSizeFound = false;
|
|
|
|
// test loop
|
|
while ( ( iTrialBufSize <= HWBufferInfo.lMaxSize ) && ( !bSizeFound ) )
|
|
{
|
|
if ( iTrialBufSize >= iDesiredBufferSizeMono )
|
|
{
|
|
// test which buffer size fits better: the old one or the
|
|
// current one
|
|
if ( ( iTrialBufSize - iDesiredBufferSizeMono ) >
|
|
( iDesiredBufferSizeMono - iLastTrialBufSize ) )
|
|
{
|
|
iTrialBufSize = iLastTrialBufSize;
|
|
}
|
|
|
|
// exit while loop
|
|
bSizeFound = true;
|
|
}
|
|
|
|
if ( !bSizeFound )
|
|
{
|
|
// store old trial buffer size
|
|
iLastTrialBufSize = iTrialBufSize;
|
|
|
|
// increment trial buffer size (check for special case first)
|
|
if ( HWBufferInfo.lGranularity == -1 )
|
|
{
|
|
// special case: buffer sizes are a power of 2
|
|
iTrialBufSize *= 2;
|
|
}
|
|
else
|
|
{
|
|
iTrialBufSize += HWBufferInfo.lGranularity;
|
|
}
|
|
}
|
|
}
|
|
|
|
// set ASIO buffer size
|
|
iActualBufferSizeMono = iTrialBufSize;
|
|
}
|
|
}
|
|
|
|
return iActualBufferSizeMono;
|
|
}
|
|
|
|
int CSound::Init ( const int iNewPrefMonoBufferSize )
|
|
{
|
|
ASIOMutex.lock(); // get mutex lock
|
|
{
|
|
// get the actual sound card buffer size which is supported
|
|
// by the audio hardware
|
|
iASIOBufferSizeMono =
|
|
GetActualBufferSize ( iNewPrefMonoBufferSize );
|
|
|
|
// init base clasee
|
|
CSoundBase::Init ( iASIOBufferSizeMono );
|
|
|
|
// set internal buffer size value and calculate stereo buffer size
|
|
iASIOBufferSizeStereo = 2 * iASIOBufferSizeMono;
|
|
|
|
// set the sample rate
|
|
ASIOSetSampleRate ( SND_CRD_SAMPLE_RATE );
|
|
|
|
// create memory for intermediate audio buffer
|
|
vecsTmpAudioSndCrdStereo.Init ( iASIOBufferSizeStereo );
|
|
|
|
// create and activate ASIO buffers (buffer size in samples)
|
|
ASIOCreateBuffers ( bufferInfos,
|
|
2 /* in/out */ * NUM_IN_OUT_CHANNELS /* stereo */,
|
|
iASIOBufferSizeMono, &asioCallbacks );
|
|
|
|
// check wether the driver requires the ASIOOutputReady() optimization
|
|
// (can be used by the driver to reduce output latency by one block)
|
|
bASIOPostOutput = ( ASIOOutputReady() == ASE_OK );
|
|
}
|
|
ASIOMutex.unlock();
|
|
|
|
return iASIOBufferSizeMono;
|
|
}
|
|
|
|
void CSound::Start()
|
|
{
|
|
// start audio
|
|
ASIOStart();
|
|
|
|
// call base class
|
|
CSoundBase::Start();
|
|
}
|
|
|
|
void CSound::Stop()
|
|
{
|
|
// stop audio
|
|
ASIOStop();
|
|
|
|
// call base class
|
|
CSoundBase::Stop();
|
|
}
|
|
|
|
CSound::CSound ( void (*fpNewCallback) ( CVector<short>& psData, void* arg ), void* arg ) :
|
|
CSoundBase ( true, fpNewCallback, arg )
|
|
{
|
|
int i;
|
|
|
|
// TEST
|
|
pSound = this;
|
|
|
|
|
|
// get available ASIO driver names in system
|
|
for ( i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ )
|
|
{
|
|
cDriverNames[i] = new char[32];
|
|
}
|
|
|
|
loadAsioDriver ( "dummy" ); // to initialize external object
|
|
lNumDevs = asioDrivers->getDriverNames ( cDriverNames, MAX_NUMBER_SOUND_CARDS );
|
|
|
|
// in case we do not have a driver available, throw error
|
|
if ( lNumDevs == 0 )
|
|
{
|
|
throw CGenErr ( "No ASIO audio device (driver) found." );
|
|
}
|
|
asioDrivers->removeCurrentDriver();
|
|
|
|
// init device index with illegal value to show that driver is not initialized
|
|
lCurDev = -1;
|
|
|
|
// init buffer infos, we always want to have two input and
|
|
// two output channels
|
|
for ( i = 0; i < NUM_IN_OUT_CHANNELS; i++ )
|
|
{
|
|
// prepare input channels
|
|
bufferInfos[i].isInput = ASIOTrue;
|
|
bufferInfos[i].channelNum = i;
|
|
bufferInfos[i].buffers[0] = 0;
|
|
bufferInfos[i].buffers[1] = 0;
|
|
|
|
// prepare output channels
|
|
bufferInfos[NUM_IN_OUT_CHANNELS + i].isInput = ASIOFalse;
|
|
bufferInfos[NUM_IN_OUT_CHANNELS + i].channelNum = i;
|
|
bufferInfos[NUM_IN_OUT_CHANNELS + i].buffers[0] = 0;
|
|
bufferInfos[NUM_IN_OUT_CHANNELS + i].buffers[1] = 0;
|
|
}
|
|
|
|
// set up the asioCallback structure
|
|
asioCallbacks.bufferSwitch = &bufferSwitch;
|
|
asioCallbacks.sampleRateDidChange = &sampleRateChanged;
|
|
asioCallbacks.asioMessage = &asioMessages;
|
|
asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo;
|
|
}
|
|
|
|
CSound::~CSound()
|
|
{
|
|
// cleanup ASIO stuff
|
|
ASIOStop();
|
|
ASIODisposeBuffers();
|
|
ASIOExit();
|
|
asioDrivers->removeCurrentDriver();
|
|
}
|
|
|
|
// ASIO callbacks -------------------------------------------------------------
|
|
ASIOTime* CSound::bufferSwitchTimeInfo ( ASIOTime *timeInfo,
|
|
long index,
|
|
ASIOBool processNow )
|
|
{
|
|
bufferSwitch ( index, processNow );
|
|
return 0L;
|
|
}
|
|
|
|
void CSound::bufferSwitch ( long index, ASIOBool processNow )
|
|
{
|
|
int iCurSample;
|
|
|
|
ASIOMutex.lock(); // get mutex lock
|
|
{
|
|
// perform the processing for input and output
|
|
for ( int i = 0; i < 2 * NUM_IN_OUT_CHANNELS; i++ ) // stereo
|
|
{
|
|
if ( bufferInfos[i].isInput == ASIOTrue )
|
|
{
|
|
// CAPTURE -----------------------------------------------------
|
|
// copy new captured block in thread transfer buffer (copy
|
|
// mono data interleaved in stereo buffer)
|
|
switch ( channelInfos[i].type )
|
|
{
|
|
case ASIOSTInt16LSB:
|
|
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
|
|
{
|
|
vecsTmpAudioSndCrdStereo[2 * iCurSample + bufferInfos[i].channelNum] =
|
|
( (short*) bufferInfos[i].buffers[index] )[iCurSample];
|
|
}
|
|
break;
|
|
|
|
case ASIOSTInt24LSB:
|
|
|
|
// not yet tested, horrible things might happen with the following code ;-)
|
|
|
|
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
|
|
{
|
|
// convert current sample in 16 bit format
|
|
int iCurSam = 0;
|
|
memcpy ( &iCurSam, ( (char*) bufferInfos[i].buffers[index] ) + iCurSample * 3, 3 );
|
|
iCurSam >>= 8;
|
|
|
|
vecsTmpAudioSndCrdStereo[2 * iCurSample + bufferInfos[i].channelNum] =
|
|
static_cast<short> ( iCurSam );
|
|
}
|
|
break;
|
|
|
|
case ASIOSTInt32LSB:
|
|
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
|
|
{
|
|
// convert to 16 bit
|
|
vecsTmpAudioSndCrdStereo[2 * iCurSample + bufferInfos[i].channelNum] =
|
|
(((int*) bufferInfos[i].buffers[index])[iCurSample] >> 16);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// call processing callback function
|
|
pSound->Callback( vecsTmpAudioSndCrdStereo );
|
|
|
|
// perform the processing for input and output
|
|
for ( int i = 0; i < 2 * NUM_IN_OUT_CHANNELS; i++ ) // stereo
|
|
{
|
|
if ( bufferInfos[i].isInput != ASIOTrue )
|
|
{
|
|
// PLAYBACK ----------------------------------------------------
|
|
// copy data from sound card in output buffer (copy
|
|
// interleaved stereo data in mono sound card buffer)
|
|
switch ( channelInfos[i].type )
|
|
{
|
|
case ASIOSTInt16LSB:
|
|
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
|
|
{
|
|
( (short*) bufferInfos[i].buffers[index] )[iCurSample] =
|
|
vecsTmpAudioSndCrdStereo[2 * iCurSample + bufferInfos[i].channelNum];
|
|
}
|
|
break;
|
|
|
|
case ASIOSTInt24LSB:
|
|
|
|
// not yet tested, horrible things might happen with the following code ;-)
|
|
|
|
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
|
|
{
|
|
// convert current sample in 24 bit format
|
|
int iCurSam = static_cast<int> ( vecsTmpAudioSndCrdStereo[2 * iCurSample + bufferInfos[i].channelNum] );
|
|
iCurSam <<= 8;
|
|
|
|
memcpy ( ( (char*) bufferInfos[i].buffers[index] ) + iCurSample * 3, &iCurSam, 3 );
|
|
}
|
|
break;
|
|
|
|
case ASIOSTInt32LSB:
|
|
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
|
|
{
|
|
// convert to 32 bit
|
|
int iCurSam = static_cast<int> ( vecsTmpAudioSndCrdStereo[2 * iCurSample + bufferInfos[i].channelNum] );
|
|
( (int*) bufferInfos[i].buffers[index] )[iCurSample] = ( iCurSam << 16 );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// finally if the driver supports the ASIOOutputReady() optimization,
|
|
// do it here, all data are in place -----------------------------------
|
|
if ( bASIOPostOutput )
|
|
{
|
|
ASIOOutputReady();
|
|
}
|
|
}
|
|
ASIOMutex.unlock();
|
|
}
|
|
|
|
long CSound::asioMessages ( long selector, long value, void* message, double* opt )
|
|
{
|
|
long ret = 0;
|
|
switch(selector)
|
|
{
|
|
case kAsioEngineVersion:
|
|
// return the supported ASIO version of the host application
|
|
ret = 2L; // Host ASIO implementation version, 2 or higher
|
|
break;
|
|
|
|
// both messages might be send if the buffer size changes
|
|
case kAsioBufferSizeChange:
|
|
case kAsioResetRequest:
|
|
|
|
// TODO reinit sound interface and check for new buffer size
|
|
// requires changes in client class, too
|
|
|
|
|
|
ret = 0;//1L; // 1L if request is accepted or 0 otherwise
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|