/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code 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. Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #pragma hdrstop #include "../idlib/precompiled.h" /* ================================================================================================ Contains the WaveFile implementation. ================================================================================================ */ #include "WaveFile.h" /* ======================== idWaveFile::Open Returns true if the Open was successful and the file matches the expected format. If this returns false, there is no need to call Close. ======================== */ bool idWaveFile::Open( const char * filename ) { Close(); if ( filename == NULL || filename[0] == 0 ) { return false; } if ( file == NULL ) { file = fileSystem->OpenFileReadMemory( filename ); if ( file == NULL ) { return false; } } if ( file->Length() == 0 ) { Close(); return false; } struct header_t { uint32 id; uint32 size; uint32 format; } header; file->Read( &header, sizeof( header ) ); idSwap::Big( header.id ); idSwap::Little( header.size ); idSwap::Big( header.format ); if ( header.id != 'RIFF' || header.format != 'WAVE' || header.size < 4 ) { Close(); idLib::Warning( "Header is not RIFF WAVE in %s", filename ); return false; } uint32 riffSize = header.size + 8; uint32 offset = sizeof( header ); // Scan the file collecting chunks while ( offset < riffSize ) { struct chuckHeader_t { uint32 id; uint32 size; } chunkHeader; if ( file->Read( &chunkHeader, sizeof( chunkHeader ) ) != sizeof( chunkHeader ) ) { // It seems like some tools have extra data after the last chunk for no apparent reason // so don't treat this as an error return true; } idSwap::Big( chunkHeader.id ); idSwap::Little( chunkHeader.size ); offset += sizeof( chunkHeader ); if ( chunks.Num() >= chunks.Max() ) { Close(); idLib::Warning( "More than %d chunks in %s", chunks.Max(), filename ); return false; } chunk_t * chunk = chunks.Alloc(); chunk->id = chunkHeader.id; chunk->size = chunkHeader.size; chunk->offset = offset; offset += chunk->size; file->Seek( offset, FS_SEEK_SET ); } return true; } /* ======================== idWaveFile::SeekToChunk Seeks to the specified chunk and returns the size of the chunk or 0 if the chunk wasn't found. ======================== */ uint32 idWaveFile::SeekToChunk( uint32 id ) { for ( int i = 0; i < chunks.Num(); i++ ) { if ( chunks[i].id == id ) { file->Seek( chunks[i].offset, FS_SEEK_SET ); return chunks[i].size; } } return 0; } /* ======================== idWaveFile::GetChunkOffset Seeks to the specified chunk and returns the size of the chunk or 0 if the chunk wasn't found. ======================== */ uint32 idWaveFile::GetChunkOffset( uint32 id ) { for ( int i = 0; i < chunks.Num(); i++ ) { if ( chunks[i].id == id ) { return chunks[i].offset; } } return 0; } // Used in XMA2WAVEFORMAT for per-stream data typedef struct XMA2STREAMFORMAT { byte Channels; // Number of channels in the stream (1 or 2) byte RESERVED; // Reserved for future use uint16 ChannelMask; // Spatial positions of the channels in the stream } XMA2STREAMFORMAT; // Legacy XMA2 format structure (big-endian byte ordering) typedef struct XMA2WAVEFORMAT { byte Version; // XMA encoder version that generated the file. // Always 3 or higher for XMA2 files. byte NumStreams; // Number of interleaved audio streams byte RESERVED; // Reserved for future use byte LoopCount; // Number of loop repetitions; 255 = infinite uint32 LoopBegin; // Loop begin point, in samples uint32 LoopEnd; // Loop end point, in samples uint32 SampleRate; // The file's decoded sample rate uint32 EncodeOptions; // Options for the XMA encoder/decoder uint32 PsuedoBytesPerSec; // Used internally by the XMA encoder uint32 BlockSizeInBytes; // Size in bytes of this file's XMA blocks (except // possibly the last one). Always a multiple of // 2Kb, since XMA blocks are arrays of 2Kb packets. uint32 SamplesEncoded; // Total number of PCM samples encoded in this file uint32 SamplesInSource; // Actual number of PCM samples in the source // material used to generate this file uint32 BlockCount; // Number of XMA blocks in this file (and hence // also the number of entries in its seek table) } XMA2WAVEFORMAT; /* ======================== idWaveFile::ReadWaveFormat Reads a wave format header, returns NULL if it found one and read it. otherwise, returns a human-readable error message. ======================== */ const char * idWaveFile::ReadWaveFormat( waveFmt_t & format ) { memset( &format, 0, sizeof( format ) ); uint32 formatSize = SeekToChunk( waveFmt_t::id ); if ( formatSize == 0 ) { return "No format chunk"; } if ( formatSize < sizeof( format.basic ) ) { return "Format chunk too small"; } Read( &format.basic, sizeof( format.basic ) ); idSwapClass swap; swap.Little( format.basic.formatTag ); swap.Little( format.basic.numChannels ); swap.Little( format.basic.samplesPerSec ); swap.Little( format.basic.avgBytesPerSec ); swap.Little( format.basic.blockSize ); swap.Little( format.basic.bitsPerSample ); if ( format.basic.formatTag == FORMAT_PCM ) { } else if ( format.basic.formatTag == FORMAT_ADPCM ) { Read( &format.extraSize, sizeof( format.extraSize ) ); idSwap::Little( format.extraSize ); if ( format.extraSize != sizeof( waveFmt_t::extra_t::adpcm_t ) ) { return "Incorrect number of coefficients in ADPCM file"; } Read( &format.extra.adpcm, sizeof( format.extra.adpcm ) ); idSwapClass swap; swap.Little( format.extra.adpcm.samplesPerBlock ); swap.Little( format.extra.adpcm.numCoef ); for ( int i = 0; i < format.extra.adpcm.numCoef; i++ ) { swap.Little( format.extra.adpcm.aCoef[ i ].coef1 ); swap.Little( format.extra.adpcm.aCoef[ i ].coef2 ); } } else if ( format.basic.formatTag == FORMAT_XMA2 ) { Read( &format.extraSize, sizeof( format.extraSize ) ); idSwap::Little( format.extraSize ); if ( format.extraSize != sizeof( waveFmt_t::extra_t::xma2_t ) ) { return "Incorrect chunk size in XMA2 file"; } Read( &format.extra.xma2, sizeof( format.extra.xma2 ) ); idSwapClass swap; swap.Little( format.extra.xma2.numStreams ); swap.Little( format.extra.xma2.channelMask ); swap.Little( format.extra.xma2.samplesEncoded ); swap.Little( format.extra.xma2.bytesPerBlock ); swap.Little( format.extra.xma2.playBegin ); swap.Little( format.extra.xma2.playLength ); swap.Little( format.extra.xma2.loopBegin ); swap.Little( format.extra.xma2.loopLength ); swap.Little( format.extra.xma2.loopCount ); swap.Little( format.extra.xma2.encoderVersion ); swap.Little( format.extra.xma2.blockCount ); } else if ( format.basic.formatTag == FORMAT_EXTENSIBLE ) { Read( &format.extraSize, sizeof( format.extraSize ) ); idSwap::Little( format.extraSize ); if ( format.extraSize != sizeof( waveFmt_t::extra_t::extensible_t ) ) { return "Incorrect chunk size in extensible wave file"; } Read( &format.extra.extensible, sizeof( format.extra.extensible ) ); idSwapClass swap; swap.Little( format.extra.extensible.validBitsPerSample ); swap.Little( format.extra.extensible.channelMask ); swap.Little( format.extra.extensible.subFormat.data1 ); swap.Little( format.extra.extensible.subFormat.data2 ); swap.Little( format.extra.extensible.subFormat.data3 ); swap.Little( format.extra.extensible.subFormat.data4 ); swap.LittleArray( format.extra.extensible.subFormat.data5, 6 ); waveFmt_t::extra_t::extensible_t::guid_t pcmGuid = { FORMAT_PCM, 0x0000, 0x0010, 0x8000, { 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; if ( memcmp( &pcmGuid, &format.extra.extensible.subFormat, sizeof( pcmGuid ) ) != 0 ) { return "Unsupported Extensible format"; } } else { return "Unknown wave format tag"; } return NULL; } /* ======================== idWaveFile::ReadWaveFormatDirect Reads a wave format header from a file ptr, ======================== */ bool idWaveFile::ReadWaveFormatDirect( waveFmt_t & format, idFile *file ) { file->Read( &format.basic, sizeof( format.basic ) ); idSwapClass swap; swap.Little( format.basic.formatTag ); swap.Little( format.basic.numChannels ); swap.Little( format.basic.samplesPerSec ); swap.Little( format.basic.avgBytesPerSec ); swap.Little( format.basic.blockSize ); swap.Little( format.basic.bitsPerSample ); if ( format.basic.formatTag == FORMAT_PCM ) { } else if ( format.basic.formatTag == FORMAT_ADPCM ) { file->Read( &format.extraSize, sizeof( format.extraSize ) ); idSwap::Little( format.extraSize ); if ( format.extraSize != sizeof( waveFmt_t::extra_t::adpcm_t ) ) { return false; } file->Read( &format.extra.adpcm, sizeof( format.extra.adpcm ) ); idSwapClass swap; swap.Little( format.extra.adpcm.samplesPerBlock ); swap.Little( format.extra.adpcm.numCoef ); for ( int i = 0; i < format.extra.adpcm.numCoef; i++ ) { swap.Little( format.extra.adpcm.aCoef[ i ].coef1 ); swap.Little( format.extra.adpcm.aCoef[ i ].coef2 ); } } else if ( format.basic.formatTag == FORMAT_XMA2 ) { file->Read( &format.extraSize, sizeof( format.extraSize ) ); idSwap::Little( format.extraSize ); if ( format.extraSize != sizeof( waveFmt_t::extra_t::xma2_t ) ) { return false; } file->Read( &format.extra.xma2, sizeof( format.extra.xma2 ) ); idSwapClass swap; swap.Little( format.extra.xma2.numStreams ); swap.Little( format.extra.xma2.channelMask ); swap.Little( format.extra.xma2.samplesEncoded ); swap.Little( format.extra.xma2.bytesPerBlock ); swap.Little( format.extra.xma2.playBegin ); swap.Little( format.extra.xma2.playLength ); swap.Little( format.extra.xma2.loopBegin ); swap.Little( format.extra.xma2.loopLength ); swap.Little( format.extra.xma2.loopCount ); swap.Little( format.extra.xma2.encoderVersion ); swap.Little( format.extra.xma2.blockCount ); } else if ( format.basic.formatTag == FORMAT_EXTENSIBLE ) { file->Read( &format.extraSize, sizeof( format.extraSize ) ); idSwap::Little( format.extraSize ); if ( format.extraSize != sizeof( waveFmt_t::extra_t::extensible_t ) ) { return false; } file->Read( &format.extra.extensible, sizeof( format.extra.extensible ) ); idSwapClass swap; swap.Little( format.extra.extensible.validBitsPerSample ); swap.Little( format.extra.extensible.channelMask ); swap.Little( format.extra.extensible.subFormat.data1 ); swap.Little( format.extra.extensible.subFormat.data2 ); swap.Little( format.extra.extensible.subFormat.data3 ); swap.Little( format.extra.extensible.subFormat.data4 ); swap.LittleArray( format.extra.extensible.subFormat.data5, 6 ); waveFmt_t::extra_t::extensible_t::guid_t pcmGuid = { FORMAT_PCM, 0x0000, 0x0010, 0x8000, { 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } }; if ( memcmp( &pcmGuid, &format.extra.extensible.subFormat, sizeof( pcmGuid ) ) != 0 ) { return false; } } else { return false; } return true; } /* ======================== idWaveFile::WriteWaveFormatDirect Writes a wave format header to a file ptr, ======================== */ bool idWaveFile::WriteWaveFormatDirect( waveFmt_t & format, idFile *file ) { //idSwapClass swap; //swap.Little( format.basic.formatTag ); //swap.Little( format.basic.numChannels ); //swap.Little( format.basic.samplesPerSec ); //swap.Little( format.basic.avgBytesPerSec ); //swap.Little( format.basic.blockSize ); //swap.Little( format.basic.bitsPerSample ); file->Write( &format.basic, sizeof( format.basic ) ); if ( format.basic.formatTag == FORMAT_PCM ) { //file->Write( &format.basic, sizeof( format.basic ) ); } else if ( format.basic.formatTag == FORMAT_ADPCM ) { //file->Write( &format.basic, sizeof( format.basic ) ); file->Write( &format.extraSize, sizeof( format.extraSize ) ); file->Write( &format.extra.adpcm, sizeof( format.extra.adpcm ) ); } else if ( format.basic.formatTag == FORMAT_XMA2 ) { //file->Write( &format.basic, sizeof( format.basic ) ); file->Write( &format.extraSize, sizeof( format.extraSize ) ); file->Write( &format.extra.xma2, sizeof( format.extra.xma2 ) ); } else if ( format.basic.formatTag == FORMAT_EXTENSIBLE ) { //file->Write( &format.basic, sizeof( format.basic ) ); file->Write( &format.extraSize, sizeof( format.extraSize ) ); file->Write( &format.extra.extensible, sizeof( format.extra.extensible ) ); } else { return false; } return true; } /* ======================== idWaveFile::WriteWaveFormatDirect Writes a wave format header to a file ptr, ======================== */ bool idWaveFile::WriteSampleDataDirect( idList< sampleData_t > & sampleData, idFile * file ) { static const uint32 sample = 'smpl'; file->WriteBig( sample ); uint32 samplerData = sampleData.Num() * 24; uint32 chunkSize = 36 + samplerData; uint32 zero = 0; uint32 numSamples = sampleData.Num(); file->Write( &chunkSize, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); file->Write( &numSamples, sizeof( uint32 ) ); file->Write( &samplerData, sizeof( uint32 ) ); for ( int i = 0; i < sampleData.Num(); ++i ) { file->Write( &zero, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); file->Write( &sampleData[ i ].start, sizeof( uint32 ) ); file->Write( &sampleData[ i ].end, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); file->Write( &zero, sizeof( uint32 ) ); } return true; } /* ======================== idWaveFile::WriteWaveFormatDirect Writes a data chunk to a file ptr ======================== */ bool idWaveFile::WriteDataDirect( char * _data, uint32 size, idFile * file ) { static const uint32 data = 'data'; file->WriteBig( data ); file->Write( &size, sizeof( uint32 ) ); file->WriteBigArray( _data, size ); return true; } /* ======================== idWaveFile::WriteWaveFormatDirect Writes a wave header to a file ptr, ======================== */ bool idWaveFile::WriteHeaderDirect( uint32 fileSize, idFile * file ) { static const uint32 riff = 'RIFF'; static const uint32 wave = 'WAVE'; file->WriteBig( riff ); file->WriteBig( fileSize ); file->WriteBig( wave ); return true; } /* ======================== idWaveFile::ReadLoopPoint Reads a loop point from a 'smpl' chunk in a wave file, returns 0 if none are found. ======================== */ bool idWaveFile::ReadLoopData( int & start, int & end ) { uint32 chunkSize = SeekToChunk( samplerChunk_t::id ); if ( chunkSize < sizeof( samplerChunk_t ) ) { return false; } samplerChunk_t smpl; Read( &smpl, sizeof( smpl ) ); idSwap::Little( smpl.numSampleLoops ); if ( smpl.numSampleLoops < 1 ) { return false; // this is possible returning false lets us know there are more then 1 sample look in the file and is not appropriate for traditional looping } sampleData_t smplData; Read( &smplData, sizeof( smplData ) ); idSwap::Little( smplData.start ); idSwap::Little( smplData.end ); if ( smplData.type != 0 ) { idLib::Warning( "Invalid loop type in %s", file->GetName() ); return false; } start = smplData.start; end = smplData.end; return true; } /* ======================== idWaveFile::Close Closes the file and frees resources. ======================== */ void idWaveFile::Close() { if ( file != NULL ) { delete file; file = NULL; } chunks.SetNum( 0 ); }