initial
This commit is contained in:
606
soundsystem/snd_wave_source.cpp
Normal file
606
soundsystem/snd_wave_source.cpp
Normal file
@@ -0,0 +1,606 @@
|
||||
//===== Copyright <20> 1996-2005, Valve Corporation, All rights reserved. ======//
|
||||
//
|
||||
// Purpose:
|
||||
//
|
||||
// $NoKeywords: $
|
||||
//
|
||||
//===========================================================================//
|
||||
|
||||
#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 "soundsystem/snd_audio_source.h"
|
||||
#include <mmsystem.h> // wave format
|
||||
#include <mmreg.h> // adpcm format
|
||||
#include "soundsystem.h"
|
||||
#include "FileSystem.h"
|
||||
#include "tier1/utlbuffer.h"
|
||||
|
||||
// NOTE: This has to be the last file included!
|
||||
#include "tier0/memdbgon.h"
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Purpose: Implements the RIFF i/o interface on stdio
|
||||
//-----------------------------------------------------------------------------
|
||||
class StdIOReadBinary : public IFileReadBinary
|
||||
{
|
||||
public:
|
||||
FileHandle_t open( const char *pFileName )
|
||||
{
|
||||
return g_pFullFileSystem->Open( pFileName, "rb", "GAME" );
|
||||
}
|
||||
|
||||
int read( void *pOutput, int size, FileHandle_t file )
|
||||
{
|
||||
if ( !file )
|
||||
return 0;
|
||||
|
||||
return g_pFullFileSystem->Read( pOutput, size, (FileHandle_t)file );
|
||||
}
|
||||
|
||||
void seek( FileHandle_t file, int pos )
|
||||
{
|
||||
if ( !file )
|
||||
return;
|
||||
|
||||
g_pFullFileSystem->Seek( file, pos, FILESYSTEM_SEEK_HEAD );
|
||||
}
|
||||
|
||||
unsigned int tell( FileHandle_t file )
|
||||
{
|
||||
if ( !file )
|
||||
return 0;
|
||||
|
||||
return g_pFullFileSystem->Tell( (FileHandle_t)file );
|
||||
}
|
||||
|
||||
unsigned int size( FileHandle_t file )
|
||||
{
|
||||
if ( !file )
|
||||
return 0;
|
||||
|
||||
return g_pFullFileSystem->Size( (FileHandle_t)file );
|
||||
}
|
||||
|
||||
void close( FileHandle_t file )
|
||||
{
|
||||
if ( !file )
|
||||
return;
|
||||
|
||||
g_pFullFileSystem->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_bits = 0;
|
||||
m_rate = 0;
|
||||
m_channels = 0;
|
||||
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(); };
|
||||
|
||||
virtual int GetNumChannels();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
int CAudioSourceMemWave::GetNumChannels()
|
||||
{
|
||||
return m_channels;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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 );
|
||||
|
||||
if ( riff.RIFFName() != RIFF_WAVE )
|
||||
{
|
||||
Warning("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 );
|
||||
}
|
||||
Reference in New Issue
Block a user