From e9820ddcb0bcb7a09f5341428c88912d1cbc701f Mon Sep 17 00:00:00 2001 From: Volker Fischer Date: Tue, 1 Dec 2009 08:52:06 +0000 Subject: [PATCH] added more information on error message --- src/clientsettingsdlg.cpp | 7 +- windows/sound.cpp | 1186 +++++++++++++++++++------------------ 2 files changed, 599 insertions(+), 594 deletions(-) diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index 3f8edc31..4db4633f 100755 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -373,9 +373,10 @@ void CClientSettingsDlg::OnSoundCrdSelection ( int iSndDevIdx ) if ( !strError.isEmpty() ) { QMessageBox::critical ( this, APP_NAME, - QString ( "The selected audio device could not be used because " - "of the following error: " ) + strError + - QString ( " The previous driver will be selected." ), "Ok", 0 ); + QString ( tr ( "The selected audio device could not be used " + "because of the following error: " ) ) + strError + + QString ( tr ( " The previous driver will be selected." ) ), + "Ok", 0 ); // recover old selection cbSoundcard->setCurrentIndex ( pClient->GetSndCrdDev() ); diff --git a/windows/sound.cpp b/windows/sound.cpp index a186c4c4..8ce84f32 100755 --- a/windows/sound.cpp +++ b/windows/sound.cpp @@ -1,592 +1,596 @@ -/******************************************************************************\ - * 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 vecsTmpAudioSndCrdStereo; - -QMutex ASIOMutex; - -// TEST -CSound* pSound; - - -/******************************************************************************\ -* Common * -\******************************************************************************/ -std::string CSound::SetDev ( const int iNewDev ) -{ - std::string strReturn = ""; // init with no error - - // 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() ) - { - if ( iNewDev != lCurDev ) - { - // loading and initializing the new driver failed, go back to - // original driver and display error message - LoadAndInitializeDriver ( lCurDev ); - } - else - { - // the same driver is used but the driver properties seems to - // have changed so that they are not compatible to our - // software anymore +/******************************************************************************\ + * 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 vecsTmpAudioSndCrdStereo; + +QMutex ASIOMutex; + +// TEST +CSound* pSound; + + +/******************************************************************************\ +* Common * +\******************************************************************************/ +std::string CSound::SetDev ( const int iNewDev ) +{ + std::string strReturn = ""; // init with no error + + // 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() ) + { + if ( iNewDev != lCurDev ) + { + // loading and initializing the new driver failed, go back to + // original driver and display error message + LoadAndInitializeDriver ( lCurDev ); + } + else + { + // the same driver is used but the driver properties seems to + // have changed so that they are not compatible to our + // software anymore QMessageBox::critical ( - 0, APP_NAME, "The audio driver properties have changed to " - "a state which is incompatible to this software. " - "Please restart the software.", "Close", 0 ); - - _exit ( 0 ); - } - - // store error return message - strReturn = strErrorMessage; - } - - 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." ); - } - } - } - - return strReturn; -} - -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 ( SYSTEM_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 - { - // ASIO SDK 2.2: "Notes: When minimum and maximum buffer size are - // equal, the preferred buffer size has to be the same value as - // well; granularity should be 0 in this case." - if ( HWBufferInfo.lMinSize == HWBufferInfo.lMaxSize ) - { - iActualBufferSizeMono = HWBufferInfo.lMinSize; - } - else - { - // General case ------------------------------------------------ - // 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 ( SYSTEM_SAMPLE_RATE ); - - // create memory for intermediate audio buffer - vecsTmpAudioSndCrdStereo.Init ( iASIOBufferSizeStereo ); - - // create and activate ASIO buffers (buffer size in samples), - // dispose old buffers (if any) - ASIODisposeBuffers(); - 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& 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 ( 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->ProcessCallback ( 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 ( 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 ( 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: - pSound->EmitReinitRequestSignal(); - ret = 1L; // 1L if request is accepted or 0 otherwise - break; - } - return ret; -} + 0, APP_NAME, QString ( tr ( "The audio driver properties " + "have changed to a state which is incompatible to this " + "software. The selected audio device could not be used " + "because of the following error: " ) ) + + strErrorMessage.c_str() + + QString ( tr ( "

Please restart the software." ) ), + "Close", 0 ); + + _exit ( 0 ); + } + + // store error return message + strReturn = strErrorMessage; + } + + 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." ); + } + } + } + + return strReturn; +} + +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 ( SYSTEM_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 + { + // ASIO SDK 2.2: "Notes: When minimum and maximum buffer size are + // equal, the preferred buffer size has to be the same value as + // well; granularity should be 0 in this case." + if ( HWBufferInfo.lMinSize == HWBufferInfo.lMaxSize ) + { + iActualBufferSizeMono = HWBufferInfo.lMinSize; + } + else + { + // General case ------------------------------------------------ + // 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 ( SYSTEM_SAMPLE_RATE ); + + // create memory for intermediate audio buffer + vecsTmpAudioSndCrdStereo.Init ( iASIOBufferSizeStereo ); + + // create and activate ASIO buffers (buffer size in samples), + // dispose old buffers (if any) + ASIODisposeBuffers(); + 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& 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 ( 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->ProcessCallback ( 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 ( 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 ( 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: + pSound->EmitReinitRequestSignal(); + ret = 1L; // 1L if request is accepted or 0 otherwise + break; + } + return ret; +}