summaryrefslogtreecommitdiff
path: root/soundsystem/snd_wave_source.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'soundsystem/snd_wave_source.cpp')
-rw-r--r--soundsystem/snd_wave_source.cpp602
1 files changed, 602 insertions, 0 deletions
diff --git a/soundsystem/snd_wave_source.cpp b/soundsystem/snd_wave_source.cpp
new file mode 100644
index 0000000..765a2b3
--- /dev/null
+++ b/soundsystem/snd_wave_source.cpp
@@ -0,0 +1,602 @@
+//========= Copyright 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"
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Implements the RIFF i/o interface on stdio
+//-----------------------------------------------------------------------------
+class StdIOReadBinary : public IFileReadBinary
+{
+public:
+ int open( const char *pFileName )
+ {
+ return (int)g_pFullFileSystem->Open( pFileName, "rb", "GAME" );
+ }
+
+ int read( void *pOutput, int size, int file )
+ {
+ if ( !file )
+ return 0;
+
+ return g_pFullFileSystem->Read( pOutput, size, (FileHandle_t)file );
+ }
+
+ void seek( int file, int pos )
+ {
+ if ( !file )
+ return;
+
+ g_pFullFileSystem->Seek( (FileHandle_t)file, pos, FILESYSTEM_SEEK_HEAD );
+ }
+
+ unsigned int tell( int file )
+ {
+ if ( !file )
+ return 0;
+
+ return g_pFullFileSystem->Tell( (FileHandle_t)file );
+ }
+
+ unsigned int size( int file )
+ {
+ if ( !file )
+ return 0;
+
+ return g_pFullFileSystem->Size( (FileHandle_t)file );
+ }
+
+ void close( int 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 );
+}