diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/hlfaceposer/snd_wave_mixer_adpcm.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/hlfaceposer/snd_wave_mixer_adpcm.cpp')
| -rw-r--r-- | utils/hlfaceposer/snd_wave_mixer_adpcm.cpp | 516 |
1 files changed, 516 insertions, 0 deletions
diff --git a/utils/hlfaceposer/snd_wave_mixer_adpcm.cpp b/utils/hlfaceposer/snd_wave_mixer_adpcm.cpp new file mode 100644 index 0000000..a76b66b --- /dev/null +++ b/utils/hlfaceposer/snd_wave_mixer_adpcm.cpp @@ -0,0 +1,516 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#define WIN32_LEAN_AND_MEAN +#include <windows.h> + +#pragma warning( disable: 4201 ) +#include <mmsystem.h> +#pragma warning( default: 4201 ) + +#include <mmreg.h> +#include "snd_wave_source.h" +#include "snd_wave_mixer_adpcm.h" +#include "snd_wave_mixer_private.h" +#include "hlfaceposer.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, bool scrubbing = false ); + +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, bool scrubbing ) +{ + position = max( 0, position ); + + CAudioMixerWave::SetSamplePosition( position, scrubbing ); + + 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 ); +} |