/*==LICENSE==* CyanWorlds.com Engine - MMOG client, server and tools Copyright (C) 2011 Cyan Worlds, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . You can contact Cyan Worlds, Inc. by email legal@cyan.com or by snail mail at: Cyan Worlds, Inc. 14617 N Newport Hwy Mead, WA 99021 *==LICENSE==*/ ////////////////////////////////////////////////////////////////////////////// // // // plFastWavReader - Quick, dirty, and highly optimized class for reading // // in the samples of a WAV file when you're in a hurry. // // ONLY WORKS WITH PCM (i.e. uncompressed) DATA // // // //// Notes /////////////////////////////////////////////////////////////////// // // // 11.5.2001 - Created by mcn. // // // ////////////////////////////////////////////////////////////////////////////// #include #include #include #include "hsTypes.h" #include "plFastWavReader.h" //// Local Helpers /////////////////////////////////////////////////////////// class plRIFFChunk { public: char fID[ 4 ]; UInt32 fSize; void Read( FILE *fp ) { fread( fID, 1, 4, fp ); fread( &fSize, sizeof( UInt32 ), 1, fp ); } bool IsA( const char *type ) { return ( memcmp( fID, type, 4 ) == 0 ) ? true : false; } }; class plRIFFHeader { protected: plRIFFChunk fChunk; bool fValid; char fFormat[ 4 ]; public: plRIFFHeader( FILE *fp ) { fValid = false; fChunk.Read( fp ); if( fChunk.IsA( "RIFF" ) ) { if( fread( &fFormat, 1, 4, fp ) == 4 ) { fValid = true; } } } bool IsValid( void ) { return fValid; } bool IsA( const char *type ) { return ( memcmp( fFormat, type, 4 ) == 0 ) ? true : false; } }; //// Constructor/Destructor ////////////////////////////////////////////////// plFastWAV::plFastWAV( const char *path, plAudioCore::ChannelSelect whichChan ) : fFileHandle( nil ) { hsAssert( path != nil, "Invalid path specified in plFastWAV reader" ); strncpy( fFilename, path, sizeof( fFilename ) ); fWhichChannel = whichChan; fFileHandle = fopen( path, "rb" ); if( fFileHandle != nil ) { /// Read in our header and calc our start position plRIFFHeader riffHdr( fFileHandle ); if( !riffHdr.IsValid() ) { IError( "Invalid RIFF file header in plFastWAV" ); return; } if( !riffHdr.IsA( "WAVE" ) ) { IError( "Invalid RIFF file type in plFastWAV" ); return; } fChunkStart = ftell( fFileHandle ); // Seek and read the "fmt " header plRIFFChunk chunk; if( !ISeekToChunk( "fmt ", &chunk ) ) { IError( "Unable to find fmt chunk in WAV file" ); return; } if( fread( &fHeader, 1, sizeof( plWAVHeader ), fFileHandle ) != sizeof( plWAVHeader ) ) { IError( "Invalid WAV file header in plFastWAV" ); return; } // Check format if( fHeader.fFormatTag != kPCMFormatTag ) { IError( "Invalid format in plFastWAV" ); return; } // Seek to and get the position of the data chunk if( !ISeekToChunk( "data", &chunk ) ) { IError( "Unable to find data chunk in WAV file" ); return; } fDataStartPos = ftell( fFileHandle ); fDataSize = chunk.fSize; // HACKY FIX FOR BAD WAV FILES fDataSize -= ( fDataSize & ( fHeader.fBlockAlign - 1 ) ); if( fWhichChannel != plAudioCore::kAll ) { fChannelAdjust = 2; fChannelOffset = ( fWhichChannel == plAudioCore::kLeft ) ? 0 : 1; } else { fChannelAdjust = 1; fChannelOffset = 0; } fFakeHeader = fHeader; fFakeHeader.fAvgBytesPerSec /= fChannelAdjust; fFakeHeader.fNumChannels /= (UInt16)fChannelAdjust; fFakeHeader.fBlockAlign /= (UInt16)fChannelAdjust; SetPosition( 0 ); // fCurrDataPos = 0; } } plFastWAV::~plFastWAV() { if( fFileHandle != nil ) fclose( fFileHandle ); } bool plFastWAV::ISeekToChunk( const char *type, plRIFFChunk *c ) { plRIFFChunk chunk; // Start from chunk start and search through all the, well, chunks :) fseek( fFileHandle, fChunkStart, SEEK_SET ); while( !feof( fFileHandle ) ) { chunk.Read( fFileHandle ); if( chunk.IsA( type ) ) { *c = chunk; return true; } // Seek past this one fseek( fFileHandle, chunk.fSize, SEEK_CUR ); } return false; } void plFastWAV::Open() { if(fFileHandle) return; fFileHandle = fopen( fFilename, "rb" ); if(!fFileHandle) return; fCurrDataPos = 0; fseek( fFileHandle, fDataStartPos, SEEK_SET ); } void plFastWAV::Close( void ) { if( fFileHandle != nil ) { fclose( fFileHandle ); fFileHandle = nil; } } void plFastWAV::IError( const char *msg ) { hsAssert( false, msg ); Close(); } plWAVHeader &plFastWAV::GetHeader( void ) { hsAssert( IsValid(), "GetHeader() called on an invalid WAV file" ); return fFakeHeader; } float plFastWAV::GetLengthInSecs( void ) { hsAssert( IsValid(), "GetLengthInSecs() called on an invalid WAV file" ); return (float)( fDataSize / fChannelAdjust ) / (float)fHeader.fAvgBytesPerSec; } hsBool plFastWAV::SetPosition( UInt32 numBytes ) { hsAssert( IsValid(), "GetHeader() called on an invalid WAV file" ); fCurrDataPos = numBytes * fChannelAdjust + ( fChannelOffset * fHeader.fBlockAlign / fChannelAdjust ); hsAssert( fCurrDataPos <= fDataSize, "Invalid new position while seeking WAV file" ); return ( fseek( fFileHandle, fDataStartPos + fCurrDataPos, SEEK_SET ) == 0 ) ? true : false; } hsBool plFastWAV::Read( UInt32 numBytes, void *buffer ) { hsAssert( IsValid(), "GetHeader() called on an invalid WAV file" ); if( fWhichChannel != plAudioCore::kAll ) { size_t numRead, sampleSize = fHeader.fBlockAlign / fChannelAdjust; static UInt8 trashBuffer[ 32 ]; UInt32 numBytesFull = numBytes; if( fCurrDataPos + ( numBytes * fChannelAdjust ) > fDataSize ) numBytesFull -= sampleSize; for( numRead = 0; numRead < numBytesFull; numRead += sampleSize ) { size_t thisRead = fread( buffer, 1, sampleSize, fFileHandle ); if( thisRead != sampleSize ) return false; // fseek( fFileHandle, sampleSize * ( fChannelAdjust - 1 ), SEEK_CUR ); thisRead = fread( trashBuffer, 1, sampleSize, fFileHandle ); if( thisRead != sampleSize ) return false; buffer = (void *)( (UInt8 *)buffer + sampleSize ); fCurrDataPos += sampleSize * fChannelAdjust; } if( numRead < numBytes ) { if( numBytes - numRead > sampleSize ) { hsAssert( false, "Invalid logic in plFastWAV::Read()" ); return false; } // Must not have enough room left, so no more skipping size_t thisRead = fread( buffer, 1, sampleSize, fFileHandle ); if( thisRead != sampleSize ) return false; buffer = (void *)( (UInt8 *)buffer + sampleSize ); fCurrDataPos += sampleSize; } hsAssert( fCurrDataPos <= fDataSize, "Invalid new position while reading WAV file" ); } else { size_t numRead = fread( buffer, 1, numBytes, fFileHandle ); fCurrDataPos += numRead; hsAssert( fCurrDataPos <= fDataSize, "Invalid new position while reading WAV file" ); if( numRead < numBytes ) return false; } return true; } UInt32 plFastWAV::NumBytesLeft( void ) { hsAssert( IsValid(), "GetHeader() called on an invalid WAV file" ); hsAssert( fCurrDataPos <= fDataSize, "Invalid current position while reading WAV file" ); return ( fDataSize - fCurrDataPos ) / fChannelAdjust; }