You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
511 lines
14 KiB
511 lines
14 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#define WIN32_LEAN_AND_MEAN |
|
#include <windows.h> |
|
#include <mmsystem.h> |
|
#include <mmreg.h> |
|
#include "snd_wave_source.h" |
|
#include "snd_wave_mixer_adpcm.h" |
|
#include "snd_wave_mixer_private.h" |
|
|
|
// max size of ADPCM block in bytes |
|
#define MAX_BLOCK_SIZE 4096 |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Mixer for ADPCM encoded audio |
|
//----------------------------------------------------------------------------- |
|
class CAudioMixerWaveADPCM : public CAudioMixerWave |
|
{ |
|
public: |
|
CAudioMixerWaveADPCM( CWaveData *data ); |
|
~CAudioMixerWaveADPCM( void ); |
|
|
|
virtual void Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress, bool forward = true ); |
|
virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward = true ); |
|
|
|
virtual bool SetSamplePosition( int position ); |
|
|
|
private: |
|
bool DecodeBlock( void ); |
|
int NumChannels( void ); |
|
void DecompressBlockMono( short *pOut, const char *pIn, int count ); |
|
void DecompressBlockStereo( short *pOut, const char *pIn, int count ); |
|
|
|
void SetCurrentBlock( int block ); |
|
int GetCurrentBlock( void ) const; |
|
int GetBlockNumberForSample( int samplePosition ); |
|
bool IsSampleInCurrentBlock( int samplePosition ); |
|
int GetFirstSampleForBlock( int blocknum ) const; |
|
|
|
const ADPCMWAVEFORMAT *m_pFormat; |
|
const ADPCMCOEFSET *m_pCoefficients; |
|
|
|
short *m_pSamples; |
|
int m_sampleCount; |
|
int m_samplePosition; |
|
|
|
int m_blockSize; |
|
int m_offset; |
|
|
|
int m_currentBlock; |
|
}; |
|
|
|
|
|
CAudioMixerWaveADPCM::CAudioMixerWaveADPCM( CWaveData *data ) : CAudioMixerWave( data ) |
|
{ |
|
m_currentBlock = -1; |
|
m_pSamples = NULL; |
|
m_sampleCount = 0; |
|
m_samplePosition = 0; |
|
m_offset = 0; |
|
|
|
m_pFormat = (const ADPCMWAVEFORMAT *)m_pData->Source().GetHeader(); |
|
if ( m_pFormat ) |
|
{ |
|
m_pCoefficients = (ADPCMCOEFSET *)((char *)m_pFormat + sizeof(WAVEFORMATEX) + 4); |
|
|
|
// create the decode buffer |
|
m_pSamples = new short[m_pFormat->wSamplesPerBlock * m_pFormat->wfx.nChannels]; |
|
|
|
// number of bytes for samples |
|
m_blockSize = ((m_pFormat->wSamplesPerBlock - 2) * m_pFormat->wfx.nChannels ) / 2; |
|
// size of channel header |
|
m_blockSize += 7 * m_pFormat->wfx.nChannels; |
|
// Assert(m_blockSize < MAX_BLOCK_SIZE); |
|
} |
|
} |
|
|
|
|
|
CAudioMixerWaveADPCM::~CAudioMixerWaveADPCM( void ) |
|
{ |
|
delete[] m_pSamples; |
|
} |
|
|
|
|
|
int CAudioMixerWaveADPCM::NumChannels( void ) |
|
{ |
|
if ( m_pFormat ) |
|
{ |
|
return m_pFormat->wfx.nChannels; |
|
} |
|
return 0; |
|
} |
|
|
|
void CAudioMixerWaveADPCM::Mix( IAudioDevice *pDevice, channel_t *pChannel, void *pData, int outputOffset, int inputOffset, fixedint fracRate, int outCount, int timecompress, bool forward /*= true*/ ) |
|
{ |
|
if ( NumChannels() == 1 ) |
|
pDevice->Mix16Mono( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress, forward ); |
|
else |
|
pDevice->Mix16Stereo( pChannel, (short *)pData, outputOffset, inputOffset, fracRate, outCount, timecompress, forward ); |
|
} |
|
|
|
|
|
static int error_sign_lut[] = { 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1 }; |
|
static int error_coefficients_lut[] = { 230, 230, 230, 230, 307, 409, 512, 614, |
|
768, 614, 512, 409, 307, 230, 230, 230 }; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: ADPCM decompress a single block of 1-channel audio |
|
// Input : *pOut - output buffer 16-bit |
|
// *pIn - input block |
|
// count - number of samples to decode (to support partial blocks) |
|
//----------------------------------------------------------------------------- |
|
void CAudioMixerWaveADPCM::DecompressBlockMono( short *pOut, const char *pIn, int count ) |
|
{ |
|
|
|
int pred = *pIn++; |
|
int co1 = m_pCoefficients[pred].iCoef1; |
|
int co2 = m_pCoefficients[pred].iCoef2; |
|
|
|
// read initial delta |
|
int delta = *((short *)pIn); |
|
pIn += 2; |
|
|
|
// read initial samples for prediction |
|
int samp1 = *((short *)pIn); |
|
pIn += 2; |
|
|
|
int samp2 = *((short *)pIn); |
|
pIn += 2; |
|
|
|
// write out the initial samples (stored in reverse order) |
|
*pOut++ = (short)samp2; |
|
*pOut++ = (short)samp1; |
|
|
|
// subtract the 2 samples in the header |
|
count -= 2; |
|
|
|
// this is a toggle to read nibbles, first nibble is high |
|
int high = 1; |
|
|
|
int error = 0, sample = 0; |
|
|
|
// now process the block |
|
while ( count ) |
|
{ |
|
// read the error nibble from the input stream |
|
if ( high ) |
|
{ |
|
sample = (unsigned char) (*pIn++); |
|
// high nibble |
|
error = sample >> 4; |
|
// cache low nibble for next read |
|
sample = sample & 0xf; |
|
// Next read is from cache, not stream |
|
high = 0; |
|
} |
|
else |
|
{ |
|
// stored in previous read (low nibble) |
|
error = sample; |
|
// next read is from stream |
|
high = 1; |
|
} |
|
// convert to signed with LUT |
|
int errorSign = error_sign_lut[error]; |
|
|
|
// interpolate the new sample |
|
int predSample = (samp1 * co1) + (samp2 * co2); |
|
// coefficients are fixed point 8-bit, so shift back to 16-bit integer |
|
predSample >>= 8; |
|
|
|
// Add in current error estimate |
|
predSample += (errorSign * delta); |
|
|
|
// Correct error estimate |
|
delta = (delta * error_coefficients_lut[error]) >> 8; |
|
// Clamp error estimate |
|
if ( delta < 16 ) |
|
delta = 16; |
|
|
|
// clamp |
|
if ( predSample > 32767L ) |
|
predSample = 32767L; |
|
else if ( predSample < -32768L ) |
|
predSample = -32768L; |
|
|
|
// output |
|
*pOut++ = (short)predSample; |
|
// move samples over |
|
samp2 = samp1; |
|
samp1 = predSample; |
|
|
|
count--; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Decode a single block of stereo ADPCM audio |
|
// Input : *pOut - 16-bit output buffer |
|
// *pIn - ADPCM encoded block data |
|
// count - number of sample pairs to decode |
|
//----------------------------------------------------------------------------- |
|
void CAudioMixerWaveADPCM::DecompressBlockStereo( short *pOut, const char *pIn, int count ) |
|
{ |
|
int pred[2], co1[2], co2[2]; |
|
int i; |
|
|
|
for ( i = 0; i < 2; i++ ) |
|
{ |
|
pred[i] = *pIn++; |
|
co1[i] = m_pCoefficients[pred[i]].iCoef1; |
|
co2[i] = m_pCoefficients[pred[i]].iCoef2; |
|
} |
|
|
|
int delta[2], samp1[2], samp2[2]; |
|
|
|
for ( i = 0; i < 2; i++, pIn += 2 ) |
|
{ |
|
// read initial delta |
|
delta[i] = *((short *)pIn); |
|
} |
|
|
|
// read initial samples for prediction |
|
for ( i = 0; i < 2; i++, pIn += 2 ) |
|
{ |
|
samp1[i] = *((short *)pIn); |
|
} |
|
for ( i = 0; i < 2; i++, pIn += 2 ) |
|
{ |
|
samp2[i] = *((short *)pIn); |
|
} |
|
|
|
// write out the initial samples (stored in reverse order) |
|
*pOut++ = (short)samp2[0]; // left |
|
*pOut++ = (short)samp2[1]; // right |
|
*pOut++ = (short)samp1[0]; // left |
|
*pOut++ = (short)samp1[1]; // right |
|
|
|
// subtract the 2 samples in the header |
|
count -= 2; |
|
|
|
// this is a toggle to read nibbles, first nibble is high |
|
int high = 1; |
|
|
|
int error, sample = 0; |
|
|
|
// now process the block |
|
while ( count ) |
|
{ |
|
for ( i = 0; i < 2; i++ ) |
|
{ |
|
// read the error nibble from the input stream |
|
if ( high ) |
|
{ |
|
sample = (unsigned char) (*pIn++); |
|
// high nibble |
|
error = sample >> 4; |
|
// cache low nibble for next read |
|
sample = sample & 0xf; |
|
// Next read is from cache, not stream |
|
high = 0; |
|
} |
|
else |
|
{ |
|
// stored in previous read (low nibble) |
|
error = sample; |
|
// next read is from stream |
|
high = 1; |
|
} |
|
// convert to signed with LUT |
|
int errorSign = error_sign_lut[error]; |
|
|
|
// interpolate the new sample |
|
int predSample = (samp1[i] * co1[i]) + (samp2[i] * co2[i]); |
|
// coefficients are fixed point 8-bit, so shift back to 16-bit integer |
|
predSample >>= 8; |
|
|
|
// Add in current error estimate |
|
predSample += (errorSign * delta[i]); |
|
|
|
// Correct error estimate |
|
delta[i] = (delta[i] * error_coefficients_lut[error]) >> 8; |
|
// Clamp error estimate |
|
if ( delta[i] < 16 ) |
|
delta[i] = 16; |
|
|
|
// clamp |
|
if ( predSample > 32767L ) |
|
predSample = 32767L; |
|
else if ( predSample < -32768L ) |
|
predSample = -32768L; |
|
|
|
// output |
|
*pOut++ = (short)predSample; |
|
// move samples over |
|
samp2[i] = samp1[i]; |
|
samp1[i] = predSample; |
|
} |
|
count--; |
|
} |
|
} |
|
|
|
|
|
bool CAudioMixerWaveADPCM::DecodeBlock( void ) |
|
{ |
|
char tmpBlock[MAX_BLOCK_SIZE]; |
|
char *pData; |
|
|
|
int available = m_pData->ReadSourceData( (void **) (&pData), m_offset, m_blockSize ); |
|
if ( available < m_blockSize ) |
|
{ |
|
int total = 0; |
|
while ( available && total < m_blockSize ) |
|
{ |
|
memcpy( tmpBlock + total, pData, available ); |
|
total += available; |
|
available = m_pData->ReadSourceData( (void **) (&pData), m_offset + total, m_blockSize - total ); |
|
} |
|
pData = tmpBlock; |
|
available = total; |
|
} |
|
|
|
Assert( m_blockSize > 0 ); |
|
|
|
// Current block number is based on starting offset |
|
int blockNumber = m_offset / m_blockSize; |
|
SetCurrentBlock( blockNumber ); |
|
|
|
if ( !available ) |
|
{ |
|
return false; |
|
} |
|
|
|
// advance the file pointer |
|
m_offset += available; |
|
|
|
int channelCount = NumChannels(); |
|
|
|
// this is sample pairs for stereo, samples for mono |
|
m_sampleCount = m_pFormat->wSamplesPerBlock; |
|
|
|
// short block?, fixup sample count (2 samples per byte, divided by number of channels per sample set) |
|
m_sampleCount -= ((m_blockSize - available) * 2) / channelCount; |
|
|
|
// new block, start at the first sample |
|
m_samplePosition = 0; |
|
|
|
// no need to subclass for different channel counts... |
|
if ( channelCount == 1 ) |
|
{ |
|
DecompressBlockMono( m_pSamples, pData, m_sampleCount ); |
|
} |
|
else |
|
{ |
|
DecompressBlockStereo( m_pSamples, pData, m_sampleCount ); |
|
} |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : block - |
|
//----------------------------------------------------------------------------- |
|
void CAudioMixerWaveADPCM::SetCurrentBlock( int block ) |
|
{ |
|
m_currentBlock = block; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CAudioMixerWaveADPCM::GetCurrentBlock( void ) const |
|
{ |
|
return m_currentBlock; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : samplePosition - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CAudioMixerWaveADPCM::GetBlockNumberForSample( int samplePosition ) |
|
{ |
|
int blockNum = samplePosition / m_pFormat->wSamplesPerBlock; |
|
return blockNum; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : samplePosition - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CAudioMixerWaveADPCM::IsSampleInCurrentBlock( int samplePosition ) |
|
{ |
|
int currentBlock = GetCurrentBlock(); |
|
|
|
int startSample = currentBlock * m_pFormat->wSamplesPerBlock; |
|
int endSample = startSample + m_pFormat->wSamplesPerBlock - 1; |
|
|
|
if ( samplePosition >= startSample && |
|
samplePosition <= endSample ) |
|
{ |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : blocknum - |
|
// Output : int |
|
//----------------------------------------------------------------------------- |
|
int CAudioMixerWaveADPCM::GetFirstSampleForBlock( int blocknum ) const |
|
{ |
|
return m_pFormat->wSamplesPerBlock * blocknum; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Read existing buffer or decompress a new block when necessary |
|
// Input : **pData - output data pointer |
|
// sampleCount - number of samples (or pairs) |
|
// Output : int - available samples (zero to stop decoding) |
|
//----------------------------------------------------------------------------- |
|
int CAudioMixerWaveADPCM::GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward /*= true*/ ) |
|
{ |
|
int requestedBlock = GetBlockNumberForSample( samplePosition ); |
|
if ( requestedBlock != GetCurrentBlock() ) |
|
{ |
|
// Ran out of data!!! |
|
if ( !SetSamplePosition( samplePosition ) ) |
|
return 0; |
|
} |
|
|
|
Assert( requestedBlock == GetCurrentBlock() ); |
|
|
|
if ( m_samplePosition >= m_sampleCount ) |
|
{ |
|
if ( !DecodeBlock() ) |
|
return 0; |
|
} |
|
|
|
if ( m_samplePosition < m_sampleCount ) |
|
{ |
|
*pData = (void *)(m_pSamples + m_samplePosition * NumChannels()); |
|
int available = m_sampleCount - m_samplePosition; |
|
if ( available > sampleCount ) |
|
available = sampleCount; |
|
|
|
m_samplePosition += available; |
|
return available; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : position - |
|
//----------------------------------------------------------------------------- |
|
bool CAudioMixerWaveADPCM::SetSamplePosition( int position ) |
|
{ |
|
position = max( 0, position ); |
|
|
|
CAudioMixerWave::SetSamplePosition( position ); |
|
|
|
int requestedBlock = GetBlockNumberForSample( position ); |
|
int firstSample = GetFirstSampleForBlock( requestedBlock ); |
|
|
|
if ( firstSample >= m_pData->Source().SampleCount() ) |
|
{ |
|
// Read past end of file!!! |
|
return false; |
|
} |
|
|
|
int currentSample = ( position - firstSample ); |
|
|
|
if ( requestedBlock != GetCurrentBlock() ) |
|
{ |
|
// Rewind file to beginning of block |
|
m_offset = requestedBlock * m_blockSize; |
|
if ( !DecodeBlock() ) |
|
{ |
|
return false; |
|
} |
|
} |
|
|
|
m_samplePosition = currentSample; |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Abstract factory function for ADPCM mixers |
|
// Input : *data - wave data access object |
|
// channels - |
|
// Output : CAudioMixer |
|
//----------------------------------------------------------------------------- |
|
CAudioMixer *CreateADPCMMixer( CWaveData *data ) |
|
{ |
|
return new CAudioMixerWaveADPCM( data ); |
|
}
|
|
|