|
|
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <windows.h>
#include "tier2/riff.h"
#include "snd_wave_source.h"
#include "snd_wave_mixer_private.h"
#include "snd_audio_source.h"
#include <mmsystem.h> // wave format
#include <mmreg.h> // adpcm format
#include "filesystem.h"
#include "utlbuffer.h"
//-----------------------------------------------------------------------------
// Purpose: Implements the RIFF i/o interface on stdio
//-----------------------------------------------------------------------------
class StdIOReadBinary : public IFileReadBinary { public: int open( const char *pFileName ) { return (int)filesystem->Open( pFileName, "rb" ); }
int read( void *pOutput, int size, int file ) { if ( !file ) return 0;
return filesystem->Read( pOutput, size, (FileHandle_t)file ); }
void seek( int file, int pos ) { if ( !file ) return;
filesystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD ); }
unsigned int tell( int file ) { if ( !file ) return 0;
return filesystem->Tell( (FileHandle_t)file ); }
unsigned int size( int file ) { if ( !file ) return 0;
return filesystem->Size( (FileHandle_t)file ); }
void close( int file ) { if ( !file ) return;
filesystem->Close( (FileHandle_t)file ); } };
static StdIOReadBinary io;
#define RIFF_WAVE MAKEID('W','A','V','E')
#define WAVE_FMT MAKEID('f','m','t',' ')
#define WAVE_DATA MAKEID('d','a','t','a')
#define WAVE_FACT MAKEID('f','a','c','t')
#define WAVE_CUE MAKEID('c','u','e',' ')
void ChunkError( unsigned int id ) { }
//-----------------------------------------------------------------------------
// Purpose: Init to empty wave
//-----------------------------------------------------------------------------
CAudioSourceWave::CAudioSourceWave( void ) { m_format = 0; m_pHeader = NULL; // no looping
m_loopStart = -1; m_sampleSize = 1; m_sampleCount = 0; }
CAudioSourceWave::~CAudioSourceWave( void ) { // for non-standard waves, we store a copy of the header in RAM
delete[] m_pHeader; // m_pWords points into m_pWordBuffer, no need to delete
}
//-----------------------------------------------------------------------------
// Purpose: Init the wave data.
// Input : *pHeaderBuffer - the RIFF fmt chunk
// headerSize - size of that chunk
//-----------------------------------------------------------------------------
void CAudioSourceWave::Init( const char *pHeaderBuffer, int headerSize ) { const WAVEFORMATEX *pHeader = (const WAVEFORMATEX *)pHeaderBuffer;
// copy the relevant header data
m_format = pHeader->wFormatTag; m_bits = pHeader->wBitsPerSample; m_rate = pHeader->nSamplesPerSec; m_channels = pHeader->nChannels;
m_sampleSize = (m_bits * m_channels) / 8; // this can never be zero -- other functions divide by this.
// This should never happen, but avoid crashing
if ( m_sampleSize <= 0 ) m_sampleSize = 1;
// For non-standard waves (like ADPCM) store the header, it has some useful data
if ( m_format != WAVE_FORMAT_PCM ) { m_pHeader = new char[headerSize]; memcpy( m_pHeader, pHeader, headerSize ); if ( m_format == WAVE_FORMAT_ADPCM ) { // treat ADPCM sources as a file of bytes. They are decoded by the mixer
m_sampleSize = 1; } } }
//-----------------------------------------------------------------------------
// Purpose:
// Output : float
//-----------------------------------------------------------------------------
float CAudioSourceWave::TrueSampleSize( void ) { if ( m_format == WAVE_FORMAT_ADPCM ) { return 0.5f; } return (float)m_sampleSize; }
//-----------------------------------------------------------------------------
// Purpose: Total number of samples in this source
// Output : int
//-----------------------------------------------------------------------------
int CAudioSourceWave::SampleCount( void ) { if ( m_format == WAVE_FORMAT_ADPCM ) { ADPCMWAVEFORMAT *pFormat = (ADPCMWAVEFORMAT *)m_pHeader; int blockSize = ((pFormat->wSamplesPerBlock - 2) * pFormat->wfx.nChannels ) / 2; blockSize += 7 * pFormat->wfx.nChannels;
int blockCount = m_sampleCount / blockSize; int blockRem = m_sampleCount % blockSize; // total samples in complete blocks
int sampleCount = blockCount * pFormat->wSamplesPerBlock;
// add remaining in a short block
if ( blockRem ) { sampleCount += pFormat->wSamplesPerBlock - (((blockSize - blockRem) * 2) / m_channels); } return sampleCount; } return m_sampleCount; }
//-----------------------------------------------------------------------------
// Purpose: Do any sample conversion
// For 8 bit PCM, convert to signed because the mixing routine assumes this
// Input : *pData - pointer to sample data
// sampleCount - number of samples
//-----------------------------------------------------------------------------
void CAudioSourceWave::ConvertSamples( char *pData, int sampleCount ) { if ( m_format == WAVE_FORMAT_PCM ) { if ( m_bits == 8 ) { for ( int i = 0; i < sampleCount; i++ ) { for ( int j = 0; j < m_channels; j++ ) { *pData = (unsigned char)((int)((unsigned)*pData) - 128); pData++; } } } } }
//-----------------------------------------------------------------------------
// Purpose:
// Input : &walk -
//-----------------------------------------------------------------------------
void CAudioSourceWave::ParseSentence( IterateRIFF &walk ) { CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
buf.EnsureCapacity( walk.ChunkSize() ); walk.ChunkRead( buf.Base() ); buf.SeekPut( CUtlBuffer::SEEK_HEAD, walk.ChunkSize() );
m_Sentence.InitFromDataChunk( buf.Base(), buf.TellPut() ); }
//-----------------------------------------------------------------------------
// Purpose: Parse base chunks
// Input : &walk - riff file to parse
// : chunkName - name of the chunk to parse
//-----------------------------------------------------------------------------
// UNDONE: Move parsing loop here and drop each chunk into a virtual function
// instead of this being virtual.
void CAudioSourceWave::ParseChunk( IterateRIFF &walk, int chunkName ) { switch( chunkName ) { case WAVE_CUE: { m_loopStart = ParseCueChunk( walk ); } break; case WAVE_VALVEDATA: { ParseSentence( walk ); } break; // unknown/don't care
default: { ChunkError( walk.ChunkName() ); } break; } }
//-----------------------------------------------------------------------------
// Purpose:
// Output : CSentence
//-----------------------------------------------------------------------------
CSentence *CAudioSourceWave::GetSentence( void ) { return &m_Sentence; }
//-----------------------------------------------------------------------------
// Purpose: Bastardized construction routine. This is just to avoid complex
// constructor functions so code can be shared more easily by sub-classes
// Input : *pFormatBuffer - RIFF header
// formatSize - header size
// &walk - RIFF file
//-----------------------------------------------------------------------------
void CAudioSourceWave::Setup( const char *pFormatBuffer, int formatSize, IterateRIFF &walk ) { Init( pFormatBuffer, formatSize );
while ( walk.ChunkAvailable() ) { ParseChunk( walk, walk.ChunkName() ); walk.ChunkNext(); } }
//-----------------------------------------------------------------------------
// Purpose: Wave file that is completely in memory
// UNDONE: Implement Lock/Unlock and caching
//-----------------------------------------------------------------------------
class CAudioSourceMemWave : public CAudioSourceWave { public: CAudioSourceMemWave( void ); ~CAudioSourceMemWave( void );
// Create an instance (mixer) of this audio source
virtual CAudioMixer *CreateMixer( void );
virtual void ParseChunk( IterateRIFF &walk, int chunkName ); void ParseDataChunk( IterateRIFF &walk );
virtual int GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward = true ); virtual float GetRunningLength( void ) { return CAudioSourceWave::GetRunningLength(); };
private: char *m_pData; // wave data
};
//-----------------------------------------------------------------------------
// Purpose: Iterator for wave data (this is to abstract streaming/buffering)
//-----------------------------------------------------------------------------
class CWaveDataMemory : public CWaveData { public: CWaveDataMemory( CAudioSourceWave &source ) : m_source(source) {} ~CWaveDataMemory( void ) {} CAudioSourceWave &Source( void ) { return m_source; } // this file is in memory, simply pass along the data request to the source
virtual int ReadSourceData( void **pData, int sampleIndex, int sampleCount, bool forward /*= true*/ ) { return m_source.GetOutputData( pData, sampleIndex, sampleCount, forward ); } private: CAudioSourceWave &m_source; // pointer to source
};
//-----------------------------------------------------------------------------
// Purpose: NULL the wave data pointer (we haven't loaded yet)
//-----------------------------------------------------------------------------
CAudioSourceMemWave::CAudioSourceMemWave( void ) { m_pData = NULL; }
//-----------------------------------------------------------------------------
// Purpose: Free any wave data we've allocated
//-----------------------------------------------------------------------------
CAudioSourceMemWave::~CAudioSourceMemWave( void ) { delete[] m_pData; }
//-----------------------------------------------------------------------------
// Purpose: Creates a mixer and initializes it with an appropriate mixer
//-----------------------------------------------------------------------------
CAudioMixer *CAudioSourceMemWave::CreateMixer( void ) { return CreateWaveMixer( new CWaveDataMemory(*this), m_format, m_channels, m_bits ); }
//-----------------------------------------------------------------------------
// Purpose: parse chunks with unique processing to in-memory waves
// Input : &walk - RIFF file
//-----------------------------------------------------------------------------
void CAudioSourceMemWave::ParseChunk( IterateRIFF &walk, int chunkName ) { switch( chunkName ) { // this is the audio data
case WAVE_DATA: { ParseDataChunk( walk ); } return; }
CAudioSourceWave::ParseChunk( walk, chunkName ); }
//-----------------------------------------------------------------------------
// Purpose: reads the actual sample data and parses it
// Input : &walk - RIFF file
//-----------------------------------------------------------------------------
void CAudioSourceMemWave::ParseDataChunk( IterateRIFF &walk ) { int size = walk.ChunkSize(); // create a buffer for the samples
m_pData = new char[size];
// load them into memory
walk.ChunkRead( m_pData );
if ( m_format == WAVE_FORMAT_PCM ) { // number of samples loaded
m_sampleCount = size / m_sampleSize;
// some samples need to be converted
ConvertSamples( m_pData, m_sampleCount ); } else if ( m_format == WAVE_FORMAT_ADPCM ) { // The ADPCM mixers treat the wave source as a flat file of bytes.
m_sampleSize = 1; // Since each "sample" is a byte (this is a flat file), the number of samples is the file size
m_sampleCount = size;
// file says 4, output is 16
m_bits = 16; } }
//-----------------------------------------------------------------------------
// Purpose: parses loop information from a cue chunk
// Input : &walk - RIFF iterator
// Output : int loop start position
//-----------------------------------------------------------------------------
int CAudioSourceWave::ParseCueChunk( IterateRIFF &walk ) { // Cue chunk as specified by RIFF format
// see $/research/jay/sound/riffnew.htm
struct { unsigned int dwName; unsigned int dwPosition; unsigned int fccChunk; unsigned int dwChunkStart; unsigned int dwBlockStart; unsigned int dwSampleOffset; } cue_chunk;
int cueCount;
// assume that the cue chunk stored in the wave is the start of the loop
// assume only one cue chunk, UNDONE: Test this assumption here?
cueCount = walk.ChunkReadInt();
walk.ChunkReadPartial( &cue_chunk, sizeof(cue_chunk) ); return cue_chunk.dwSampleOffset; }
//-----------------------------------------------------------------------------
// Purpose: get the wave header
//-----------------------------------------------------------------------------
void *CAudioSourceWave::GetHeader( void ) { return m_pHeader; }
//-----------------------------------------------------------------------------
// Purpose: wrap the position wrt looping
// Input : samplePosition - absolute position
// Output : int - looped position
//-----------------------------------------------------------------------------
int CAudioSourceWave::ConvertLoopedPosition( int samplePosition ) { // if the wave is looping and we're past the end of the sample
// convert to a position within the loop
// At the end of the loop, we return a short buffer, and subsequent call
// will loop back and get the rest of the buffer
if ( m_loopStart >= 0 ) { if ( samplePosition >= m_sampleCount ) { // size of loop
int loopSize = m_sampleCount - m_loopStart; // subtract off starting bit of the wave
samplePosition -= m_loopStart; if ( loopSize ) { // "real" position in memory (mod off extra loops)
samplePosition = m_loopStart + (samplePosition % loopSize); } // ERROR? if no loopSize
} }
return samplePosition; }
//-----------------------------------------------------------------------------
// Purpose:
// Input : **pData - output pointer to samples
// samplePosition - position (in samples not bytes)
// sampleCount - number of samples (not bytes)
// Output : int - number of samples available
//-----------------------------------------------------------------------------
int CAudioSourceMemWave::GetOutputData( void **pData, int samplePosition, int sampleCount, bool forward /*= true*/ ) { // handle position looping
samplePosition = ConvertLoopedPosition( samplePosition );
// how many samples are available (linearly not counting looping)
int availableSampleCount = m_sampleCount - samplePosition; if ( !forward ) { if ( samplePosition >= m_sampleCount ) { availableSampleCount = 0; } else { availableSampleCount = samplePosition; } }
// may be asking for a sample out of range, clip at zero
if ( availableSampleCount < 0 ) availableSampleCount = 0;
// clip max output samples to max available
if ( sampleCount > availableSampleCount ) sampleCount = availableSampleCount;
// byte offset in sample database
samplePosition *= m_sampleSize;
// if we are returning some samples, store the pointer
if ( sampleCount ) { *pData = m_pData + samplePosition; }
return sampleCount; }
//-----------------------------------------------------------------------------
// Purpose: Create a wave audio source (streaming or in memory)
// Input : *pName - file name
// streaming - if true, don't load, stream each instance
// Output : CAudioSource * - a new source
//-----------------------------------------------------------------------------
// UNDONE : Pool these and check for duplicates?
CAudioSource *CreateWave( const char *pName ) { char formatBuffer[1024]; InFileRIFF riff( pName, io );
// UNDONE: Don't use printf to handle errors
if ( riff.RIFFName() != RIFF_WAVE ) { printf("Bad RIFF file type %s\n", pName ); return NULL; }
// set up the iterator for the whole file (root RIFF is a chunk)
IterateRIFF walk( riff, riff.RIFFSize() );
int format = 0; int formatSize = 0;
// This chunk must be first as it contains the wave's format
// break out when we've parsed it
while ( walk.ChunkAvailable() && format == 0 ) { switch( walk.ChunkName() ) { case WAVE_FMT: { if ( walk.ChunkSize() <= 1024 ) { walk.ChunkRead( formatBuffer ); formatSize = walk.ChunkSize(); format = ((WAVEFORMATEX *)formatBuffer)->wFormatTag; } } break; default: { ChunkError( walk.ChunkName() ); } break; } walk.ChunkNext(); }
// Not really a WAVE file or no format chunk, bail
if ( !format ) return NULL;
CAudioSourceWave *pWave;
// create the source from this file
pWave = new CAudioSourceMemWave();
// init the wave source
pWave->Setup( formatBuffer, formatSize, walk );
return pWave; }
//-----------------------------------------------------------------------------
// Purpose: Wrapper for CreateWave()
//-----------------------------------------------------------------------------
CAudioSource *Audio_CreateMemoryWave( const char *pName ) { return CreateWave( pName ); }
|