jamulus/windows/sound.cpp

1194 lines
46 KiB
C++
Executable file

/******************************************************************************\
* Copyright (c) 2004-2020
*
* 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 );
// pointer to our sound object
CSound* pSound;
/******************************************************************************\
* Common *
\******************************************************************************/
QString CSound::LoadAndInitializeDriver ( int iDriverIdx,
bool bOpenDriverSetup )
{
// load driver
loadAsioDriver ( cDriverNames[iDriverIdx] );
if ( ASIOInit ( &driverInfo ) != ASE_OK )
{
// clean up and return error string
asioDrivers->removeCurrentDriver();
return tr ( "The audio driver could not be initialized." );
}
// check device capabilities if it fullfills our requirements
const QString strStat = CheckDeviceCapabilities();
// check if device is capable
if ( strStat.isEmpty() )
{
// the device has changed, per definition we reset the channel
// mapping to the defaults (first two available channels)
ResetChannelMapping();
// store ID of selected driver if initialization was successful
lCurDev = iDriverIdx;
}
else
{
// if requested, open ASIO driver setup in case of an error
if ( bOpenDriverSetup )
{
OpenDriverSetup();
QMessageBox::question ( nullptr, APP_NAME, "Are you done with your ASIO driver settings of device " + GetDeviceName ( iDriverIdx ) + "?", QMessageBox::Yes );
}
// driver cannot be used, clean up
asioDrivers->removeCurrentDriver();
}
return strStat;
}
void CSound::UnloadCurrentDriver()
{
// clean up ASIO stuff
ASIOStop();
ASIODisposeBuffers();
ASIOExit();
asioDrivers->removeCurrentDriver();
}
QString 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_HZ );
if ( ( CanSaRateReturn == ASE_NoClock ) ||
( CanSaRateReturn == ASE_NotPresent ) )
{
// return error string
return tr ( "The audio device does not support the "
"required sample rate. The required sample rate is: " ) +
QString().setNum ( SYSTEM_SAMPLE_RATE_HZ ) + " Hz";
}
// check if sample rate can be set
const ASIOError SetSaRateReturn = ASIOSetSampleRate ( SYSTEM_SAMPLE_RATE_HZ );
if ( ( SetSaRateReturn == ASE_NoClock ) ||
( SetSaRateReturn == ASE_InvalidMode ) ||
( SetSaRateReturn == ASE_NotPresent ) )
{
// return error string
return tr ( "The audio device does not support to set the required sampling "
"rate. This error can happen if you have an audio interface like the "
"Roland UA-25EX where you set the sample rate with a hardware switch "
"on the audio device. If this is the case, please change the sample rate "
"to " ) + QString().setNum ( SYSTEM_SAMPLE_RATE_HZ ) + tr ( " Hz on the "
"device and restart the " ) + APP_NAME + tr ( " software." );
}
// check the number of available channels
ASIOGetChannels ( &lNumInChan, &lNumOutChan );
if ( ( lNumInChan < NUM_IN_OUT_CHANNELS ) ||
( lNumOutChan < NUM_IN_OUT_CHANNELS ) )
{
// return error string
return tr ( "The audio device does not support the "
"required number of channels. The required number of channels "
"for input and output is: " ) +
QString().setNum ( NUM_IN_OUT_CHANNELS );
}
// clip number of input/output channels to our maximum
if ( lNumInChan > MAX_NUM_IN_OUT_CHANNELS )
{
lNumInChan = MAX_NUM_IN_OUT_CHANNELS;
}
if ( lNumOutChan > MAX_NUM_IN_OUT_CHANNELS )
{
lNumOutChan = MAX_NUM_IN_OUT_CHANNELS;
}
// query channel infos for all available input channels
bool bInputChMixingSupported = true;
for ( int i = 0; i < lNumInChan; i++ )
{
// setup for input channels
channelInfosInput[i].isInput = ASIOTrue;
channelInfosInput[i].channel = i;
ASIOGetChannelInfo ( &channelInfosInput[i] );
// Check supported sample formats.
// Actually, it would be enough to have at least two channels which
// support the required sample format. But since we have support for
// all known sample types, the following check should always pass and
// therefore we throw the error message on any channel which does not
// fullfill the sample format requirement (quick hack solution).
if ( !CheckSampleTypeSupported ( channelInfosInput[i].type ) )
{
// return error string
return tr ( "Required audio sample format not available." );
}
// store the name of the channel and check if channel mixing is supported
channelInputName[i] = channelInfosInput[i].name;
if ( !CheckSampleTypeSupportedForCHMixing ( channelInfosInput[i].type ) )
{
bInputChMixingSupported = false;
}
}
// query channel infos for all available output channels
for ( int i = 0; i < lNumOutChan; i++ )
{
// setup for output channels
channelInfosOutput[i].isInput = ASIOFalse;
channelInfosOutput[i].channel = i;
ASIOGetChannelInfo ( &channelInfosOutput[i] );
// Check supported sample formats.
// Actually, it would be enough to have at least two channels which
// support the required sample format. But since we have support for
// all known sample types, the following check should always pass and
// therefore we throw the error message on any channel which does not
// fullfill the sample format requirement (quick hack solution).
if ( !CheckSampleTypeSupported ( channelInfosOutput[i].type ) )
{
// return error string
return tr ( "Required audio sample format not available." );
}
}
// special case with 4 input channels: support adding channels
if ( ( lNumInChan == 4 ) && bInputChMixingSupported )
{
// add four mixed channels (i.e. 4 normal, 4 mixed channels)
lNumInChanPlusAddChan = 8;
for ( int iCh = 0; iCh < lNumInChanPlusAddChan; iCh++ )
{
int iSelCH, iSelAddCH;
GetSelCHAndAddCH ( iCh, lNumInChan, iSelCH, iSelAddCH );
if ( iSelAddCH >= 0 )
{
// for mixed channels, show both audio channel names to be mixed
channelInputName[iCh] =
channelInputName[iSelCH] + " + " + channelInputName[iSelAddCH];
}
}
}
else
{
// regular case: no mixing input channels used
lNumInChanPlusAddChan = lNumInChan;
}
// everything is ok, return empty string for "no error" case
return "";
}
void CSound::SetLeftInputChannel ( const int iNewChan )
{
// apply parameter after input parameter check
if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) )
{
vSelectedInputChannels[0] = iNewChan;
}
}
void CSound::SetRightInputChannel ( const int iNewChan )
{
// apply parameter after input parameter check
if ( ( iNewChan >= 0 ) && ( iNewChan < lNumInChanPlusAddChan ) )
{
vSelectedInputChannels[1] = iNewChan;
}
}
void CSound::SetLeftOutputChannel ( const int iNewChan )
{
// apply parameter after input parameter check
if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) )
{
vSelectedOutputChannels[0] = iNewChan;
}
}
void CSound::SetRightOutputChannel ( const int iNewChan )
{
// apply parameter after input parameter check
if ( ( iNewChan >= 0 ) && ( iNewChan < lNumOutChan ) )
{
vSelectedOutputChannels[1] = iNewChan;
}
}
int CSound::GetActualBufferSize ( const int iDesiredBufferSizeMono )
{
int iActualBufferSizeMono;
// query the usable buffer sizes
ASIOGetBufferSize ( &HWBufferInfo.lMinSize,
&HWBufferInfo.lMaxSize,
&HWBufferInfo.lPreferredSize,
&HWBufferInfo.lGranularity );
/*
// TEST
#include <QMessageBox>
QMessageBox::information ( 0, "APP_NAME", QString("lMinSize: %1, lMaxSize: %2, lPreferredSize: %3, lGranularity: %4").
arg(HWBufferInfo.lMinSize).arg(HWBufferInfo.lMaxSize).arg(HWBufferInfo.lPreferredSize).arg(HWBufferInfo.lGranularity) );
_exit(1);
*/
// TODO see https://github.com/EddieRingle/portaudio/blob/master/src/hostapi/asio/pa_asio.cpp#L1654 (SelectHostBufferSizeForUnspecifiedUserFramesPerBuffer)
// 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
{
if ( ( HWBufferInfo.lGranularity < -1 ) ||
( HWBufferInfo.lGranularity == 0 ) )
{
// Special case (seen for EMU audio cards): granularity is
// zero or less than zero (make sure to exclude the special
// case of -1).
// There is no definition of this case in the ASIO SDK
// document. We assume here that all buffer sizes in between
// minimum and maximum buffer sizes are allowed.
iActualBufferSizeMono = iDesiredBufferSizeMono;
}
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;
}
}
}
// clip trial buffer size (it may happen in the while
// routine that "iTrialBufSize" is larger than "lMaxSize" in
// case "lMaxSize - lMinSize" is not divisible by the
// granularity)
if ( iTrialBufSize > HWBufferInfo.lMaxSize )
{
iTrialBufSize = HWBufferInfo.lMaxSize;
}
// 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 class
CSoundBase::Init ( iASIOBufferSizeMono );
// set internal buffer size value and calculate stereo buffer size
iASIOBufferSizeStereo = 2 * iASIOBufferSizeMono;
// set the sample rate
ASIOSetSampleRate ( SYSTEM_SAMPLE_RATE_HZ );
// create memory for intermediate audio buffer
vecsMultChanAudioSndCrd.Init ( iASIOBufferSizeStereo );
// create and activate ASIO buffers (buffer size in samples),
// dispose old buffers (if any)
ASIODisposeBuffers();
// prepare input channels
for ( int i = 0; i < lNumInChan; i++ )
{
bufferInfos[i].isInput = ASIOTrue;
bufferInfos[i].channelNum = i;
bufferInfos[i].buffers[0] = 0;
bufferInfos[i].buffers[1] = 0;
}
// prepare output channels
for ( int i = 0; i < lNumOutChan; i++ )
{
bufferInfos[lNumInChan + i].isInput = ASIOFalse;
bufferInfos[lNumInChan + i].channelNum = i;
bufferInfos[lNumInChan + i].buffers[0] = 0;
bufferInfos[lNumInChan + i].buffers[1] = 0;
}
ASIOCreateBuffers ( bufferInfos, lNumInChan + lNumOutChan,
iASIOBufferSizeMono, &asioCallbacks );
// query the latency of the driver
long lInputLatency = 0;
long lOutputLatency = 0;
if ( ASIOGetLatencies ( &lInputLatency, &lOutputLatency ) != ASE_NotPresent )
{
// add the input and output latencies (returned in number of
// samples) and calculate the time in ms
dInOutLatencyMs =
( static_cast<double> ( lInputLatency ) + lOutputLatency ) *
1000 / SYSTEM_SAMPLE_RATE_HZ;
}
else
{
// no latency available
dInOutLatencyMs = 0.0;
}
// 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();
// make sure the working thread is actually done
// (by checking the locked state)
if ( ASIOMutex.tryLock ( 5000 ) )
{
ASIOMutex.unlock();
}
}
CSound::CSound ( void (*fpNewCallback) ( CVector<int16_t>& psData, void* arg ),
void* arg,
const int iCtrlMIDIChannel,
const bool bNoAutoJackConnect) :
CSoundBase ( "ASIO", true, fpNewCallback, arg, iCtrlMIDIChannel, bNoAutoJackConnect ),
lNumInChan ( 0 ),
lNumInChanPlusAddChan ( 0 ),
lNumOutChan ( 0 ),
dInOutLatencyMs ( 0.0 ), // "0.0" means that no latency value is available
vSelectedInputChannels ( NUM_IN_OUT_CHANNELS ),
vSelectedOutputChannels ( NUM_IN_OUT_CHANNELS )
{
int i;
// init pointer to our sound object
pSound = this;
// get available ASIO driver names in system
for ( i = 0; i < MAX_NUMBER_SOUND_CARDS; i++ )
{
// allocate memory for driver names
cDriverNames[i] = new char[32];
}
char cDummyName[] = "dummy";
loadAsioDriver ( cDummyName ); // 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 ( tr ( "<b>No ASIO audio device (driver) found.</b><br><br>"
"The " ) + APP_NAME + tr ( " software requires the low latency audio "
"interface <b>ASIO</b> to work properly. This is no standard "
"Windows audio interface and therefore a special audio driver is "
"required. Either your sound card has a native ASIO driver (which "
"is recommended) or you might want to use alternative drivers like "
"the ASIO4All driver." ) );
}
asioDrivers->removeCurrentDriver();
// copy driver names to base class but internally we still have to use
// the char* variable because of the ASIO API :-(
for ( i = 0; i < lNumDevs; i++ )
{
strDriverNames[i] = cDriverNames[i];
}
// init device index as not initialized (invalid)
lCurDev = INVALID_SNC_CARD_DEVICE;
// init channel mapping
ResetChannelMapping();
// set up the asioCallback structure
asioCallbacks.bufferSwitch = &bufferSwitch;
asioCallbacks.sampleRateDidChange = &sampleRateChanged;
asioCallbacks.asioMessage = &asioMessages;
asioCallbacks.bufferSwitchTimeInfo = &bufferSwitchTimeInfo;
}
void CSound::ResetChannelMapping()
{
// init selected channel numbers with defaults: use first available
// channels for input and output
vSelectedInputChannels[0] = 0;
vSelectedInputChannels[1] = 1;
vSelectedOutputChannels[0] = 0;
vSelectedOutputChannels[1] = 1;
}
// ASIO callbacks -------------------------------------------------------------
ASIOTime* CSound::bufferSwitchTimeInfo ( ASIOTime*,
long index,
ASIOBool processNow )
{
bufferSwitch ( index, processNow );
return 0L;
}
bool CSound::CheckSampleTypeSupported ( const ASIOSampleType SamType )
{
// check for supported sample types
return ( ( SamType == ASIOSTInt16LSB ) ||
( SamType == ASIOSTInt24LSB ) ||
( SamType == ASIOSTInt32LSB ) ||
( SamType == ASIOSTFloat32LSB ) ||
( SamType == ASIOSTFloat64LSB ) ||
( SamType == ASIOSTInt32LSB16 ) ||
( SamType == ASIOSTInt32LSB18 ) ||
( SamType == ASIOSTInt32LSB20 ) ||
( SamType == ASIOSTInt32LSB24 ) ||
( SamType == ASIOSTInt16MSB ) ||
( SamType == ASIOSTInt24MSB ) ||
( SamType == ASIOSTInt32MSB ) ||
( SamType == ASIOSTFloat32MSB ) ||
( SamType == ASIOSTFloat64MSB ) ||
( SamType == ASIOSTInt32MSB16 ) ||
( SamType == ASIOSTInt32MSB18 ) ||
( SamType == ASIOSTInt32MSB20 ) ||
( SamType == ASIOSTInt32MSB24 ) );
}
bool CSound::CheckSampleTypeSupportedForCHMixing ( const ASIOSampleType SamType )
{
// check for supported sample types for audio channel mixing (see bufferSwitch)
return ( ( SamType == ASIOSTInt16LSB ) ||
( SamType == ASIOSTInt24LSB ) ||
( SamType == ASIOSTInt32LSB ) );
}
void CSound::bufferSwitch ( long index, ASIOBool )
{
int iCurSample;
// get references to class members
int& iASIOBufferSizeMono = pSound->iASIOBufferSizeMono;
CVector<int16_t>& vecsMultChanAudioSndCrd = pSound->vecsMultChanAudioSndCrd;
// perform the processing for input and output
pSound->ASIOMutex.lock(); // get mutex lock
{
// CAPTURE -------------------------------------------------------------
for ( int i = 0; i < NUM_IN_OUT_CHANNELS; i++ )
{
int iSelCH, iSelAddCH;
GetSelCHAndAddCH ( pSound->vSelectedInputChannels[i], pSound->lNumInChan,
iSelCH, iSelAddCH );
// copy new captured block in thread transfer buffer (copy
// mono data interleaved in stereo buffer)
switch ( pSound->channelInfosInput[iSelCH].type )
{
case ASIOSTInt16LSB:
{
// no type conversion required, just copy operation
int16_t* pASIOBuf = static_cast<int16_t*> ( pSound->bufferInfos[iSelCH].buffers[index] );
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] = pASIOBuf[iCurSample];
}
if ( iSelAddCH >= 0 )
{
// mix input channels case:
int16_t* pASIOBufAdd = static_cast<int16_t*> ( pSound->bufferInfos[iSelAddCH].buffers[index] );
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
Double2Short ( (double) vecsMultChanAudioSndCrd[2 * iCurSample + i] +
(double) pASIOBufAdd[iCurSample] );
}
}
break;
}
case ASIOSTInt24LSB:
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
int iCurSam = 0;
memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, 3 );
iCurSam >>= 8;
vecsMultChanAudioSndCrd[2 * iCurSample + i] = static_cast<int16_t> ( iCurSam );
}
if ( iSelAddCH >= 0 )
{
// mix input channels case:
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
int iCurSam = 0;
memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelAddCH].buffers[index] ) + iCurSample * 3, 3 );
iCurSam >>= 8;
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
Double2Short ( (double) vecsMultChanAudioSndCrd[2 * iCurSample + i] +
(double) static_cast<int16_t> ( iCurSam ) );
}
}
break;
case ASIOSTInt32LSB:
{
int32_t* pASIOBuf = static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] );
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( pASIOBuf[iCurSample] >> 16 );
}
if ( iSelAddCH >= 0 )
{
// mix input channels case:
int32_t* pASIOBufAdd = static_cast<int32_t*> ( pSound->bufferInfos[iSelAddCH].buffers[index] );
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
Double2Short ( (double) vecsMultChanAudioSndCrd[2 * iCurSample + i] +
(double) static_cast<int16_t> ( pASIOBufAdd[iCurSample] >> 16 ) );
}
}
break;
}
case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( static_cast<float*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] * _MAXSHORT );
}
break;
case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( static_cast<double*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] * _MAXSHORT );
}
break;
case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFF );
}
break;
case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0x3FFFF ) >> 2 );
}
break;
case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFFF ) >> 4 );
}
break;
case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] & 0xFFFFFF ) >> 8 );
}
break;
case ASIOSTInt16MSB:
// NOT YET TESTED
// flip bits
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
Flip16Bits ( ( static_cast<int16_t*> (
pSound->bufferInfos[iSelCH].buffers[index] ) )[iCurSample] );
}
break;
case ASIOSTInt24MSB:
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// because the bits are flipped, we do not have to perform the
// shift by 8 bits
int iCurSam = 0;
memcpy ( &iCurSam, ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, 3 );
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
Flip16Bits ( static_cast<int16_t> ( iCurSam ) );
}
break;
case ASIOSTInt32MSB:
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// flip bits and convert to 16 bit
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( Flip32Bits ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) >> 16 );
}
break;
case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( static_cast<float> (
Flip32Bits ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) ) * _MAXSHORT );
}
break;
case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( static_cast<double> (
Flip64Bits ( static_cast<int64_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) ) * _MAXSHORT );
}
break;
case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( Flip32Bits ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFF );
}
break;
case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( ( Flip32Bits ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0x3FFFF ) >> 2 );
}
break;
case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( ( Flip32Bits ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFFF ) >> 4 );
}
break;
case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
vecsMultChanAudioSndCrd[2 * iCurSample + i] =
static_cast<int16_t> ( ( Flip32Bits ( static_cast<int32_t*> (
pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] ) & 0xFFFFFF ) >> 8 );
}
break;
}
}
// call processing callback function
pSound->ProcessCallback ( vecsMultChanAudioSndCrd );
// PLAYBACK ------------------------------------------------------------
for ( int i = 0; i < NUM_IN_OUT_CHANNELS; i++ )
{
const int iSelCH = pSound->lNumInChan + pSound->vSelectedOutputChannels[i];
// copy data from sound card in output buffer (copy
// interleaved stereo data in mono sound card buffer)
switch ( pSound->channelInfosOutput[pSound->vSelectedOutputChannels[i]].type )
{
case ASIOSTInt16LSB:
{
// no type conversion required, just copy operation
int16_t* pASIOBuf = static_cast<int16_t*> ( pSound->bufferInfos[iSelCH].buffers[index] );
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
pASIOBuf[iCurSample] = vecsMultChanAudioSndCrd[2 * iCurSample + i];
}
break;
}
case ASIOSTInt24LSB:
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert current sample in 24 bit format
int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
iCurSam <<= 8;
memcpy ( ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, &iCurSam, 3 );
}
break;
case ASIOSTInt32LSB:
{
int32_t* pASIOBuf = static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] );
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit
const int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
pASIOBuf[iCurSample] = ( iCurSam << 16 );
}
break;
}
case ASIOSTFloat32LSB: // IEEE 754 32 bit float, as found on Intel x86 architecture
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
const float fCurSam = static_cast<float> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<float*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
fCurSam / _MAXSHORT;
}
break;
case ASIOSTFloat64LSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
const double fCurSam = static_cast<double> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<double*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
fCurSam / _MAXSHORT;
}
break;
case ASIOSTInt32LSB16: // 32 bit data with 16 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit
const int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
iCurSam;
}
break;
case ASIOSTInt32LSB18: // 32 bit data with 18 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit
const int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
( iCurSam << 2 );
}
break;
case ASIOSTInt32LSB20: // 32 bit data with 20 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit
const int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
( iCurSam << 4 );
}
break;
case ASIOSTInt32LSB24: // 32 bit data with 24 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit
const int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
( iCurSam << 8 );
}
break;
case ASIOSTInt16MSB:
// NOT YET TESTED
// flip bits
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
( (int16_t*) pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
Flip16Bits ( vecsMultChanAudioSndCrd[2 * iCurSample + i] );
}
break;
case ASIOSTInt24MSB:
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// because the bits are flipped, we do not have to perform the
// shift by 8 bits
int32_t iCurSam = static_cast<int32_t> ( Flip16Bits (
vecsMultChanAudioSndCrd[2 * iCurSample + i] ) );
memcpy ( ( (char*) pSound->bufferInfos[iSelCH].buffers[index] ) + iCurSample * 3, &iCurSam, 3 );
}
break;
case ASIOSTInt32MSB:
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit and flip bits
int iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
Flip32Bits ( iCurSam << 16 );
}
break;
case ASIOSTFloat32MSB: // IEEE 754 32 bit float, as found on Intel x86 architecture
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
const float fCurSam = static_cast<float> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<float*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
static_cast<float> ( Flip32Bits ( static_cast<int32_t> (
fCurSam / _MAXSHORT ) ) );
}
break;
case ASIOSTFloat64MSB: // IEEE 754 64 bit double float, as found on Intel x86 architecture
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
const double fCurSam = static_cast<double> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<float*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
static_cast<double> ( Flip64Bits ( static_cast<int64_t> (
fCurSam / _MAXSHORT ) ) );
}
break;
case ASIOSTInt32MSB16: // 32 bit data with 16 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit
const int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
Flip32Bits ( iCurSam );
}
break;
case ASIOSTInt32MSB18: // 32 bit data with 18 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit
const int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
Flip32Bits ( iCurSam << 2 );
}
break;
case ASIOSTInt32MSB20: // 32 bit data with 20 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit
const int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
Flip32Bits ( iCurSam << 4 );
}
break;
case ASIOSTInt32MSB24: // 32 bit data with 24 bit alignment
// NOT YET TESTED
for ( iCurSample = 0; iCurSample < iASIOBufferSizeMono; iCurSample++ )
{
// convert to 32 bit
const int32_t iCurSam = static_cast<int32_t> (
vecsMultChanAudioSndCrd[2 * iCurSample + i] );
static_cast<int32_t*> ( pSound->bufferInfos[iSelCH].buffers[index] )[iCurSample] =
Flip32Bits ( iCurSam << 8 );
}
break;
}
}
// Finally if the driver supports the ASIOOutputReady() optimization,
// do it here, all data are in place -----------------------------------
if ( pSound->bASIOPostOutput )
{
ASIOOutputReady();
}
}
pSound->ASIOMutex.unlock();
}
long CSound::asioMessages ( long selector,
long,
void*,
double* )
{
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:
pSound->EmitReinitRequestSignal ( RS_ONLY_RESTART_AND_INIT );
ret = 1L; // 1L if request is accepted or 0 otherwise
break;
case kAsioResetRequest:
pSound->EmitReinitRequestSignal ( RS_RELOAD_RESTART_AND_INIT );
ret = 1L; // 1L if request is accepted or 0 otherwise
break;
}
return ret;
}
int16_t CSound::Flip16Bits ( const int16_t iIn )
{
uint16_t iMask = ( 1 << 15 );
int16_t iOut = 0;
for ( unsigned int i = 0; i < 16; i++ )
{
// copy current bit to correct position
iOut |= ( iIn & iMask ) ? 1 : 0;
// shift out value and mask by one bit
iOut <<= 1;
iMask >>= 1;
}
return iOut;
}
int32_t CSound::Flip32Bits ( const int32_t iIn )
{
uint32_t iMask = ( static_cast<uint32_t> ( 1 ) << 31 );
int32_t iOut = 0;
for ( unsigned int i = 0; i < 32; i++ )
{
// copy current bit to correct position
iOut |= ( iIn & iMask ) ? 1 : 0;
// shift out value and mask by one bit
iOut <<= 1;
iMask >>= 1;
}
return iOut;
}
int64_t CSound::Flip64Bits ( const int64_t iIn )
{
uint64_t iMask = ( static_cast<uint64_t> ( 1 ) << 63 );
int64_t iOut = 0;
for ( unsigned int i = 0; i < 64; i++ )
{
// copy current bit to correct position
iOut |= ( iIn & iMask ) ? 1 : 0;
// shift out value and mask by one bit
iOut <<= 1;
iMask >>= 1;
}
return iOut;
}