Initial commit

This commit is contained in:
Brian Harris
2012-11-26 12:58:24 -06:00
parent a5214f79ef
commit 5016f605b8
1115 changed files with 587266 additions and 0 deletions

257
neo/sound/SoundVoice.cpp Normal file
View File

@@ -0,0 +1,257 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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"
#include "snd_local.h"
idCVar s_subFraction( "s_subFraction", "0.5", CVAR_ARCHIVE | CVAR_FLOAT, "Amount of each sound to send to the LFE channel" );
idVec2 idSoundVoice_Base::speakerPositions[idWaveFile::CHANNEL_INDEX_MAX];
int idSoundVoice_Base::speakerLeft[idWaveFile::CHANNEL_INDEX_MAX] = {0 };
int idSoundVoice_Base::speakerRight[idWaveFile::CHANNEL_INDEX_MAX] = {0 };
int idSoundVoice_Base::dstChannels = 0;
int idSoundVoice_Base::dstMask = 0;
int idSoundVoice_Base::dstCenter = -1;
int idSoundVoice_Base::dstLFE = -1;
int idSoundVoice_Base::dstMap[MAX_CHANNELS_PER_VOICE] = { 0 };
int idSoundVoice_Base::invMap[idWaveFile::CHANNEL_INDEX_MAX] = { 0 };
float idSoundVoice_Base::omniLevel = 1.0f;
/*
========================
idSoundVoice_Base::idSoundVoice_Base
========================
*/
idSoundVoice_Base::idSoundVoice_Base() :
position( 0.0f ),
gain( 1.0f ),
centerChannel( 0.0f ),
pitch( 1.0f ),
innerRadius( 32.0f ),
occlusion( 0.0f ),
channelMask( 0 ),
innerSampleRangeSqr( 0.0f ),
outerSampleRangeSqr( 0.0f )
{
}
/*
========================
idSoundVoice_Base::InitSurround
========================
*/
void idSoundVoice_Base::InitSurround( int outputChannels, int channelMask ) {
speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_LEFT ].Set( 0.70710678118654752440084436210485f, 0.70710678118654752440084436210485f ); // 45 degrees
speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT ].Set( 0.70710678118654752440084436210485f, -0.70710678118654752440084436210485f ); // 315 degrees
speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_CENTER ].Set( 0.0f, 0.0f ); // 0 degrees
speakerPositions[idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY ].Set( 0.0f, 0.0f ); // -
speakerPositions[idWaveFile::CHANNEL_INDEX_BACK_LEFT ].Set( -0.70710678118654752440084436210485f, 0.70710678118654752440084436210485f ); // 135 degrees
speakerPositions[idWaveFile::CHANNEL_INDEX_BACK_RIGHT ].Set( -0.70710678118654752440084436210485f, -0.70710678118654752440084436210485f ); // 225 degrees
speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER ].Set( 0.92387953251128675612818318939679f, 0.3826834323650897717284599840304f ); // 22.5 degrees
speakerPositions[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER ].Set( 0.92387953251128675612818318939679f, -0.3826834323650897717284599840304f ); // 337.5 degrees
speakerPositions[idWaveFile::CHANNEL_INDEX_BACK_CENTER ].Set( -1.0f, 0.0f ); // 180 degrees
speakerPositions[idWaveFile::CHANNEL_INDEX_SIDE_LEFT ].Set( 0.0f, 1.0f ); // 90 degrees
speakerPositions[idWaveFile::CHANNEL_INDEX_SIDE_RIGHT ].Set( 0.0f, -1.0f ); // 270 degrees
speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT;
speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_LEFT] = idWaveFile::CHANNEL_INDEX_SIDE_LEFT;
speakerLeft[idWaveFile::CHANNEL_INDEX_SIDE_LEFT] = idWaveFile::CHANNEL_INDEX_BACK_LEFT;
speakerLeft[idWaveFile::CHANNEL_INDEX_BACK_LEFT] = idWaveFile::CHANNEL_INDEX_BACK_CENTER;
speakerLeft[idWaveFile::CHANNEL_INDEX_BACK_CENTER] = idWaveFile::CHANNEL_INDEX_BACK_RIGHT;
speakerLeft[idWaveFile::CHANNEL_INDEX_BACK_RIGHT] = idWaveFile::CHANNEL_INDEX_SIDE_RIGHT;
speakerLeft[idWaveFile::CHANNEL_INDEX_SIDE_RIGHT] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT;
speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER;
speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER;
speakerLeft[idWaveFile::CHANNEL_INDEX_FRONT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_CENTER;
speakerLeft[idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY] = idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY;
speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT;
speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_RIGHT] = idWaveFile::CHANNEL_INDEX_SIDE_RIGHT;
speakerRight[idWaveFile::CHANNEL_INDEX_SIDE_RIGHT] = idWaveFile::CHANNEL_INDEX_BACK_RIGHT;
speakerRight[idWaveFile::CHANNEL_INDEX_BACK_RIGHT] = idWaveFile::CHANNEL_INDEX_BACK_CENTER;
speakerRight[idWaveFile::CHANNEL_INDEX_BACK_CENTER] = idWaveFile::CHANNEL_INDEX_BACK_LEFT;
speakerRight[idWaveFile::CHANNEL_INDEX_BACK_LEFT] = idWaveFile::CHANNEL_INDEX_SIDE_LEFT;
speakerRight[idWaveFile::CHANNEL_INDEX_SIDE_LEFT] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT;
speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_LEFT] = idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER;
speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_LEFT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_RIGHT_CENTER;
speakerRight[idWaveFile::CHANNEL_INDEX_FRONT_CENTER] = idWaveFile::CHANNEL_INDEX_FRONT_CENTER;
speakerRight[idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY] = idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY;
dstChannels = outputChannels;
dstMask = channelMask;
// dstMap maps a destination channel to a speaker
// invMap maps a speaker to a destination channel
dstLFE = -1;
dstCenter = -1;
memset( dstMap, 0, sizeof( dstMap ) );
memset( invMap, 0, sizeof( invMap ) );
for ( int i = 0, c = 0; i < idWaveFile::CHANNEL_INDEX_MAX && c < MAX_CHANNELS_PER_VOICE; i++ ) {
if ( dstMask & BIT(i) ) {
if ( i == idWaveFile::CHANNEL_INDEX_LOW_FREQUENCY ) {
dstLFE = c;
}
if ( i == idWaveFile::CHANNEL_INDEX_FRONT_CENTER ) {
dstCenter = c;
}
dstMap[c] = i;
invMap[i] = c++;
} else {
// Remove this speaker from the chain
int right = speakerRight[i];
int left = speakerLeft[i];
speakerRight[left] = right;
speakerLeft[right] = left;
}
}
assert( ( dstLFE == -1 ) || ( ( dstMask & idWaveFile::CHANNEL_MASK_LOW_FREQUENCY ) != 0 ) );
assert( ( dstCenter == -1 ) || ( ( dstMask & idWaveFile::CHANNEL_MASK_FRONT_CENTER ) != 0 ) );
float omniChannels = (float)dstChannels;
if ( dstMask & idWaveFile::CHANNEL_MASK_LOW_FREQUENCY ) {
omniChannels -= 1.0f;
}
if ( dstMask & idWaveFile::CHANNEL_MASK_FRONT_CENTER ) {
omniChannels -= 1.0f;
}
if ( omniChannels > 0.0f ) {
omniLevel = 1.0f / omniChannels;
} else {
// This happens in mono mode
omniLevel = 1.0f;
}
}
/*
========================
idSoundVoice_Base::CalculateSurround
========================
*/
void idSoundVoice_Base::CalculateSurround( int srcChannels, float pLevelMatrix[ MAX_CHANNELS_PER_VOICE * MAX_CHANNELS_PER_VOICE ], float scale ) {
// Hack for mono
if ( dstChannels == 1 ) {
if ( srcChannels == 1 ) {
pLevelMatrix[ 0 ] = scale;
} else if ( srcChannels == 2 ) {
pLevelMatrix[ 0 ] = scale * 0.7071f;
pLevelMatrix[ 1 ] = scale * 0.7071f;
}
return;
}
#define MATINDEX( src, dst ) ( srcChannels * dst + src )
float subFraction = s_subFraction.GetFloat();
if ( srcChannels == 1 ) {
idVec2 p2 = position.ToVec2();
float centerFraction = centerChannel;
float sqrLength = p2.LengthSqr();
if ( sqrLength <= 0.01f ) {
// If we are on top of the listener, simply route all channels to each speaker equally
for ( int i = 0; i < dstChannels; i++ ) {
pLevelMatrix[MATINDEX( 0, i )] = omniLevel;
}
} else {
float invLength = idMath::InvSqrt( sqrLength );
float distance = ( invLength * sqrLength );
p2 *= invLength;
float spatialize = 1.0f;
if ( distance < innerRadius ) {
spatialize = distance / innerRadius;
}
float omni = omniLevel * ( 1.0f - spatialize );
if ( dstCenter != -1 ) {
centerFraction *= Max( 0.0f, p2.x );
spatialize *= ( 1.0f - centerFraction );
omni *= ( 1.0f - centerFraction );
}
float channelDots[MAX_CHANNELS_PER_VOICE] = { 0 };
for ( int i = 0; i < dstChannels; i++ ) {
// Calculate the contribution to each destination channel
channelDots[i] = speakerPositions[dstMap[i]] * p2;
}
// Find the speaker nearest to the sound
int channelA = 0;
for ( int i = 1; i < dstChannels; i++ ) {
if ( channelDots[i] > channelDots[channelA] ) {
channelA = i;
}
}
int speakerA = dstMap[channelA];
// Find the 2nd nearest speaker
int speakerB;
float speakerACross = ( speakerPositions[speakerA].x * p2.y ) - ( speakerPositions[speakerA].y * p2.x );
if ( speakerACross > 0.0f ) {
speakerB = speakerLeft[speakerA];
} else {
speakerB = speakerRight[speakerA];
}
int channelB = invMap[speakerB];
// Divide the amplitude between the 2 closest speakers
float distA = ( speakerPositions[speakerA] - p2 ).Length();
float distB = ( speakerPositions[speakerB] - p2 ).Length();
float distCinv = 1.0f / ( distA + distB );
float volumes[MAX_CHANNELS_PER_VOICE] = { 0 };
volumes[channelA] = ( distB * distCinv );
volumes[channelB] = ( distA * distCinv );
for ( int i = 0; i < dstChannels; i++ ) {
pLevelMatrix[MATINDEX( 0, i )] = ( volumes[i] * spatialize ) + omni;
}
}
if ( dstLFE != -1 ) {
pLevelMatrix[MATINDEX( 0, dstLFE )] = subFraction;
}
if ( dstCenter != -1 ) {
pLevelMatrix[MATINDEX( 0, dstCenter )] = centerFraction;
}
} else if ( srcChannels == 2 ) {
pLevelMatrix[ MATINDEX( 0, 0 ) ] = 1.0f;
pLevelMatrix[ MATINDEX( 1, 1 ) ] = 1.0f;
if ( dstLFE != -1 ) {
pLevelMatrix[ MATINDEX( 0, dstLFE ) ] = subFraction * 0.5f;
pLevelMatrix[ MATINDEX( 1, dstLFE ) ] = subFraction * 0.5f;
}
} else {
idLib::Warning( "We don't support %d channel sound files", srcChannels );
}
for ( int i = 0; i < srcChannels * dstChannels; i++ ) {
pLevelMatrix[ i ] *= scale;
}
}

101
neo/sound/SoundVoice.h Normal file
View File

@@ -0,0 +1,101 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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.
===========================================================================
*/
#ifndef __SOUNDVOICE_H__
#define __SOUNDVOICE_H__
/*
================================================
idSoundVoice_Base
================================================
*/
class idSoundVoice_Base {
public:
idSoundVoice_Base();
static void InitSurround( int outputChannels, int channelMask );
void CalculateSurround( int srcChannels, float pLevelMatrix[ MAX_CHANNELS_PER_VOICE * MAX_CHANNELS_PER_VOICE ], float scale );
void SetPosition( const idVec3 & p ) { position = p; }
void SetGain( float g ) { gain = g; }
void SetCenterChannel( float c ) { centerChannel = c; }
void SetPitch( float p ) { pitch = p; }
void SetInnerRadius( float r ) { innerRadius = r; }
void SetChannelMask( uint32 mask ) { channelMask = mask; }
const idSoundSample * GetCurrentSample();
// Controls the low pass filter, where 0.0f = no filtering, 1.0f = full filter
void SetOcclusion( float f ) { occlusion = f; }
float GetGain() { return gain; }
float GetPitch() { return pitch; }
protected:
idVec3 position; // Position of the sound relative to listener
float gain; // Volume (0-1)
float centerChannel; // Value (0-1) which indicates how much of this voice goes to the center channel
float pitch; // Pitch multiplier
float innerRadius; // Anything closer than this is omni
float occlusion; // How much of this sound is occluded (0-1)
uint32 channelMask; // Set to override the default channel mask
// These are some setting used to do SSF_DISTANCE_BASED_STERO blending
float innerSampleRangeSqr;
float outerSampleRangeSqr;
idList< idSoundSample *, TAG_AUDIO> samples;
// These are constants which are initialized with InitSurround
//-------------------------------------------------------------
static idVec2 speakerPositions[idWaveFile::CHANNEL_INDEX_MAX];
// This is to figure out which speakers are "next to" this one
static int speakerLeft[idWaveFile::CHANNEL_INDEX_MAX];
static int speakerRight[idWaveFile::CHANNEL_INDEX_MAX];
// Number of channels in the output hardware
static int dstChannels;
// Mask indicating which speakers exist in the hardware configuration
static int dstMask;
// dstMap maps a destination channel to a speaker
// invMap maps a speaker to a destination channel
static int dstCenter;
static int dstLFE;
static int dstMap[MAX_CHANNELS_PER_VOICE];
static int invMap[idWaveFile::CHANNEL_INDEX_MAX];
// specifies what volume to specify for each channel when a speaker is omni
static float omniLevel;
};
#endif

516
neo/sound/WaveFile.cpp Normal file
View File

@@ -0,0 +1,516 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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<waveFmt_t::basic_t> 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<waveFmt_t::extra_t::adpcm_t> 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<waveFmt_t::extra_t::xma2_t> 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<waveFmt_t::extra_t::extensible_t> 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<waveFmt_t::basic_t> 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<waveFmt_t::extra_t::adpcm_t> 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<waveFmt_t::extra_t::xma2_t> 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<waveFmt_t::extra_t::extensible_t> 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<waveFmt_t::basic_t> 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 );
}

236
neo/sound/WaveFile.h Normal file
View File

@@ -0,0 +1,236 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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.
===========================================================================
*/
#ifndef __WAVEFILE_H
#define __WAVEFILE_H
/*
================================================================================================
Contains the WaveFile declaration.
================================================================================================
*/
/*
================================================
idWaveFile is used for reading generic RIFF WAVE files.
================================================
*/
class idWaveFile {
public:
ID_INLINE idWaveFile();
ID_INLINE ~idWaveFile();
bool Open( const char * filename );
void Close();
uint32 SeekToChunk( uint32 id );
size_t Read( void * buffer, size_t len ) { return file->Read( buffer, len ); }
uint32 GetChunkOffset( uint32 id );
ID_TIME_T Timestamp() { return file->Timestamp(); }
const char * Name() { return ( file == NULL ? "" : file->GetName() ); }
// This maps to the channel mask in waveFmtExtensible_t
enum {
CHANNEL_INDEX_FRONT_LEFT,
CHANNEL_INDEX_FRONT_RIGHT,
CHANNEL_INDEX_FRONT_CENTER,
CHANNEL_INDEX_LOW_FREQUENCY,
CHANNEL_INDEX_BACK_LEFT,
CHANNEL_INDEX_BACK_RIGHT,
CHANNEL_INDEX_FRONT_LEFT_CENTER,
CHANNEL_INDEX_FRONT_RIGHT_CENTER,
CHANNEL_INDEX_BACK_CENTER,
CHANNEL_INDEX_SIDE_LEFT,
CHANNEL_INDEX_SIDE_RIGHT,
CHANNEL_INDEX_MAX
};
enum {
CHANNEL_MASK_FRONT_LEFT = BIT( CHANNEL_INDEX_FRONT_LEFT ),
CHANNEL_MASK_FRONT_RIGHT = BIT( CHANNEL_INDEX_FRONT_RIGHT ),
CHANNEL_MASK_FRONT_CENTER = BIT( CHANNEL_INDEX_FRONT_CENTER ),
CHANNEL_MASK_LOW_FREQUENCY = BIT( CHANNEL_INDEX_LOW_FREQUENCY ),
CHANNEL_MASK_BACK_LEFT = BIT( CHANNEL_INDEX_BACK_LEFT ),
CHANNEL_MASK_BACK_RIGHT = BIT( CHANNEL_INDEX_BACK_RIGHT ),
CHANNEL_MASK_FRONT_LEFT_CENTER = BIT( CHANNEL_INDEX_FRONT_LEFT_CENTER ),
CHANNEL_MASK_FRONT_RIGHT_CENTER = BIT( CHANNEL_INDEX_FRONT_RIGHT_CENTER ),
CHANNEL_MASK_BACK_CENTER = BIT( CHANNEL_INDEX_BACK_CENTER ),
CHANNEL_MASK_SIDE_LEFT = BIT( CHANNEL_INDEX_SIDE_LEFT ),
CHANNEL_MASK_SIDE_RIGHT = BIT( CHANNEL_INDEX_SIDE_RIGHT ),
CHANNEL_MASK_ALL = BIT( CHANNEL_INDEX_MAX ) - 1,
};
// This matches waveFmt_t::formatTag
// These are the only wave formats that we understand
enum {
FORMAT_UNKNOWN = 0x0000,
FORMAT_PCM = 0x0001,
FORMAT_ADPCM = 0x0002,
FORMAT_XMA2 = 0x0166,
FORMAT_EXTENSIBLE = 0xFFFF,
};
#pragma pack( push, 1 )
struct waveFmt_t {
static const uint32 id = 'fmt ';
// This is the basic data we'd expect to see in any valid wave file
struct basic_t {
uint16 formatTag;
uint16 numChannels;
uint32 samplesPerSec;
uint32 avgBytesPerSec;
uint16 blockSize;
uint16 bitsPerSample;
} basic;
// Some wav file formats have extra data after the basic header
uint16 extraSize;
// We have a few known formats that we handle:
union extra_t {
// Valid if basic.formatTag == FORMAT_EXTENSIBLE
struct extensible_t {
uint16 validBitsPerSample; // Valid bits in each sample container
uint32 channelMask; // Positions of the audio channels
struct guid_t {
uint32 data1;
uint16 data2;
uint16 data3;
uint16 data4;
byte data5[ 6 ];
} subFormat; // Format identifier GUID
} extensible;
// Valid if basic.formatTag == FORMAT_ADPCM
// The microsoft ADPCM struct has a zero-sized array at the end
// but the array is always 7 entries, so we set it to that size
// so we can embed it in our format union. Otherwise, the struct
// is exactly the same as the one in audiodefs.h
struct adpcm_t {
uint16 samplesPerBlock;
uint16 numCoef;
struct adpcmcoef_t {
short coef1;
short coef2;
} aCoef[7]; // Always 7 coefficient pairs for MS ADPCM
} adpcm;
// Valid if basic.formatTag == FORMAT_XMA2
struct xma2_t {
uint16 numStreams; // Number of audio streams (1 or 2 channels each)
uint32 channelMask; // matches the CHANNEL_MASK enum above
uint32 samplesEncoded; // Total number of PCM samples the file decodes to
uint32 bytesPerBlock; // XMA block size (but the last one may be shorter)
uint32 playBegin; // First valid sample in the decoded audio
uint32 playLength; // Length of the valid part of the decoded audio
uint32 loopBegin; // Beginning of the loop region in decoded sample terms
uint32 loopLength; // Length of the loop region in decoded sample terms
byte loopCount; // Number of loop repetitions; 255 = infinite
byte encoderVersion; // Version of XMA encoder that generated the file
uint16 blockCount; // XMA blocks in file (and entries in its seek table)
} xma2;
} extra;
};
#pragma pack( pop )
struct dataChunk_t {
static const uint32 id = 'data';
uint32 size;
void * data;
};
struct formatChunk_t {
static const uint32 id = 'fmt ';
uint32 size;
uint16 compressionCode;
uint16 numChannels;
uint32 sampleRate;
uint32 averageBytesPerSecond;
uint16 blockAlign;
uint16 bitsPerSample;
uint16 numExtraFormatByte;
};
struct samplerChunk_t {
static const uint32 id = 'smpl';
uint32 manufacturer; // ignored
uint32 product; // ignored
uint32 samplePeriod; // ignored (normally 1000000000/samplesPerSec)
uint32 MIDIUnityNote; // ignored
uint32 MIDIPitchFraction; // ignored
uint32 SMPTEFormat; // ignored
uint32 SMPTEOffset; // ignored
uint32 numSampleLoops; // number of samples in wave file
uint32 extraSamplerData; // ignored, should always be 0
};
struct sampleData_t {
uint32 identifier; // ignored
uint32 type; // 0 for loop 33 multi-sample sample type
uint32 start; // start of the loop point
uint32 end; // end of the loop point
uint32 fraction; // ignored
uint32 playCount; // ignored
};
const char * ReadWaveFormat( waveFmt_t & waveFmt );
static bool ReadWaveFormatDirect( waveFmt_t & format, idFile *file );
static bool WriteWaveFormatDirect( waveFmt_t & format, idFile *file );
static bool WriteSampleDataDirect( idList< sampleData_t > & sampleData, idFile *file );
static bool WriteDataDirect( char * _data, uint32 size, idFile * file );
static bool WriteHeaderDirect( uint32 fileSize, idFile * file );
bool ReadLoopData( int & start, int & end );
private:
idFile * file;
struct chunk_t {
uint32 id;
uint32 size;
uint32 offset;
};
idStaticList< chunk_t, 32 > chunks;
};
/*
========================
idWaveFile::idWaveFile
========================
*/
ID_INLINE idWaveFile::idWaveFile() : file( NULL ) {
}
/*
========================
idWaveFile::~idWaveFile
========================
*/
ID_INLINE idWaveFile::~idWaveFile() {
Close();
}
#endif // !__WAVEFILE_H__

View File

@@ -0,0 +1,542 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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"
#include "../snd_local.h"
#include "../../../doomclassic/doom/i_sound.h"
idCVar s_showLevelMeter( "s_showLevelMeter", "0", CVAR_BOOL|CVAR_ARCHIVE, "Show VU meter" );
idCVar s_meterTopTime( "s_meterTopTime", "1000", CVAR_INTEGER|CVAR_ARCHIVE, "How long (in milliseconds) peaks are displayed on the VU meter" );
idCVar s_meterPosition( "s_meterPosition", "100 100 20 200", CVAR_ARCHIVE, "VU meter location (x y w h)" );
idCVar s_device( "s_device", "-1", CVAR_INTEGER|CVAR_ARCHIVE, "Which audio device to use (listDevices to list, -1 for default)" );
idCVar s_showPerfData( "s_showPerfData", "0", CVAR_BOOL, "Show XAudio2 Performance data" );
extern idCVar s_volume_dB;
/*
========================
idSoundHardware_XAudio2::idSoundHardware_XAudio2
========================
*/
idSoundHardware_XAudio2::idSoundHardware_XAudio2() {
pXAudio2 = NULL;
pMasterVoice = NULL;
pSubmixVoice = NULL;
vuMeterRMS = NULL;
vuMeterPeak = NULL;
outputChannels = 0;
channelMask = 0;
voices.SetNum( 0 );
zombieVoices.SetNum( 0 );
freeVoices.SetNum( 0 );
lastResetTime = 0;
}
void listDevices_f( const idCmdArgs & args ) {
IXAudio2 * pXAudio2 = soundSystemLocal.hardware.GetIXAudio2();
if ( pXAudio2 == NULL ) {
idLib::Warning( "No xaudio object" );
return;
}
UINT32 deviceCount = 0;
if ( pXAudio2->GetDeviceCount( &deviceCount ) != S_OK || deviceCount == 0 ) {
idLib::Warning( "No audio devices found" );
return;
}
for ( unsigned int i = 0; i < deviceCount; i++ ) {
XAUDIO2_DEVICE_DETAILS deviceDetails;
if ( pXAudio2->GetDeviceDetails( i, &deviceDetails ) != S_OK ) {
continue;
}
idStaticList< const char *, 5 > roles;
if ( deviceDetails.Role & DefaultConsoleDevice ) {
roles.Append( "Console Device" );
}
if ( deviceDetails.Role & DefaultMultimediaDevice ) {
roles.Append( "Multimedia Device" );
}
if ( deviceDetails.Role & DefaultCommunicationsDevice ) {
roles.Append( "Communications Device" );
}
if ( deviceDetails.Role & DefaultGameDevice ) {
roles.Append( "Game Device" );
}
idStaticList< const char *, 11 > channelNames;
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_LEFT ) {
channelNames.Append( "Front Left" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_RIGHT ) {
channelNames.Append( "Front Right" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_CENTER ) {
channelNames.Append( "Front Center" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_LOW_FREQUENCY ) {
channelNames.Append( "Low Frequency" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_BACK_LEFT ) {
channelNames.Append( "Back Left" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_BACK_RIGHT ) {
channelNames.Append( "Back Right" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_LEFT_OF_CENTER ) {
channelNames.Append( "Front Left of Center" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_FRONT_RIGHT_OF_CENTER ) {
channelNames.Append( "Front Right of Center" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_BACK_CENTER ) {
channelNames.Append( "Back Center" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_SIDE_LEFT ) {
channelNames.Append( "Side Left" );
}
if ( deviceDetails.OutputFormat.dwChannelMask & SPEAKER_SIDE_RIGHT ) {
channelNames.Append( "Side Right" );
}
char mbcsDisplayName[ 256 ];
wcstombs( mbcsDisplayName, deviceDetails.DisplayName, sizeof( mbcsDisplayName ) );
idLib::Printf( "%3d: %s\n", i, mbcsDisplayName );
idLib::Printf( " %d channels, %d Hz\n", deviceDetails.OutputFormat.Format.nChannels, deviceDetails.OutputFormat.Format.nSamplesPerSec );
if ( channelNames.Num() != deviceDetails.OutputFormat.Format.nChannels ) {
idLib::Printf( S_COLOR_YELLOW "WARNING: " S_COLOR_RED "Mismatch between # of channels and channel mask\n" );
}
if ( channelNames.Num() == 1 ) {
idLib::Printf( " %s\n", channelNames[0] );
} else if ( channelNames.Num() == 2 ) {
idLib::Printf( " %s and %s\n", channelNames[0], channelNames[1] );
} else if ( channelNames.Num() > 2 ) {
idLib::Printf( " %s", channelNames[0] );
for ( int i = 1; i < channelNames.Num() - 1; i++ ) {
idLib::Printf( ", %s", channelNames[i] );
}
idLib::Printf( ", and %s\n", channelNames[channelNames.Num() - 1] );
}
if ( roles.Num() == 1 ) {
idLib::Printf( " Default %s\n", roles[0] );
} else if ( roles.Num() == 2 ) {
idLib::Printf( " Default %s and %s\n", roles[0], roles[1] );
} else if ( roles.Num() > 2 ) {
idLib::Printf( " Default %s", roles[0] );
for ( int i = 1; i < roles.Num() - 1; i++ ) {
idLib::Printf( ", %s", roles[i] );
}
idLib::Printf( ", and %s\n", roles[roles.Num() - 1] );
}
}
}
/*
========================
idSoundHardware_XAudio2::Init
========================
*/
void idSoundHardware_XAudio2::Init() {
cmdSystem->AddCommand( "listDevices", listDevices_f, 0, "Lists the connected sound devices", NULL );
DWORD xAudioCreateFlags = 0;
#ifdef _DEBUG
xAudioCreateFlags |= XAUDIO2_DEBUG_ENGINE;
#endif
XAUDIO2_PROCESSOR xAudioProcessor = XAUDIO2_DEFAULT_PROCESSOR;
if ( FAILED( XAudio2Create( &pXAudio2, xAudioCreateFlags, xAudioProcessor ) ) ) {
if ( xAudioCreateFlags & XAUDIO2_DEBUG_ENGINE ) {
// in case the debug engine isn't installed
xAudioCreateFlags &= ~XAUDIO2_DEBUG_ENGINE;
if ( FAILED( XAudio2Create( &pXAudio2, xAudioCreateFlags, xAudioProcessor ) ) ) {
idLib::FatalError( "Failed to create XAudio2 engine. Try installing the latest DirectX." );
return;
}
} else {
idLib::FatalError( "Failed to create XAudio2 engine. Try installing the latest DirectX." );
return;
}
}
#ifdef _DEBUG
XAUDIO2_DEBUG_CONFIGURATION debugConfiguration = { 0 };
debugConfiguration.TraceMask = XAUDIO2_LOG_WARNINGS;
debugConfiguration.BreakMask = XAUDIO2_LOG_ERRORS;
pXAudio2->SetDebugConfiguration( &debugConfiguration );
#endif
// Register the sound engine callback
pXAudio2->RegisterForCallbacks( &soundEngineCallback );
soundEngineCallback.hardware = this;
UINT32 deviceCount = 0;
if ( pXAudio2->GetDeviceCount( &deviceCount ) != S_OK || deviceCount == 0 ) {
idLib::Warning( "No audio devices found" );
pXAudio2->Release();
pXAudio2 = NULL;
return;
}
idCmdArgs args;
listDevices_f( args );
int preferredDevice = s_device.GetInteger();
if ( preferredDevice < 0 || preferredDevice >= (int)deviceCount ) {
int preferredChannels = 0;
for ( unsigned int i = 0; i < deviceCount; i++ ) {
XAUDIO2_DEVICE_DETAILS deviceDetails;
if ( pXAudio2->GetDeviceDetails( i, &deviceDetails ) != S_OK ) {
continue;
}
if ( deviceDetails.Role & DefaultGameDevice ) {
// if we find a device the user marked as their preferred 'game' device, then always use that
preferredDevice = i;
preferredChannels = deviceDetails.OutputFormat.Format.nChannels;
break;
}
if ( deviceDetails.OutputFormat.Format.nChannels > preferredChannels ) {
preferredDevice = i;
preferredChannels = deviceDetails.OutputFormat.Format.nChannels;
}
}
}
idLib::Printf( "Using device %d\n", preferredDevice );
XAUDIO2_DEVICE_DETAILS deviceDetails;
if ( pXAudio2->GetDeviceDetails( preferredDevice, &deviceDetails ) != S_OK ) {
// One way this could happen is if a device is removed between the loop and this line of code
// Highly unlikely but possible
idLib::Warning( "Failed to get device details" );
pXAudio2->Release();
pXAudio2 = NULL;
return;
}
DWORD outputSampleRate = 44100; // Max( (DWORD)XAUDIO2FX_REVERB_MIN_FRAMERATE, Min( (DWORD)XAUDIO2FX_REVERB_MAX_FRAMERATE, deviceDetails.OutputFormat.Format.nSamplesPerSec ) );
if ( FAILED( pXAudio2->CreateMasteringVoice( &pMasterVoice, XAUDIO2_DEFAULT_CHANNELS, outputSampleRate, 0, preferredDevice, NULL ) ) ) {
idLib::Warning( "Failed to create master voice" );
pXAudio2->Release();
pXAudio2 = NULL;
return;
}
pMasterVoice->SetVolume( DBtoLinear( s_volume_dB.GetFloat() ) );
outputChannels = deviceDetails.OutputFormat.Format.nChannels;
channelMask = deviceDetails.OutputFormat.dwChannelMask;
idSoundVoice::InitSurround( outputChannels, channelMask );
// ---------------------
// Initialize the Doom classic sound system.
// ---------------------
I_InitSoundHardware( outputChannels, channelMask );
// ---------------------
// Create VU Meter Effect
// ---------------------
IUnknown * vuMeter = NULL;
XAudio2CreateVolumeMeter( &vuMeter, 0 );
XAUDIO2_EFFECT_DESCRIPTOR descriptor;
descriptor.InitialState = true;
descriptor.OutputChannels = outputChannels;
descriptor.pEffect = vuMeter;
XAUDIO2_EFFECT_CHAIN chain;
chain.EffectCount = 1;
chain.pEffectDescriptors = &descriptor;
pMasterVoice->SetEffectChain( &chain );
vuMeter->Release();
// ---------------------
// Create VU Meter Graph
// ---------------------
vuMeterRMS = console->CreateGraph( outputChannels );
vuMeterPeak = console->CreateGraph( outputChannels );
vuMeterRMS->Enable( false );
vuMeterPeak->Enable( false );
memset( vuMeterPeakTimes, 0, sizeof( vuMeterPeakTimes ) );
vuMeterPeak->SetFillMode( idDebugGraph::GRAPH_LINE );
vuMeterPeak->SetBackgroundColor( idVec4( 0.0f, 0.0f, 0.0f, 0.0f ) );
vuMeterRMS->AddGridLine( 0.500f, idVec4( 0.5f, 0.5f, 0.5f, 1.0f ) );
vuMeterRMS->AddGridLine( 0.250f, idVec4( 0.5f, 0.5f, 0.5f, 1.0f ) );
vuMeterRMS->AddGridLine( 0.125f, idVec4( 0.5f, 0.5f, 0.5f, 1.0f ) );
const char * channelNames[] = { "L", "R", "C", "S", "Lb", "Rb", "Lf", "Rf", "Cb", "Ls", "Rs" };
for ( int i = 0, ci = 0; ci < sizeof( channelNames ) / sizeof( channelNames[0] ); ci++ ) {
if ( ( channelMask & BIT( ci ) ) == 0 ) {
continue;
}
vuMeterRMS->SetLabel( i, channelNames[ ci ] );
i++;
}
// ---------------------
// Create submix buffer
// ---------------------
if ( FAILED( pXAudio2->CreateSubmixVoice( &pSubmixVoice, 1, outputSampleRate, 0, 0, NULL, NULL ) ) ) {
idLib::FatalError( "Failed to create submix voice" );
}
// XAudio doesn't really impose a maximum number of voices
voices.SetNum( voices.Max() );
freeVoices.SetNum( voices.Max() );
zombieVoices.SetNum( 0 );
for ( int i = 0; i < voices.Num(); i++ ) {
freeVoices[i] = &voices[i];
}
}
/*
========================
idSoundHardware_XAudio2::Shutdown
========================
*/
void idSoundHardware_XAudio2::Shutdown() {
for ( int i = 0; i < voices.Num(); i++ ) {
voices[ i ].DestroyInternal();
}
voices.Clear();
freeVoices.Clear();
zombieVoices.Clear();
// ---------------------
// Shutdown the Doom classic sound system.
// ---------------------
I_ShutdownSoundHardware();
if ( pXAudio2 != NULL ) {
// Unregister the sound engine callback
pXAudio2->UnregisterForCallbacks( &soundEngineCallback );
}
if ( pSubmixVoice != NULL ) {
pSubmixVoice->DestroyVoice();
pSubmixVoice = NULL;
}
if ( pMasterVoice != NULL ) {
// release the vu meter effect
pMasterVoice->SetEffectChain( NULL );
pMasterVoice->DestroyVoice();
pMasterVoice = NULL;
}
if ( pXAudio2 != NULL ) {
XAUDIO2_PERFORMANCE_DATA perfData;
pXAudio2->GetPerformanceData( &perfData );
idLib::Printf( "Final pXAudio2 performanceData: Voices: %d/%d CPU: %.2f%% Mem: %dkb\n", perfData.ActiveSourceVoiceCount, perfData.TotalSourceVoiceCount, perfData.AudioCyclesSinceLastQuery / (float)perfData.TotalCyclesSinceLastQuery, perfData.MemoryUsageInBytes / 1024 );
pXAudio2->Release();
pXAudio2 = NULL;
}
if ( vuMeterRMS != NULL ) {
console->DestroyGraph( vuMeterRMS );
vuMeterRMS = NULL;
}
if ( vuMeterPeak != NULL ) {
console->DestroyGraph( vuMeterPeak );
vuMeterPeak = NULL;
}
}
/*
========================
idSoundHardware_XAudio2::AllocateVoice
========================
*/
idSoundVoice * idSoundHardware_XAudio2::AllocateVoice( const idSoundSample * leadinSample, const idSoundSample * loopingSample ) {
if ( leadinSample == NULL ) {
return NULL;
}
if ( loopingSample != NULL ) {
if ( ( leadinSample->format.basic.formatTag != loopingSample->format.basic.formatTag ) || ( leadinSample->format.basic.numChannels != loopingSample->format.basic.numChannels ) ) {
idLib::Warning( "Leadin/looping format mismatch: %s & %s", leadinSample->GetName(), loopingSample->GetName() );
loopingSample = NULL;
}
}
// Try to find a free voice that matches the format
// But fallback to the last free voice if none match the format
idSoundVoice * voice = NULL;
for ( int i = 0; i < freeVoices.Num(); i++ ) {
if ( freeVoices[i]->IsPlaying() ) {
continue;
}
voice = (idSoundVoice *)freeVoices[i];
if ( voice->CompatibleFormat( (idSoundSample_XAudio2*)leadinSample ) ) {
break;
}
}
if ( voice != NULL ) {
voice->Create( leadinSample, loopingSample );
freeVoices.Remove( voice );
return voice;
}
return NULL;
}
/*
========================
idSoundHardware_XAudio2::FreeVoice
========================
*/
void idSoundHardware_XAudio2::FreeVoice( idSoundVoice * voice ) {
voice->Stop();
// Stop() is asyncronous, so we won't flush bufferes until the
// voice on the zombie channel actually returns !IsPlaying()
zombieVoices.Append( voice );
}
/*
========================
idSoundHardware_XAudio2::Update
========================
*/
void idSoundHardware_XAudio2::Update() {
if ( pXAudio2 == NULL ) {
int nowTime = Sys_Milliseconds();
if ( lastResetTime + 1000 < nowTime ) {
lastResetTime = nowTime;
Init();
}
return;
}
if ( soundSystem->IsMuted() ) {
pMasterVoice->SetVolume( 0.0f, OPERATION_SET );
} else {
pMasterVoice->SetVolume( DBtoLinear( s_volume_dB.GetFloat() ), OPERATION_SET );
}
pXAudio2->CommitChanges( XAUDIO2_COMMIT_ALL );
// IXAudio2SourceVoice::Stop() has been called for every sound on the
// zombie list, but it is documented as asyncronous, so we have to wait
// until it actually reports that it is no longer playing.
for ( int i = 0; i < zombieVoices.Num(); i++ ) {
zombieVoices[i]->FlushSourceBuffers();
if ( !zombieVoices[i]->IsPlaying() ) {
freeVoices.Append( zombieVoices[i] );
zombieVoices.RemoveIndexFast( i );
i--;
} else {
static int playingZombies;
playingZombies++;
}
}
if ( s_showPerfData.GetBool() ) {
XAUDIO2_PERFORMANCE_DATA perfData;
pXAudio2->GetPerformanceData( &perfData );
idLib::Printf( "Voices: %d/%d CPU: %.2f%% Mem: %dkb\n", perfData.ActiveSourceVoiceCount, perfData.TotalSourceVoiceCount, perfData.AudioCyclesSinceLastQuery / (float)perfData.TotalCyclesSinceLastQuery, perfData.MemoryUsageInBytes / 1024 );
}
if ( vuMeterRMS == NULL ) {
// Init probably hasn't been called yet
return;
}
vuMeterRMS->Enable( s_showLevelMeter.GetBool() );
vuMeterPeak->Enable( s_showLevelMeter.GetBool() );
if ( !s_showLevelMeter.GetBool() ) {
pMasterVoice->DisableEffect( 0 );
return;
} else {
pMasterVoice->EnableEffect( 0 );
}
float peakLevels[ 8 ];
float rmsLevels[ 8 ];
XAUDIO2FX_VOLUMEMETER_LEVELS levels;
levels.ChannelCount = outputChannels;
levels.pPeakLevels = peakLevels;
levels.pRMSLevels = rmsLevels;
if ( levels.ChannelCount > 8 ) {
levels.ChannelCount = 8;
}
pMasterVoice->GetEffectParameters( 0, &levels, sizeof( levels ) );
int currentTime = Sys_Milliseconds();
for ( int i = 0; i < outputChannels; i++ ) {
if ( vuMeterPeakTimes[i] < currentTime ) {
vuMeterPeak->SetValue( i, vuMeterPeak->GetValue( i ) * 0.9f, colorRed );
}
}
float width = 20.0f;
float height = 200.0f;
float left = 100.0f;
float top = 100.0f;
sscanf( s_meterPosition.GetString(), "%f %f %f %f", &left, &top, &width, &height );
vuMeterRMS->SetPosition( left, top, width * levels.ChannelCount, height );
vuMeterPeak->SetPosition( left, top, width * levels.ChannelCount, height );
for ( uint32 i = 0; i < levels.ChannelCount; i++ ) {
vuMeterRMS->SetValue( i, rmsLevels[ i ], idVec4( 0.5f, 1.0f, 0.0f, 1.00f ) );
if ( peakLevels[ i ] >= vuMeterPeak->GetValue( i ) ) {
vuMeterPeak->SetValue( i, peakLevels[ i ], colorRed );
vuMeterPeakTimes[i] = currentTime + s_meterTopTime.GetInteger();
}
}
}
/*
================================================
idSoundEngineCallback
================================================
*/
/*
========================
idSoundEngineCallback::OnCriticalError
========================
*/
void idSoundEngineCallback::OnCriticalError( HRESULT Error ) {
soundSystemLocal.SetNeedsRestart();
}

View File

@@ -0,0 +1,112 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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.
===========================================================================
*/
#ifndef __XA_SOUNDHARDWARE_H__
#define __XA_SOUNDHARDWARE_H__
class idSoundSample_XAudio2;
class idSoundVoice_XAudio2;
/*
================================================
idSoundEngineCallback
================================================
*/
class idSoundEngineCallback : public IXAudio2EngineCallback {
public:
idSoundHardware_XAudio2 * hardware;
private:
// Called by XAudio2 just before an audio processing pass begins.
STDMETHOD_( void, OnProcessingPassStart ) ( THIS ) {}
// Called just after an audio processing pass ends.
STDMETHOD_( void, OnProcessingPassEnd ) ( THIS ) {}
// Called in the event of a critical system error which requires XAudio2
// to be closed down and restarted. The error code is given in Error.
STDMETHOD_( void, OnCriticalError ) ( THIS_ HRESULT Error );
};
/*
================================================
idSoundHardware_XAudio2
================================================
*/
class idSoundHardware_XAudio2 {
public:
idSoundHardware_XAudio2();
void Init();
void Shutdown();
void Update();
idSoundVoice * AllocateVoice( const idSoundSample * leadinSample, const idSoundSample * loopingSample );
void FreeVoice( idSoundVoice * voice );
// video playback needs this
IXAudio2 * GetIXAudio2() const { return pXAudio2; };
int GetNumZombieVoices() const { return zombieVoices.Num(); }
int GetNumFreeVoices() const { return freeVoices.Num(); }
protected:
friend class idSoundSample_XAudio2;
friend class idSoundVoice_XAudio2;
private:
IXAudio2 * pXAudio2;
IXAudio2MasteringVoice * pMasterVoice;
IXAudio2SubmixVoice * pSubmixVoice;
idSoundEngineCallback soundEngineCallback;
int lastResetTime;
int outputChannels;
int channelMask;
idDebugGraph * vuMeterRMS;
idDebugGraph * vuMeterPeak;
int vuMeterPeakTimes[ 8 ];
// Can't stop and start a voice on the same frame, so we have to double this to handle the worst case scenario of stopping all voices and starting a full new set
idStaticList<idSoundVoice_XAudio2, MAX_HARDWARE_VOICES * 2 > voices;
idStaticList<idSoundVoice_XAudio2 *, MAX_HARDWARE_VOICES * 2 > zombieVoices;
idStaticList<idSoundVoice_XAudio2 *, MAX_HARDWARE_VOICES * 2 > freeVoices;
};
/*
================================================
idSoundHardware
================================================
*/
class idSoundHardware : public idSoundHardware_XAudio2 {
};
#endif

View File

@@ -0,0 +1,487 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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"
#include "../snd_local.h"
extern idCVar s_useCompression;
extern idCVar s_noSound;
#define GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( x ) x
const uint32 SOUND_MAGIC_IDMSA = 0x6D7A7274;
extern idCVar sys_lang;
/*
========================
AllocBuffer
========================
*/
static void * AllocBuffer( int size, const char * name ) {
return Mem_Alloc( size, TAG_AUDIO );
}
/*
========================
FreeBuffer
========================
*/
static void FreeBuffer( void * p ) {
return Mem_Free( p );
}
/*
========================
idSoundSample_XAudio2::idSoundSample_XAudio2
========================
*/
idSoundSample_XAudio2::idSoundSample_XAudio2() {
timestamp = FILE_NOT_FOUND_TIMESTAMP;
loaded = false;
neverPurge = false;
levelLoadReferenced = false;
memset( &format, 0, sizeof( format ) );
totalBufferSize = 0;
playBegin = 0;
playLength = 0;
lastPlayedTime = 0;
}
/*
========================
idSoundSample_XAudio2::~idSoundSample_XAudio2
========================
*/
idSoundSample_XAudio2::~idSoundSample_XAudio2() {
FreeData();
}
/*
========================
idSoundSample_XAudio2::WriteGeneratedSample
========================
*/
void idSoundSample_XAudio2::WriteGeneratedSample( idFile *fileOut ) {
fileOut->WriteBig( SOUND_MAGIC_IDMSA );
fileOut->WriteBig( timestamp );
fileOut->WriteBig( loaded );
fileOut->WriteBig( playBegin );
fileOut->WriteBig( playLength );
idWaveFile::WriteWaveFormatDirect( format, fileOut );
fileOut->WriteBig( ( int )amplitude.Num() );
fileOut->Write( amplitude.Ptr(), amplitude.Num() );
fileOut->WriteBig( totalBufferSize );
fileOut->WriteBig( ( int )buffers.Num() );
for ( int i = 0; i < buffers.Num(); i++ ) {
fileOut->WriteBig( buffers[ i ].numSamples );
fileOut->WriteBig( buffers[ i ].bufferSize );
fileOut->Write( buffers[ i ].buffer, buffers[ i ].bufferSize );
};
}
/*
========================
idSoundSample_XAudio2::WriteAllSamples
========================
*/
void idSoundSample_XAudio2::WriteAllSamples( const idStr &sampleName ) {
idSoundSample_XAudio2 * samplePC = new idSoundSample_XAudio2();
{
idStrStatic< MAX_OSPATH > inName = sampleName;
inName.Append( ".msadpcm" );
idStrStatic< MAX_OSPATH > inName2 = sampleName;
inName2.Append( ".wav" );
idStrStatic< MAX_OSPATH > outName = "generated/";
outName.Append( sampleName );
outName.Append( ".idwav" );
if ( samplePC->LoadWav( inName ) || samplePC->LoadWav( inName2 ) ) {
idFile *fileOut = fileSystem->OpenFileWrite( outName, "fs_basepath" );
samplePC->WriteGeneratedSample( fileOut );
delete fileOut;
}
}
delete samplePC;
}
/*
========================
idSoundSample_XAudio2::LoadGeneratedSound
========================
*/
bool idSoundSample_XAudio2::LoadGeneratedSample( const idStr &filename ) {
idFileLocal fileIn( fileSystem->OpenFileReadMemory( filename ) );
if ( fileIn != NULL ) {
uint32 magic;
fileIn->ReadBig( magic );
fileIn->ReadBig( timestamp );
fileIn->ReadBig( loaded );
fileIn->ReadBig( playBegin );
fileIn->ReadBig( playLength );
idWaveFile::ReadWaveFormatDirect( format, fileIn );
int num;
fileIn->ReadBig( num );
amplitude.Clear();
amplitude.SetNum( num );
fileIn->Read( amplitude.Ptr(), amplitude.Num() );
fileIn->ReadBig( totalBufferSize );
fileIn->ReadBig( num );
buffers.SetNum( num );
for ( int i = 0; i < num; i++ ) {
fileIn->ReadBig( buffers[ i ].numSamples );
fileIn->ReadBig( buffers[ i ].bufferSize );
buffers[ i ].buffer = AllocBuffer( buffers[ i ].bufferSize, GetName() );
fileIn->Read( buffers[ i ].buffer, buffers[ i ].bufferSize );
buffers[ i ].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[ i ].buffer );
}
return true;
}
return false;
}
/*
========================
idSoundSample_XAudio2::Load
========================
*/
void idSoundSample_XAudio2::LoadResource() {
FreeData();
if ( idStr::Icmpn( GetName(), "_default", 8 ) == 0 ) {
MakeDefault();
return;
}
if ( s_noSound.GetBool() ) {
MakeDefault();
return;
}
loaded = false;
for ( int i = 0; i < 2; i++ ) {
idStrStatic< MAX_OSPATH > sampleName = GetName();
if ( ( i == 0 ) && !sampleName.Replace( "/vo/", va( "/vo/%s/", sys_lang.GetString() ) ) ) {
i++;
}
idStrStatic< MAX_OSPATH > generatedName = "generated/";
generatedName.Append( sampleName );
{
if ( s_useCompression.GetBool() ) {
sampleName.Append( ".msadpcm" );
} else {
sampleName.Append( ".wav" );
}
generatedName.Append( ".idwav" );
}
loaded = LoadGeneratedSample( generatedName ) || LoadWav( sampleName );
if ( !loaded && s_useCompression.GetBool() ) {
sampleName.SetFileExtension( "wav" );
loaded = LoadWav( sampleName );
}
if ( loaded ) {
if ( cvarSystem->GetCVarBool( "fs_buildresources" ) ) {
fileSystem->AddSamplePreload( GetName() );
WriteAllSamples( GetName() );
if ( sampleName.Find( "/vo/" ) >= 0 ) {
for ( int i = 0; i < Sys_NumLangs(); i++ ) {
const char * lang = Sys_Lang( i );
if ( idStr::Icmp( lang, ID_LANG_ENGLISH ) == 0 ) {
continue;
}
idStrStatic< MAX_OSPATH > locName = GetName();
locName.Replace( "/vo/", va( "/vo/%s/", Sys_Lang( i ) ) );
WriteAllSamples( locName );
}
}
}
return;
}
}
if ( !loaded ) {
// make it default if everything else fails
MakeDefault();
}
return;
}
/*
========================
idSoundSample_XAudio2::LoadWav
========================
*/
bool idSoundSample_XAudio2::LoadWav( const idStr & filename ) {
// load the wave
idWaveFile wave;
if ( !wave.Open( filename ) ) {
return false;
}
idStrStatic< MAX_OSPATH > sampleName = filename;
sampleName.SetFileExtension( "amp" );
LoadAmplitude( sampleName );
const char * formatError = wave.ReadWaveFormat( format );
if ( formatError != NULL ) {
idLib::Warning( "LoadWav( %s ) : %s", filename.c_str(), formatError );
MakeDefault();
return false;
}
timestamp = wave.Timestamp();
totalBufferSize = wave.SeekToChunk( 'data' );
if ( format.basic.formatTag == idWaveFile::FORMAT_PCM || format.basic.formatTag == idWaveFile::FORMAT_EXTENSIBLE ) {
if ( format.basic.bitsPerSample != 16 ) {
idLib::Warning( "LoadWav( %s ) : %s", filename.c_str(), "Not a 16 bit PCM wav file" );
MakeDefault();
return false;
}
playBegin = 0;
playLength = ( totalBufferSize ) / format.basic.blockSize;
buffers.SetNum( 1 );
buffers[0].bufferSize = totalBufferSize;
buffers[0].numSamples = playLength;
buffers[0].buffer = AllocBuffer( totalBufferSize, GetName() );
wave.Read( buffers[0].buffer, totalBufferSize );
if ( format.basic.bitsPerSample == 16 ) {
idSwap::LittleArray( (short *)buffers[0].buffer, totalBufferSize / sizeof( short ) );
}
buffers[0].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[0].buffer );
} else if ( format.basic.formatTag == idWaveFile::FORMAT_ADPCM ) {
playBegin = 0;
playLength = ( ( totalBufferSize / format.basic.blockSize ) * format.extra.adpcm.samplesPerBlock );
buffers.SetNum( 1 );
buffers[0].bufferSize = totalBufferSize;
buffers[0].numSamples = playLength;
buffers[0].buffer = AllocBuffer( totalBufferSize, GetName() );
wave.Read( buffers[0].buffer, totalBufferSize );
buffers[0].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[0].buffer );
} else if ( format.basic.formatTag == idWaveFile::FORMAT_XMA2 ) {
if ( format.extra.xma2.blockCount == 0 ) {
idLib::Warning( "LoadWav( %s ) : %s", filename.c_str(), "No data blocks in file" );
MakeDefault();
return false;
}
int bytesPerBlock = format.extra.xma2.bytesPerBlock;
assert( format.extra.xma2.blockCount == ALIGN( totalBufferSize, bytesPerBlock ) / bytesPerBlock );
assert( format.extra.xma2.blockCount * bytesPerBlock >= totalBufferSize );
assert( format.extra.xma2.blockCount * bytesPerBlock < totalBufferSize + bytesPerBlock );
buffers.SetNum( format.extra.xma2.blockCount );
for ( int i = 0; i < buffers.Num(); i++ ) {
if ( i == buffers.Num() - 1 ) {
buffers[i].bufferSize = totalBufferSize - ( i * bytesPerBlock );
} else {
buffers[i].bufferSize = bytesPerBlock;
}
buffers[i].buffer = AllocBuffer( buffers[i].bufferSize, GetName() );
wave.Read( buffers[i].buffer, buffers[i].bufferSize );
buffers[i].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[i].buffer );
}
int seekTableSize = wave.SeekToChunk( 'seek' );
if ( seekTableSize != 4 * buffers.Num() ) {
idLib::Warning( "LoadWav( %s ) : %s", filename.c_str(), "Wrong number of entries in seek table" );
MakeDefault();
return false;
}
for ( int i = 0; i < buffers.Num(); i++ ) {
wave.Read( &buffers[i].numSamples, sizeof( buffers[i].numSamples ) );
idSwap::Big( buffers[i].numSamples );
}
playBegin = format.extra.xma2.loopBegin;
playLength = format.extra.xma2.loopLength;
if ( buffers[buffers.Num()-1].numSamples < playBegin + playLength ) {
// This shouldn't happen, but it's not fatal if it does
playLength = buffers[buffers.Num()-1].numSamples - playBegin;
} else {
// Discard samples beyond playLength
for ( int i = 0; i < buffers.Num(); i++ ) {
if ( buffers[i].numSamples > playBegin + playLength ) {
buffers[i].numSamples = playBegin + playLength;
// Ideally, the following loop should always have 0 iterations because playBegin + playLength ends in the last block already
// But there is no guarantee for that, so to be safe, discard all buffers beyond this one
for ( int j = i + 1; j < buffers.Num(); j++ ) {
FreeBuffer( buffers[j].buffer );
}
buffers.SetNum( i + 1 );
break;
}
}
}
} else {
idLib::Warning( "LoadWav( %s ) : Unsupported wave format %d", filename.c_str(), format.basic.formatTag );
MakeDefault();
return false;
}
wave.Close();
if ( format.basic.formatTag == idWaveFile::FORMAT_EXTENSIBLE ) {
// HACK: XAudio2 doesn't really support FORMAT_EXTENSIBLE so we convert it to a basic format after extracting the channel mask
format.basic.formatTag = format.extra.extensible.subFormat.data1;
}
// sanity check...
assert( buffers[buffers.Num()-1].numSamples == playBegin + playLength );
return true;
}
/*
========================
idSoundSample_XAudio2::MakeDefault
========================
*/
void idSoundSample_XAudio2::MakeDefault() {
FreeData();
static const int DEFAULT_NUM_SAMPLES = 256;
timestamp = FILE_NOT_FOUND_TIMESTAMP;
loaded = true;
memset( &format, 0, sizeof( format ) );
format.basic.formatTag = idWaveFile::FORMAT_PCM;
format.basic.numChannels = 1;
format.basic.bitsPerSample = 16;
format.basic.samplesPerSec = XAUDIO2_MIN_SAMPLE_RATE;
format.basic.blockSize = format.basic.numChannels * format.basic.bitsPerSample / 8;
format.basic.avgBytesPerSec = format.basic.samplesPerSec * format.basic.blockSize;
assert( format.basic.blockSize == 2 );
totalBufferSize = DEFAULT_NUM_SAMPLES * 2;
short * defaultBuffer = (short *)AllocBuffer( totalBufferSize, GetName() );
for ( int i = 0; i < DEFAULT_NUM_SAMPLES; i += 2 ) {
defaultBuffer[i + 0] = SHRT_MIN;
defaultBuffer[i + 1] = SHRT_MAX;
}
buffers.SetNum( 1 );
buffers[0].buffer = defaultBuffer;
buffers[0].bufferSize = totalBufferSize;
buffers[0].numSamples = DEFAULT_NUM_SAMPLES;
buffers[0].buffer = GPU_CONVERT_CPU_TO_CPU_CACHED_READONLY_ADDRESS( buffers[0].buffer );
playBegin = 0;
playLength = DEFAULT_NUM_SAMPLES;
}
/*
========================
idSoundSample_XAudio2::FreeData
Called before deleting the object and at the start of LoadResource()
========================
*/
void idSoundSample_XAudio2::FreeData() {
if ( buffers.Num() > 0 ) {
soundSystemLocal.StopVoicesWithSample( (idSoundSample *)this );
for ( int i = 0; i < buffers.Num(); i++ ) {
FreeBuffer( buffers[i].buffer );
}
buffers.Clear();
}
amplitude.Clear();
timestamp = FILE_NOT_FOUND_TIMESTAMP;
memset( &format, 0, sizeof( format ) );
loaded = false;
totalBufferSize = 0;
playBegin = 0;
playLength = 0;
}
/*
========================
idSoundSample_XAudio2::LoadAmplitude
========================
*/
bool idSoundSample_XAudio2::LoadAmplitude( const idStr & name ) {
amplitude.Clear();
idFileLocal f( fileSystem->OpenFileRead( name ) );
if ( f == NULL ) {
return false;
}
amplitude.SetNum( f->Length() );
f->Read( amplitude.Ptr(), amplitude.Num() );
return true;
}
/*
========================
idSoundSample_XAudio2::GetAmplitude
========================
*/
float idSoundSample_XAudio2::GetAmplitude( int timeMS ) const {
if ( timeMS < 0 || timeMS > LengthInMsec() ) {
return 0.0f;
}
if ( IsDefault() ) {
return 1.0f;
}
int index = timeMS * 60 / 1000;
if ( index < 0 || index >= amplitude.Num() ) {
return 0.0f;
}
return (float)amplitude[index] / 255.0f;
}

View File

@@ -0,0 +1,129 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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.
===========================================================================
*/
#ifndef __XA2_SOUNDSAMPLE_H__
#define __XA2_SOUNDSAMPLE_H__
/*
================================================
idSoundSample_XAudio2
================================================
*/
class idSampleInfo;
class idSoundSample_XAudio2 {
public:
idSoundSample_XAudio2();
// Loads and initializes the resource based on the name.
virtual void LoadResource();
void SetName( const char * n ) { name = n; }
const char * GetName() const { return name; }
ID_TIME_T GetTimestamp() const { return timestamp; }
// turns it into a beep
void MakeDefault();
// frees all data
void FreeData();
int LengthInMsec() const { return SamplesToMsec( NumSamples(), SampleRate() ); }
int SampleRate() const { return format.basic.samplesPerSec; }
int NumSamples() const { return playLength; }
int NumChannels() const { return format.basic.numChannels; }
int BufferSize() const { return totalBufferSize; }
bool IsCompressed() const { return ( format.basic.formatTag != idWaveFile::FORMAT_PCM ); }
bool IsDefault() const { return timestamp == FILE_NOT_FOUND_TIMESTAMP; }
bool IsLoaded() const { return loaded; }
void SetNeverPurge() { neverPurge = true; }
bool GetNeverPurge() const { return neverPurge; }
void SetLevelLoadReferenced() { levelLoadReferenced = true; }
void ResetLevelLoadReferenced() { levelLoadReferenced = false; }
bool GetLevelLoadReferenced() const { return levelLoadReferenced; }
int GetLastPlayedTime() const { return lastPlayedTime; }
void SetLastPlayedTime( int t ) { lastPlayedTime = t; }
float GetAmplitude( int timeMS ) const;
protected:
friend class idSoundHardware_XAudio2;
friend class idSoundVoice_XAudio2;
~idSoundSample_XAudio2();
bool LoadWav( const idStr & name );
bool LoadAmplitude( const idStr & name );
void WriteAllSamples( const idStr &sampleName );
bool LoadGeneratedSample( const idStr &name );
void WriteGeneratedSample( idFile *fileOut );
struct sampleBuffer_t {
void * buffer;
int bufferSize;
int numSamples;
};
idStr name;
ID_TIME_T timestamp;
bool loaded;
bool neverPurge;
bool levelLoadReferenced;
bool usesMapHeap;
uint32 lastPlayedTime;
int totalBufferSize; // total size of all the buffers
idList<sampleBuffer_t, TAG_AUDIO> buffers;
int playBegin;
int playLength;
idWaveFile::waveFmt_t format;
idList<byte, TAG_AMPLITUDE> amplitude;
};
/*
================================================
idSoundSample
This reverse-inheritance purportedly makes working on
multiple platforms easier.
================================================
*/
class idSoundSample : public idSoundSample_XAudio2 {
public:
};
#endif

View File

@@ -0,0 +1,492 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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"
#include "../snd_local.h"
idCVar s_skipHardwareSets( "s_skipHardwareSets", "0", CVAR_BOOL, "Do all calculation, but skip XA2 calls" );
idCVar s_debugHardware( "s_debugHardware", "0", CVAR_BOOL, "Print a message any time a hardware voice changes" );
// The whole system runs at this sample rate
static int SYSTEM_SAMPLE_RATE = 44100;
static float ONE_OVER_SYSTEM_SAMPLE_RATE = 1.0f / SYSTEM_SAMPLE_RATE;
/*
========================
idStreamingVoiceContext
========================
*/
class idStreamingVoiceContext : public IXAudio2VoiceCallback {
public:
STDMETHOD_(void, OnVoiceProcessingPassStart)( UINT32 BytesRequired ) {}
STDMETHOD_(void, OnVoiceProcessingPassEnd)() {}
STDMETHOD_(void, OnStreamEnd)() {}
STDMETHOD_(void, OnBufferStart)( void * pContext ) {
idSoundSystemLocal::bufferContext_t * bufferContext = (idSoundSystemLocal::bufferContext_t *) pContext;
bufferContext->voice->OnBufferStart( bufferContext->sample, bufferContext->bufferNumber );
}
STDMETHOD_(void, OnLoopEnd)( void * ) {}
STDMETHOD_(void, OnVoiceError)( void *, HRESULT hr ) { idLib::Warning( "OnVoiceError( %d )", hr ); }
STDMETHOD_(void, OnBufferEnd)( void* pContext ) {
idSoundSystemLocal::bufferContext_t * bufferContext = (idSoundSystemLocal::bufferContext_t *) pContext;
soundSystemLocal.ReleaseStreamBufferContext( bufferContext );
}
} streamContext;
/*
========================
idSoundVoice_XAudio2::idSoundVoice_XAudio2
========================
*/
idSoundVoice_XAudio2::idSoundVoice_XAudio2()
: pSourceVoice( NULL ),
leadinSample( NULL ),
loopingSample( NULL ),
formatTag( 0 ),
numChannels( 0 ),
sampleRate( 0 ),
paused( true ),
hasVUMeter( false ) {
}
/*
========================
idSoundVoice_XAudio2::~idSoundVoice_XAudio2
========================
*/
idSoundVoice_XAudio2::~idSoundVoice_XAudio2() {
DestroyInternal();
}
/*
========================
idSoundVoice_XAudio2::CompatibleFormat
========================
*/
bool idSoundVoice_XAudio2::CompatibleFormat( idSoundSample_XAudio2 * s ) {
if ( pSourceVoice == NULL ) {
// If this voice has never been allocated, then it's compatible with everything
return true;
}
return false;
}
/*
========================
idSoundVoice_XAudio2::Create
========================
*/
void idSoundVoice_XAudio2::Create( const idSoundSample * leadinSample_, const idSoundSample * loopingSample_ ) {
if ( IsPlaying() ) {
// This should never hit
Stop();
return;
}
leadinSample = (idSoundSample_XAudio2 *)leadinSample_;
loopingSample = (idSoundSample_XAudio2 *)loopingSample_;
if ( pSourceVoice != NULL && CompatibleFormat( leadinSample ) ) {
sampleRate = leadinSample->format.basic.samplesPerSec;
} else {
DestroyInternal();
formatTag = leadinSample->format.basic.formatTag;
numChannels = leadinSample->format.basic.numChannels;
sampleRate = leadinSample->format.basic.samplesPerSec;
soundSystemLocal.hardware.pXAudio2->CreateSourceVoice( &pSourceVoice, (const WAVEFORMATEX *)&leadinSample->format, XAUDIO2_VOICE_USEFILTER, 4.0f, &streamContext );
if ( pSourceVoice == NULL ) {
// If this hits, then we are most likely passing an invalid sample format, which should have been caught by the loader (and the sample defaulted)
return;
}
if ( s_debugHardware.GetBool() ) {
if ( loopingSample == NULL || loopingSample == leadinSample ) {
idLib::Printf( "%dms: %p created for %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>" );
} else {
idLib::Printf( "%dms: %p created for %s and %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>", loopingSample ? loopingSample->GetName() : "<null>" );
}
}
}
sourceVoiceRate = sampleRate;
pSourceVoice->SetSourceSampleRate( sampleRate );
pSourceVoice->SetVolume( 0.0f );
}
/*
========================
idSoundVoice_XAudio2::DestroyInternal
========================
*/
void idSoundVoice_XAudio2::DestroyInternal() {
if ( pSourceVoice != NULL ) {
if ( s_debugHardware.GetBool() ) {
idLib::Printf( "%dms: %p destroyed\n", Sys_Milliseconds(), pSourceVoice );
}
pSourceVoice->DestroyVoice();
pSourceVoice = NULL;
hasVUMeter = false;
}
}
/*
========================
idSoundVoice_XAudio2::Start
========================
*/
void idSoundVoice_XAudio2::Start( int offsetMS, int ssFlags ) {
if ( s_debugHardware.GetBool() ) {
idLib::Printf( "%dms: %p starting %s @ %dms\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>", offsetMS );
}
if ( !leadinSample ) {
return;
}
if ( !pSourceVoice ) {
return;
}
if ( leadinSample->IsDefault() ) {
idLib::Warning( "Starting defaulted sound sample %s", leadinSample->GetName() );
}
bool flicker = ( ssFlags & SSF_NO_FLICKER ) == 0;
if ( flicker != hasVUMeter ) {
hasVUMeter = flicker;
if ( flicker ) {
IUnknown * vuMeter = NULL;
if ( XAudio2CreateVolumeMeter( &vuMeter, 0 ) == S_OK ) {
XAUDIO2_EFFECT_DESCRIPTOR descriptor;
descriptor.InitialState = true;
descriptor.OutputChannels = leadinSample->NumChannels();
descriptor.pEffect = vuMeter;
XAUDIO2_EFFECT_CHAIN chain;
chain.EffectCount = 1;
chain.pEffectDescriptors = &descriptor;
pSourceVoice->SetEffectChain( &chain );
vuMeter->Release();
}
} else {
pSourceVoice->SetEffectChain( NULL );
}
}
assert( offsetMS >= 0 );
int offsetSamples = MsecToSamples( offsetMS, leadinSample->SampleRate() );
if ( loopingSample == NULL && offsetSamples >= leadinSample->playLength ) {
return;
}
RestartAt( offsetSamples );
Update();
UnPause();
}
/*
========================
idSoundVoice_XAudio2::RestartAt
========================
*/
int idSoundVoice_XAudio2::RestartAt( int offsetSamples ) {
offsetSamples &= ~127;
idSoundSample_XAudio2 * sample = leadinSample;
if ( offsetSamples >= leadinSample->playLength ) {
if ( loopingSample != NULL ) {
offsetSamples %= loopingSample->playLength;
sample = loopingSample;
} else {
return 0;
}
}
int previousNumSamples = 0;
for ( int i = 0; i < sample->buffers.Num(); i++ ) {
if ( sample->buffers[i].numSamples > sample->playBegin + offsetSamples ) {
return SubmitBuffer( sample, i, sample->playBegin + offsetSamples - previousNumSamples );
}
previousNumSamples = sample->buffers[i].numSamples;
}
return 0;
}
/*
========================
idSoundVoice_XAudio2::SubmitBuffer
========================
*/
int idSoundVoice_XAudio2::SubmitBuffer( idSoundSample_XAudio2 * sample, int bufferNumber, int offset ) {
if ( sample == NULL || ( bufferNumber < 0 ) || ( bufferNumber >= sample->buffers.Num() ) ) {
return 0;
}
idSoundSystemLocal::bufferContext_t * bufferContext = soundSystemLocal.ObtainStreamBufferContext();
if ( bufferContext == NULL ) {
idLib::Warning( "No free buffer contexts!" );
return 0;
}
bufferContext->voice = this;
bufferContext->sample = sample;
bufferContext->bufferNumber = bufferNumber;
XAUDIO2_BUFFER buffer = { 0 };
if ( offset > 0 ) {
int previousNumSamples = 0;
if ( bufferNumber > 0 ) {
previousNumSamples = sample->buffers[bufferNumber-1].numSamples;
}
buffer.PlayBegin = offset;
buffer.PlayLength = sample->buffers[bufferNumber].numSamples - previousNumSamples - offset;
}
buffer.AudioBytes = sample->buffers[bufferNumber].bufferSize;
buffer.pAudioData = (BYTE *)sample->buffers[bufferNumber].buffer;
buffer.pContext = bufferContext;
if ( ( loopingSample == NULL ) && ( bufferNumber == sample->buffers.Num() - 1 ) ) {
buffer.Flags = XAUDIO2_END_OF_STREAM;
}
pSourceVoice->SubmitSourceBuffer( &buffer );
return buffer.AudioBytes;
}
/*
========================
idSoundVoice_XAudio2::Update
========================
*/
bool idSoundVoice_XAudio2::Update() {
if ( pSourceVoice == NULL || leadinSample == NULL ) {
return false;
}
XAUDIO2_VOICE_STATE state;
pSourceVoice->GetState( &state );
const int srcChannels = leadinSample->NumChannels();
float pLevelMatrix[ MAX_CHANNELS_PER_VOICE * MAX_CHANNELS_PER_VOICE ] = { 0 };
CalculateSurround( srcChannels, pLevelMatrix, 1.0f );
if ( s_skipHardwareSets.GetBool() ) {
return true;
}
pSourceVoice->SetOutputMatrix( soundSystemLocal.hardware.pMasterVoice, srcChannels, dstChannels, pLevelMatrix, OPERATION_SET );
assert( idMath::Fabs( gain ) <= XAUDIO2_MAX_VOLUME_LEVEL );
pSourceVoice->SetVolume( gain, OPERATION_SET );
SetSampleRate( sampleRate, OPERATION_SET );
// we don't do this any longer because we pause and unpause explicitly when the soundworld is paused or unpaused
// UnPause();
return true;
}
/*
========================
idSoundVoice_XAudio2::IsPlaying
========================
*/
bool idSoundVoice_XAudio2::IsPlaying() {
if ( pSourceVoice == NULL ) {
return false;
}
XAUDIO2_VOICE_STATE state;
pSourceVoice->GetState( &state );
return ( state.BuffersQueued != 0 );
}
/*
========================
idSoundVoice_XAudio2::FlushSourceBuffers
========================
*/
void idSoundVoice_XAudio2::FlushSourceBuffers() {
if ( pSourceVoice != NULL ) {
pSourceVoice->FlushSourceBuffers();
}
}
/*
========================
idSoundVoice_XAudio2::Pause
========================
*/
void idSoundVoice_XAudio2::Pause() {
if ( !pSourceVoice || paused ) {
return;
}
if ( s_debugHardware.GetBool() ) {
idLib::Printf( "%dms: %p pausing %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>" );
}
pSourceVoice->Stop( 0, OPERATION_SET );
paused = true;
}
/*
========================
idSoundVoice_XAudio2::UnPause
========================
*/
void idSoundVoice_XAudio2::UnPause() {
if ( !pSourceVoice || !paused ) {
return;
}
if ( s_debugHardware.GetBool() ) {
idLib::Printf( "%dms: %p unpausing %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>" );
}
pSourceVoice->Start( 0, OPERATION_SET );
paused = false;
}
/*
========================
idSoundVoice_XAudio2::Stop
========================
*/
void idSoundVoice_XAudio2::Stop() {
if ( !pSourceVoice ) {
return;
}
if ( !paused ) {
if ( s_debugHardware.GetBool() ) {
idLib::Printf( "%dms: %p stopping %s\n", Sys_Milliseconds(), pSourceVoice, leadinSample ? leadinSample->GetName() : "<null>" );
}
pSourceVoice->Stop( 0, OPERATION_SET );
paused = true;
}
}
/*
========================
idSoundVoice_XAudio2::GetAmplitude
========================
*/
float idSoundVoice_XAudio2::GetAmplitude() {
if ( !hasVUMeter ) {
return 1.0f;
}
float peakLevels[ MAX_CHANNELS_PER_VOICE ];
float rmsLevels[ MAX_CHANNELS_PER_VOICE ];
XAUDIO2FX_VOLUMEMETER_LEVELS levels;
levels.ChannelCount = leadinSample->NumChannels();
levels.pPeakLevels = peakLevels;
levels.pRMSLevels = rmsLevels;
if ( levels.ChannelCount > MAX_CHANNELS_PER_VOICE ) {
levels.ChannelCount = MAX_CHANNELS_PER_VOICE;
}
if ( pSourceVoice->GetEffectParameters( 0, &levels, sizeof( levels ) ) != S_OK ) {
return 0.0f;
}
if ( levels.ChannelCount == 1 ) {
return rmsLevels[0];
}
float rms = 0.0f;
for ( uint32 i = 0; i < levels.ChannelCount; i++ ) {
rms += rmsLevels[i];
}
return rms / (float)levels.ChannelCount;
}
/*
========================
idSoundVoice_XAudio2::ResetSampleRate
========================
*/
void idSoundVoice_XAudio2::SetSampleRate( uint32 newSampleRate, uint32 operationSet ){
if ( pSourceVoice == NULL || leadinSample == NULL ) {
return;
}
sampleRate = newSampleRate;
XAUDIO2_FILTER_PARAMETERS filter;
filter.Type = LowPassFilter;
filter.OneOverQ = 1.0f; // [0.0f, XAUDIO2_MAX_FILTER_ONEOVERQ]
float cutoffFrequency = 1000.0f / Max( 0.01f, occlusion );
if ( cutoffFrequency * 6.0f >= (float)sampleRate ) {
filter.Frequency = XAUDIO2_MAX_FILTER_FREQUENCY;
} else {
filter.Frequency = 2.0f * idMath::Sin( idMath::PI * cutoffFrequency / (float)sampleRate );
}
assert( filter.Frequency >= 0.0f && filter.Frequency <= XAUDIO2_MAX_FILTER_FREQUENCY );
filter.Frequency = idMath::ClampFloat( 0.0f, XAUDIO2_MAX_FILTER_FREQUENCY, filter.Frequency );
pSourceVoice->SetFilterParameters( &filter, operationSet );
float freqRatio = pitch * (float)sampleRate / (float)sourceVoiceRate;
assert( freqRatio >= XAUDIO2_MIN_FREQ_RATIO && freqRatio <= XAUDIO2_MAX_FREQ_RATIO );
freqRatio = idMath::ClampFloat( XAUDIO2_MIN_FREQ_RATIO, XAUDIO2_MAX_FREQ_RATIO, freqRatio );
// if the value specified for maxFreqRatio is too high for the specified format, the call to CreateSourceVoice will fail
if ( numChannels == 1 ) {
assert( freqRatio * (float)SYSTEM_SAMPLE_RATE <= XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MONO );
} else {
assert( freqRatio * (float)SYSTEM_SAMPLE_RATE <= XAUDIO2_MAX_RATIO_TIMES_RATE_XMA_MULTICHANNEL );
}
pSourceVoice->SetFrequencyRatio( freqRatio, operationSet );
}
/*
========================
idSoundVoice_XAudio2::OnBufferStart
========================
*/
void idSoundVoice_XAudio2::OnBufferStart( idSoundSample_XAudio2 * sample, int bufferNumber ) {
SetSampleRate( sample->SampleRate(), XAUDIO2_COMMIT_NOW );
idSoundSample_XAudio2 * nextSample = sample;
int nextBuffer = bufferNumber + 1;
if ( nextBuffer == sample->buffers.Num() ) {
if ( sample == leadinSample ) {
if ( loopingSample == NULL ) {
return;
}
nextSample = loopingSample;
}
nextBuffer = 0;
}
SubmitBuffer( nextSample, nextBuffer, 0 );
}

View File

@@ -0,0 +1,114 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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.
===========================================================================
*/
#ifndef __XA2_SOUNDVOICE_H__
#define __XA2_SOUNDVOICE_H__
static const int MAX_QUEUED_BUFFERS = 3;
/*
================================================
idSoundVoice_XAudio2
================================================
*/
class idSoundVoice_XAudio2 : public idSoundVoice_Base {
public:
idSoundVoice_XAudio2();
~idSoundVoice_XAudio2();
void Create( const idSoundSample * leadinSample, const idSoundSample * loopingSample );
// Start playing at a particular point in the buffer. Does an Update() too
void Start( int offsetMS, int ssFlags );
// Stop playing.
void Stop();
// Stop consuming buffers
void Pause();
// Start consuming buffers again
void UnPause();
// Sends new position/volume/pitch information to the hardware
bool Update();
// returns the RMS levels of the most recently processed block of audio, SSF_FLICKER must have been passed to Start
float GetAmplitude();
// returns true if we can re-use this voice
bool CompatibleFormat( idSoundSample_XAudio2 * s );
uint32 GetSampleRate() const { return sampleRate; }
// callback function
void OnBufferStart( idSoundSample_XAudio2 * sample, int bufferNumber );
private:
friend class idSoundHardware_XAudio2;
// Returns true when all the buffers are finished processing
bool IsPlaying();
// Called after the voice has been stopped
void FlushSourceBuffers();
// Destroy the internal hardware resource
void DestroyInternal();
// Helper function used by the initial start as well as for looping a streamed buffer
int RestartAt( int offsetSamples );
// Helper function to submit a buffer
int SubmitBuffer( idSoundSample_XAudio2 * sample, int bufferNumber, int offset );
// Adjust the voice frequency based on the new sample rate for the buffer
void SetSampleRate( uint32 newSampleRate, uint32 operationSet );
IXAudio2SourceVoice * pSourceVoice;
idSoundSample_XAudio2 * leadinSample;
idSoundSample_XAudio2 * loopingSample;
// These are the fields from the sample format that matter to us for voice reuse
uint16 formatTag;
uint16 numChannels;
uint32 sourceVoiceRate;
uint32 sampleRate;
bool hasVUMeter;
bool paused;
};
/*
================================================
idSoundVoice
================================================
*/
class idSoundVoice : public idSoundVoice_XAudio2 {
};
#endif

977
neo/sound/snd_emitter.cpp Normal file
View File

@@ -0,0 +1,977 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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"
#include "snd_local.h"
idCVar s_singleEmitter( "s_singleEmitter", "0", CVAR_INTEGER, "mute all sounds but this emitter" );
idCVar s_showStartSound( "s_showStartSound", "0", CVAR_BOOL, "print a message every time a sound starts/stops" );
idCVar s_useOcclusion( "s_useOcclusion", "1", CVAR_BOOL, "Attenuate sounds based on walls" );
idCVar s_centerFractionVO( "s_centerFractionVO", "0.75", CVAR_FLOAT, "Portion of VO sounds routed to the center channel" );
extern idCVar s_playDefaultSound;
extern idCVar s_noSound;
/*
================================================================================================
idSoundFade
================================================================================================
*/
/*
========================
idSoundFade::Clear
========================
*/
void idSoundFade::Clear() {
fadeStartTime = 0;
fadeEndTime = 0;
fadeStartVolume = 0.0f;
fadeEndVolume = 0.0f;
}
/*
========================
idSoundFade::SetVolume
========================
*/
void idSoundFade::SetVolume( float to ) {
fadeStartVolume = to;
fadeEndVolume = to;
fadeStartTime = 0;
fadeEndTime = 0;
}
/*
========================
idSoundFade::Fade
========================
*/
void idSoundFade::Fade( float to, int length, int soundTime ) {
int startTime = soundTime;
// if it is already fading to this volume at this rate, don't change it
if ( fadeEndTime == startTime + length && fadeEndVolume == to ) {
return;
}
fadeStartVolume = GetVolume( soundTime );
fadeEndVolume = to;
fadeStartTime = startTime;
fadeEndTime = startTime + length;
}
/*
========================
idSoundFade::GetVolume
========================
*/
float idSoundFade::GetVolume( const int soundTime ) const {
const float fadeDuration = ( fadeEndTime - fadeStartTime );
const int currentTime = soundTime;
const float playTime = ( currentTime - fadeStartTime );
if ( fadeDuration <= 0.0f ) {
return fadeEndVolume;
} else if ( currentTime >= fadeEndTime ) {
return fadeEndVolume;
} else if ( currentTime > fadeStartTime ) {
return fadeStartVolume + ( fadeEndVolume - fadeStartVolume ) * playTime / fadeDuration;
} else {
return fadeStartVolume;
}
}
/*
========================
idSoundChannel::idSoundChannel
========================
*/
idSoundChannel::idSoundChannel() {
emitter = NULL;
hardwareVoice = NULL;
startTime = 0;
endTime = 0;
leadinSample = NULL;
loopingSample = NULL;
logicalChannel = SCHANNEL_ANY;
allowSlow = false;
soundShader = NULL;
volumeFade.Clear();
volumeDB = DB_SILENCE;
currentAmplitude = 0.0f;
}
/*
========================
idSoundChannel::~idSoundChannel
========================
*/
idSoundChannel::~idSoundChannel() {
}
/*
========================
idSoundChannel::CanMute
Never actually mute VO because we can't restart them precisely enough for lip syncing to not fuck up
========================
*/
bool idSoundChannel::CanMute() const {
return true;
}
/*
========================
idSoundChannel::Mute
A muted sound is considered still running, and can restart when a listener
gets close enough.
========================
*/
void idSoundChannel::Mute() {
if ( hardwareVoice != NULL ) {
soundSystemLocal.FreeVoice( hardwareVoice );
hardwareVoice = NULL;
}
}
/*
========================
idSoundChannel::IsLooping
========================
*/
bool idSoundChannel::IsLooping() const {
return ( parms.soundShaderFlags & SSF_LOOPING ) != 0;
}
/*
========================
idSoundChannel::CheckForCompletion
========================
*/
bool idSoundChannel::CheckForCompletion( int currentTime ) {
if ( leadinSample == NULL ) {
return true;
}
// endTime of 0 indicates a sound should loop forever
if ( endTime > 0 && endTime < currentTime ) {
return true;
}
return false;
}
/*
========================
idSoundChannel::UpdateVolume
========================
*/
void idSoundChannel::UpdateVolume( int currentTime ) {
idSoundWorldLocal * soundWorld = emitter->soundWorld;
volumeDB = DB_SILENCE;
currentAmplitude = 0.0f;
if ( leadinSample == NULL ) {
return;
}
if ( startTime > currentTime ) {
return;
}
if ( endTime > 0 && endTime < currentTime ) {
return;
}
// if you don't want to hear all the beeps from missing sounds
if ( leadinSample->IsDefault() && !s_playDefaultSound.GetBool() ) {
return;
}
bool emitterIsListener = ( emitter->emitterId == soundWorld->listener.id );
// if it is a private sound, set the volume to zero unless we match the listener.id
if ( parms.soundShaderFlags & SSF_PRIVATE_SOUND ) {
if ( !emitterIsListener ) {
return;
}
}
if ( parms.soundShaderFlags & SSF_ANTI_PRIVATE_SOUND ) {
if ( emitterIsListener ) {
return;
}
}
// volume fading
float newVolumeDB = parms.volume;
newVolumeDB += volumeFade.GetVolume( currentTime );
newVolumeDB += soundWorld->volumeFade.GetVolume( currentTime );
newVolumeDB += soundWorld->pauseFade.GetVolume( currentTime );
if ( parms.soundClass >= 0 && parms.soundClass < SOUND_MAX_CLASSES ) {
newVolumeDB += soundWorld->soundClassFade[parms.soundClass].GetVolume( currentTime );
}
bool global = ( parms.soundShaderFlags & SSF_GLOBAL ) != 0;
// attenuation
if ( !global && !emitterIsListener ) {
float distance = ( parms.soundShaderFlags & SSF_NO_OCCLUSION ) == 0 ? emitter->spatializedDistance : emitter->directDistance;
float mindist = parms.minDistance;
float maxdist = parms.maxDistance;
if ( distance >= maxdist ) {
newVolumeDB = DB_SILENCE;
} else if ( ( distance > mindist ) && ( maxdist > mindist ) ) {
float f = ( distance - mindist ) / ( maxdist - mindist );
newVolumeDB += LinearToDB( Square( 1.0f - f ) );
}
}
if ( soundSystemLocal.musicMuted && ( parms.soundShaderFlags & SSF_MUSIC ) != 0 ) {
newVolumeDB = DB_SILENCE;
}
// store the new volume on the channel
volumeDB = newVolumeDB;
// keep track of the maximum volume
float currentVolumeDB = newVolumeDB;
if ( hardwareVoice != NULL ) {
float amplitude = hardwareVoice->GetAmplitude();
if ( amplitude <= 0.0f ) {
currentVolumeDB = DB_SILENCE;
} else {
currentVolumeDB += LinearToDB( amplitude );
}
currentAmplitude = amplitude;
}
}
/*
========================
idSoundChannel::UpdateHardware
========================
*/
void idSoundChannel::UpdateHardware( float volumeAdd, int currentTime ) {
idSoundWorldLocal * soundWorld = emitter->soundWorld;
if ( soundWorld == NULL ) {
return;
}
if ( leadinSample == NULL ) {
return;
}
if ( startTime > currentTime ) {
return;
}
if ( endTime > 0 && endTime < currentTime ) {
return;
}
// convert volumes from decibels to linear
float volume = Max( 0.0f, DBtoLinear( volumeDB + volumeAdd ) );
if ( ( parms.soundShaderFlags & SSF_UNCLAMPED ) == 0 ) {
volume = Min( 1.0f, volume );
}
bool global = ( parms.soundShaderFlags & SSF_GLOBAL ) != 0;
bool omni = ( parms.soundShaderFlags & SSF_OMNIDIRECTIONAL ) != 0;
bool emitterIsListener = ( emitter->emitterId == soundWorld->listener.id );
int startOffset = 0;
bool issueStart = false;
if ( hardwareVoice == NULL ) {
if ( volume <= 0.00001f ) {
return;
}
hardwareVoice = soundSystemLocal.AllocateVoice( leadinSample, loopingSample );
if ( hardwareVoice == NULL ) {
return;
}
issueStart = true;
startOffset = currentTime - startTime;
}
if ( omni || global || emitterIsListener ) {
hardwareVoice->SetPosition( vec3_zero );
} else {
hardwareVoice->SetPosition( ( emitter->spatializedOrigin - soundWorld->listener.pos ) * soundWorld->listener.axis.Transpose() );
}
if ( parms.soundShaderFlags & SSF_VO ) {
hardwareVoice->SetCenterChannel( s_centerFractionVO.GetFloat() );
} else {
hardwareVoice->SetCenterChannel( 0.0f );
}
extern idCVar timescale;
hardwareVoice->SetGain( volume );
hardwareVoice->SetInnerRadius( parms.minDistance * METERS_TO_DOOM );
hardwareVoice->SetPitch( soundWorld->slowmoSpeed * idMath::ClampFloat( 0.2f, 5.0f, timescale.GetFloat() ) );
if ( soundWorld->enviroSuitActive ) {
hardwareVoice->SetOcclusion( 0.5f );
} else {
hardwareVoice->SetOcclusion( 0.0f );
}
if ( issueStart ) {
hardwareVoice->Start( startOffset, parms.soundShaderFlags | ( parms.shakes == 0.0f ? SSF_NO_FLICKER : 0 ) );
} else {
hardwareVoice->Update();
}
}
/*
================================================================================================
idSoundEmitterLocal
================================================================================================
*/
/*
========================
idSoundEmitterLocal::idSoundEmitterLocal
========================
*/
idSoundEmitterLocal::idSoundEmitterLocal() {
Init( 0, NULL );
}
/*
========================
idSoundEmitterLocal::~idSoundEmitterLocal
========================
*/
idSoundEmitterLocal::~idSoundEmitterLocal() {
assert( channels.Num() == 0 );
}
/*
========================
idSoundEmitterLocal::Clear
========================
*/
void idSoundEmitterLocal::Init( int i, idSoundWorldLocal * sw ) {
index = i;
soundWorld = sw;
// Init should only be called on a freshly constructed sound emitter or in a Reset()
assert( channels.Num() == 0 );
canFree = false;
origin.Zero();
emitterId = 0;
directDistance = 0.0f;
lastValidPortalArea = -1;
spatializedDistance = 0.0f;
spatializedOrigin.Zero();
memset( &parms, 0, sizeof( parms ) );
}
/*
========================
idSoundEmitterLocal::Reset
========================
*/
void idSoundEmitterLocal::Reset() {
for ( int i = 0; i < channels.Num(); i++ ) {
soundWorld->FreeSoundChannel( channels[i] );
}
channels.Clear();
Init( index, soundWorld );
}
/*
==================
idSoundEmitterLocal::OverrideParms
==================
*/
void idSoundEmitterLocal::OverrideParms( const soundShaderParms_t * base, const soundShaderParms_t * over, soundShaderParms_t * out ) {
if ( !over ) {
*out = *base;
return;
}
if ( over->minDistance ) {
out->minDistance = over->minDistance;
} else {
out->minDistance = base->minDistance;
}
if ( over->maxDistance ) {
out->maxDistance = over->maxDistance;
} else {
out->maxDistance = base->maxDistance;
}
if ( over->shakes ) {
out->shakes = over->shakes;
} else {
out->shakes = base->shakes;
}
if ( over->volume ) {
out->volume = over->volume;
} else {
out->volume = base->volume;
}
if ( over->soundClass ) {
out->soundClass = over->soundClass;
} else {
out->soundClass = base->soundClass;
}
out->soundShaderFlags = base->soundShaderFlags | over->soundShaderFlags;
}
/*
========================
idSoundEmitterLocal::CheckForCompletion
Checks to see if any of the channels have completed, removing them as they do
This will also play any postSounds on the same channel as their owner.
Returns true if the emitter should be freed.
========================
*/
bool idSoundEmitterLocal::CheckForCompletion( int currentTime ) {
for ( int i = channels.Num() - 1; i >= 0 ; i-- ) {
idSoundChannel * chan = channels[i];
if ( chan->CheckForCompletion( currentTime ) ) {
channels.RemoveIndex( i );
soundWorld->FreeSoundChannel( chan );
}
}
return ( canFree && channels.Num() == 0 );
}
/*
========================
idSoundEmitterLocal::Update
========================
*/
void idSoundEmitterLocal::Update( int currentTime ) {
if ( channels.Num() == 0 ) {
return;
}
directDistance = ( soundWorld->listener.pos - origin ).LengthFast() * DOOM_TO_METERS;
spatializedDistance = directDistance;
spatializedOrigin = origin;
// Initialize all channels to silence
for ( int i = 0; i < channels.Num(); i++ ) {
channels[i]->volumeDB = DB_SILENCE;
}
if ( s_singleEmitter.GetInteger() > 0 && s_singleEmitter.GetInteger() != index ) {
return;
}
if ( soundWorld->listener.area == -1 ) {
// listener is outside the world
return;
}
if ( soundSystemLocal.muted || soundWorld != soundSystemLocal.currentSoundWorld ) {
return;
}
float maxDistance = 0.0f;
bool maxDistanceValid = false;
bool useOcclusion = false;
if ( emitterId != soundWorld->listener.id ) {
for ( int i = 0; i < channels.Num(); i++ ) {
idSoundChannel * chan = channels[i];
if ( ( chan->parms.soundShaderFlags & SSF_GLOBAL ) != 0 ) {
continue;
}
useOcclusion = useOcclusion || ( ( chan->parms.soundShaderFlags & SSF_NO_OCCLUSION ) == 0 );
maxDistanceValid = true;
if ( maxDistance < channels[i]->parms.maxDistance ) {
maxDistance = channels[i]->parms.maxDistance;
}
}
}
if ( maxDistanceValid && directDistance >= maxDistance ) {
// too far away to possibly hear it
return;
}
if ( useOcclusion && s_useOcclusion.GetBool() ) {
// work out virtual origin and distance, which may be from a portal instead of the actual origin
if ( soundWorld->renderWorld != NULL ) {
// we have a valid renderWorld
int soundInArea = soundWorld->renderWorld->PointInArea( origin );
if ( soundInArea == -1 ) {
soundInArea = lastValidPortalArea;
} else {
lastValidPortalArea = soundInArea;
}
if ( soundInArea != -1 && soundInArea != soundWorld->listener.area ) {
spatializedDistance = maxDistance * METERS_TO_DOOM;
soundWorld->ResolveOrigin( 0, NULL, soundInArea, 0.0f, origin, this );
spatializedDistance *= DOOM_TO_METERS;
}
}
}
for ( int j = 0; j < channels.Num(); j++ ) {
channels[j]->UpdateVolume( currentTime );
}
return;
}
/*
========================
idSoundEmitterLocal::Index
========================
*/
int idSoundEmitterLocal::Index() const {
assert( soundWorld );
assert( soundWorld->emitters[this->index] == this );
return index;
}
/*
========================
idSoundEmitterLocal::Free
Doesn't free it until the next update.
========================
*/
void idSoundEmitterLocal::Free( bool immediate ) {
assert( soundWorld );
assert( soundWorld->emitters[this->index] == this );
if ( canFree ) {
// Double free
return;
}
if ( soundWorld && soundWorld->writeDemo ) {
soundWorld->writeDemo->WriteInt( DS_SOUND );
soundWorld->writeDemo->WriteInt( SCMD_FREE );
soundWorld->writeDemo->WriteInt( index );
soundWorld->writeDemo->WriteInt( immediate );
}
if ( immediate ) {
Reset();
}
canFree = true;
}
/*
========================
idSoundEmitterLocal::UpdateEmitter
========================
*/
void idSoundEmitterLocal::UpdateEmitter( const idVec3 &origin, int listenerId, const soundShaderParms_t *parms ) {
assert( soundWorld != NULL );
assert( soundWorld->emitters[this->index] == this );
if ( soundWorld && soundWorld->writeDemo ) {
soundWorld->writeDemo->WriteInt( DS_SOUND );
soundWorld->writeDemo->WriteInt( SCMD_UPDATE );
soundWorld->writeDemo->WriteInt( index );
soundWorld->writeDemo->WriteVec3( origin );
soundWorld->writeDemo->WriteInt( listenerId );
soundWorld->writeDemo->WriteFloat( parms->minDistance );
soundWorld->writeDemo->WriteFloat( parms->maxDistance );
soundWorld->writeDemo->WriteFloat( parms->volume );
soundWorld->writeDemo->WriteFloat( parms->shakes );
soundWorld->writeDemo->WriteInt( parms->soundShaderFlags );
soundWorld->writeDemo->WriteInt( parms->soundClass );
}
this->origin = origin;
this->emitterId = listenerId;
this->parms = *parms;
}
/*
========================
idSoundEmitterLocal::StartSound
in most cases play sounds immediately, however
intercept sounds using SSF_FINITE_SPEED_OF_SOUND
and schedule them for playback later
return: int - the length of the started sound in msec.
========================
*/
int idSoundEmitterLocal::StartSound( const idSoundShader * shader, const s_channelType channel, float diversity, int shaderFlags, bool allowSlow ) {
assert( soundWorld != NULL );
assert( soundWorld->emitters[this->index] == this );
if ( shader == NULL ) {
return 0;
}
if ( soundWorld && soundWorld->writeDemo ) {
soundWorld->writeDemo->WriteInt( DS_SOUND );
soundWorld->writeDemo->WriteInt( SCMD_START );
soundWorld->writeDemo->WriteInt( index );
soundWorld->writeDemo->WriteHashString( shader->GetName() );
soundWorld->writeDemo->WriteInt( channel );
soundWorld->writeDemo->WriteFloat( diversity );
soundWorld->writeDemo->WriteInt( shaderFlags );
}
if ( s_noSound.GetBool() ) {
return 0;
}
int currentTime = soundWorld->GetSoundTime();
bool showStartSound = s_showStartSound.GetBool();
if ( showStartSound ) {
idLib::Printf( "%dms: StartSound(%d:%d): %s: ", currentTime, index, channel, shader->GetName() );
}
// build the channel parameters by taking the shader parms and optionally overriding
soundShaderParms_t chanParms;
chanParms = shader->parms;
OverrideParms( &chanParms, &parms, &chanParms );
chanParms.soundShaderFlags |= shaderFlags;
if ( shader->entries.Num() == 0 ) {
if ( showStartSound ) {
idLib::Printf( S_COLOR_RED "No Entries\n" );
}
return 0;
}
// PLAY_ONCE sounds will never be restarted while they are running
if ( chanParms.soundShaderFlags & SSF_PLAY_ONCE ) {
for ( int i = 0; i < channels.Num(); i++ ) {
idSoundChannel * chan = channels[i];
if ( chan->soundShader == shader && !chan->CheckForCompletion( currentTime ) ) {
if ( showStartSound ) {
idLib::Printf( S_COLOR_YELLOW "Not started because of playOnce\n" );
}
return 0;
}
}
}
// never play the same sound twice with the same starting time, even
// if they are on different channels
for ( int i = 0; i < channels.Num(); i++ ) {
idSoundChannel * chan = channels[i];
if ( chan->soundShader == shader && chan->startTime == currentTime && chan->endTime != 1 ) {
if ( showStartSound ) {
idLib::Printf( S_COLOR_RED "Already started this frame\n" );
}
return 0;
}
}
// kill any sound that is currently playing on this channel
if ( channel != SCHANNEL_ANY ) {
for ( int i = 0; i < channels.Num(); i++ ) {
idSoundChannel * chan = channels[i];
if ( chan->soundShader && chan->logicalChannel == channel ) {
if ( showStartSound ) {
idLib::Printf( S_COLOR_YELLOW "OVERRIDE %s: ", chan->soundShader->GetName() );
}
channels.RemoveIndex( i );
soundWorld->FreeSoundChannel( chan );
break;
}
}
}
idSoundSample * leadinSample = NULL;
idSoundSample * loopingSample = NULL;
if ( shader->leadin && ( chanParms.soundShaderFlags & SSF_LOOPING ) ) {
leadinSample = shader->entries[0];
loopingSample = shader->entries.Num() > 1 ? shader->entries[1] : NULL;
} else {
if ( shader->entries.Num() == 1 ) {
leadinSample = shader->entries[0];
} else {
int choice;
if ( chanParms.soundShaderFlags & SSF_NO_DUPS ) {
// Don't select the most recently played entry
int mostRecentTime = 0;
int mostRecent = 0;
for ( int i = 0; i < shader->entries.Num(); i++ ) {
int entryTime = shader->entries[i]->GetLastPlayedTime();
if ( entryTime > mostRecentTime ) {
mostRecentTime = entryTime;
mostRecent = i;
}
}
choice = (int)( diversity * ( shader->entries.Num() - 1 ) );
if ( choice >= mostRecent ) {
choice++;
}
} else {
// pick a sound from the list based on the passed diversity
choice = (int)( diversity * shader->entries.Num() );
}
choice = idMath::ClampInt( 0, shader->entries.Num() - 1, choice );
leadinSample = shader->entries[choice];
leadinSample->SetLastPlayedTime( soundWorld->GetSoundTime() );
}
if ( chanParms.soundShaderFlags & SSF_LOOPING ) {
loopingSample = leadinSample;
}
}
// set all the channel parameters here,
// a hardware voice will be allocated next update if the volume is high enough to be audible
if ( channels.Num() == channels.Max() ) {
CheckForCompletion( currentTime ); // as a last chance try to release finished sounds here
if ( channels.Num() == channels.Max() ) {
if ( showStartSound ) {
idLib::Printf( S_COLOR_RED "No free emitter channels!\n" );
}
return 0;
}
}
idSoundChannel * chan = soundWorld->AllocSoundChannel();
if ( chan == NULL ) {
if ( showStartSound ) {
idLib::Printf( S_COLOR_RED "No free global channels!\n" );
}
return 0;
}
channels.Append( chan );
chan->emitter = this;
chan->parms = chanParms;
chan->soundShader = shader;
chan->logicalChannel = channel;
chan->leadinSample = leadinSample;
chan->loopingSample = loopingSample;
chan->allowSlow = allowSlow;
// return length of sound in milliseconds
int length = chan->leadinSample->LengthInMsec();
// adjust the start time based on diversity for looping sounds, so they don't all start at the same point
int startOffset = 0;
if ( chan->IsLooping() && !shader->leadin ) {
// looping sounds start at a random point...
startOffset = soundSystemLocal.random.RandomInt( length );
}
chan->startTime = currentTime - startOffset;
if ( ( chanParms.soundShaderFlags & SSF_LOOPING ) != 0 ) {
// This channel will never end!
chan->endTime = 0;
} else {
// This channel will automatically end at this time
chan->endTime = chan->startTime + length + 100;
}
if ( showStartSound ) {
if ( loopingSample == NULL || leadinSample == loopingSample ) {
idLib::Printf( "Playing %s @ %d\n", leadinSample->GetName(), startOffset );
} else {
idLib::Printf( "Playing %s then looping %s\n", leadinSample->GetName(), loopingSample->GetName() );
}
}
return length;
}
/*
========================
idSoundEmitterLocal::OnReloadSound
This is a shortened version of StartSound, called whenever a sound shader is reloaded.
If the emitter is currently playing the given sound shader, restart it so
a change in the sound sample used for a given sound shader will be picked up.
========================
*/
void idSoundEmitterLocal::OnReloadSound( const idDecl *decl ) {
}
/*
========================
idSoundEmitterLocal::StopSound
Can pass SCHANNEL_ANY.
========================
*/
void idSoundEmitterLocal::StopSound( const s_channelType channel ) {
assert( soundWorld != NULL );
assert( soundWorld->emitters[this->index] == this );
if ( soundWorld && soundWorld->writeDemo ) {
soundWorld->writeDemo->WriteInt( DS_SOUND );
soundWorld->writeDemo->WriteInt( SCMD_STOP );
soundWorld->writeDemo->WriteInt( index );
soundWorld->writeDemo->WriteInt( channel );
}
for( int i = 0; i < channels.Num(); i++ ) {
idSoundChannel * chan = channels[i];
if ( channel != SCHANNEL_ANY && chan->logicalChannel != channel ) {
continue;
}
if ( s_showStartSound.GetBool() ) {
idLib::Printf( "%dms: StopSound(%d:%d): %s\n", soundWorld->GetSoundTime(), index, channel, chan->soundShader->GetName() );
}
// This forces CheckForCompletion to return true
chan->endTime = 1;
}
}
/*
========================
idSoundEmitterLocal::ModifySound
========================
*/
void idSoundEmitterLocal::ModifySound( const s_channelType channel, const soundShaderParms_t *parms ) {
assert( soundWorld != NULL );
assert( soundWorld->emitters[this->index] == this );
if ( soundWorld && soundWorld->writeDemo ) {
soundWorld->writeDemo->WriteInt( DS_SOUND );
soundWorld->writeDemo->WriteInt( SCMD_MODIFY );
soundWorld->writeDemo->WriteInt( index );
soundWorld->writeDemo->WriteInt( channel );
soundWorld->writeDemo->WriteFloat( parms->minDistance );
soundWorld->writeDemo->WriteFloat( parms->maxDistance );
soundWorld->writeDemo->WriteFloat( parms->volume );
soundWorld->writeDemo->WriteFloat( parms->shakes );
soundWorld->writeDemo->WriteInt( parms->soundShaderFlags );
soundWorld->writeDemo->WriteInt( parms->soundClass );
}
for ( int i = channels.Num() - 1; i >= 0; i-- ) {
idSoundChannel * chan = channels[i];
if ( channel != SCHANNEL_ANY && chan->logicalChannel != channel ) {
continue;
}
if ( s_showStartSound.GetBool() ) {
idLib::Printf( "%dms: ModifySound(%d:%d): %s\n", soundWorld->GetSoundTime(), index, channel, chan->soundShader->GetName() );
}
OverrideParms( &chan->parms, parms, &chan->parms );
}
}
/*
========================
idSoundEmitterLocal::FadeSound
========================
*/
void idSoundEmitterLocal::FadeSound( const s_channelType channel, float to, float over ) {
assert( soundWorld != NULL );
assert( soundWorld->emitters[this->index] == this );
if ( soundWorld->writeDemo ) {
soundWorld->writeDemo->WriteInt( DS_SOUND );
soundWorld->writeDemo->WriteInt( SCMD_FADE );
soundWorld->writeDemo->WriteInt( index );
soundWorld->writeDemo->WriteInt( channel );
soundWorld->writeDemo->WriteFloat( to );
soundWorld->writeDemo->WriteFloat( over );
}
int overMSec = SEC2MS( over );
for ( int i = 0; i < channels.Num(); i++ ) {
idSoundChannel * chan = channels[i];
if ( channel != SCHANNEL_ANY && chan->logicalChannel != channel ) {
continue;
}
if ( s_showStartSound.GetBool() ) {
idLib::Printf( "%dms: FadeSound(%d:%d): %s to %.2fdb over %.2f seconds\n", soundWorld->GetSoundTime(), index, channel, chan->soundShader->GetName(), to, over );
}
// fade it
chan->volumeFade.Fade( to - chan->parms.volume, overMSec, soundWorld->GetSoundTime() );
}
}
/*
========================
idSoundEmitterLocal::CurrentlyPlaying
========================
*/
bool idSoundEmitterLocal::CurrentlyPlaying( const s_channelType channel ) const {
if ( channel == SCHANNEL_ANY ) {
return ( channels.Num() > 0 );
}
for ( int i = 0; i < channels.Num(); ++i ) {
if ( channels[i] != NULL && channels[i]->logicalChannel == channel ) {
if ( channels[i]->endTime == 1 ) {
return false;
} else {
return true;
}
}
}
return false;
}
/*
========================
idSoundEmitterLocal::CurrentAmplitude
========================
*/
float idSoundEmitterLocal::CurrentAmplitude() {
float amplitude = 0.0f;
int currentTime = soundWorld->GetSoundTime();
for ( int i = 0; i < channels.Num(); i++ ) {
idSoundChannel * chan = channels[i];
if ( chan == NULL || currentTime < chan->startTime || ( chan->endTime > 0 && currentTime >= chan->endTime ) ) {
continue;
}
int relativeTime = currentTime - chan->startTime;
int leadinLength = chan->leadinSample->LengthInMsec();
if ( relativeTime < leadinLength ) {
amplitude = Max( amplitude, chan->leadinSample->GetAmplitude( relativeTime ) );
} else if ( chan->loopingSample != NULL ) {
amplitude = Max( amplitude, chan->loopingSample->GetAmplitude( ( relativeTime - leadinLength ) % chan->loopingSample->LengthInMsec() ) );
}
}
return amplitude;
}

483
neo/sound/snd_local.h Normal file
View File

@@ -0,0 +1,483 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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.
===========================================================================
*/
#ifndef __SND_LOCAL_H__
#define __SND_LOCAL_H__
#include "WaveFile.h"
// Maximum number of voices we can have allocated
#define MAX_HARDWARE_VOICES 48
// A single voice can play multiple channels (up to 5.1, but most commonly stereo)
// This is the maximum number of channels which can play simultaneously
// This is limited primarily by seeking on the optical drive, secondarily by memory consumption, and tertiarily by CPU time spent mixing
#define MAX_HARDWARE_CHANNELS 64
// We may need up to 3 buffers for each hardware voice if they are all long sounds
#define MAX_SOUND_BUFFERS ( MAX_HARDWARE_VOICES * 3 )
// Maximum number of channels in a sound sample
#define MAX_CHANNELS_PER_VOICE 8
/*
========================
MsecToSamples
SamplesToMsec
========================
*/
ID_INLINE_EXTERN uint32 MsecToSamples( uint32 msec, uint32 sampleRate ) { return ( msec * ( sampleRate / 100 ) ) / 10; }
ID_INLINE_EXTERN uint32 SamplesToMsec( uint32 samples, uint32 sampleRate ) { return sampleRate < 100 ? 0 : ( samples * 10 ) / ( sampleRate / 100 ); }
/*
========================
DBtoLinear
LinearToDB
========================
*/
ID_INLINE_EXTERN float DBtoLinear( float db ) { return idMath::Pow( 2.0f, db * ( 1.0f / 6.0f ) ); }
ID_INLINE_EXTERN float LinearToDB( float linear ) { return ( linear > 0.0f ) ? ( idMath::Log( linear ) * ( 6.0f / 0.693147181f ) ) : -999.0f; }
// demo sound commands
typedef enum {
SCMD_STATE, // followed by a load game state
SCMD_PLACE_LISTENER,
SCMD_ALLOC_EMITTER,
SCMD_FREE,
SCMD_UPDATE,
SCMD_START,
SCMD_MODIFY,
SCMD_STOP,
SCMD_FADE
} soundDemoCommand_t;
#include "SoundVoice.h"
#define OPERATION_SET 1
#include <dxsdkver.h>
#include <xaudio2.h>
#include <xaudio2fx.h>
#include <X3DAudio.h>
#include <xma2defs.h>
#include "XAudio2/XA2_SoundSample.h"
#include "XAudio2/XA2_SoundVoice.h"
#include "XAudio2/XA2_SoundHardware.h"
//------------------------
// Listener data
//------------------------
struct listener_t {
idMat3 axis; // orientation of the listener
idVec3 pos; // position in meters
int id; // the entity number, used to detect when a sound is local
int area; // area number the listener is in
};
class idSoundFade {
public:
int fadeStartTime;
int fadeEndTime;
float fadeStartVolume;
float fadeEndVolume;
public:
idSoundFade() { Clear(); }
void Clear();
void SetVolume( float to );
void Fade( float to, int length, int soundTime );
float GetVolume( int soundTime ) const;
};
/*
================================================
idSoundChannel
================================================
*/
class idSoundChannel {
public:
bool CanMute() const;
void Mute();
bool CheckForCompletion( int currentTime );
void UpdateVolume( int currentTime );
void UpdateHardware( float volumeAdd, int currentTime );
// returns true if this channel is marked as looping
bool IsLooping() const;
class idSoundEmitterLocal * emitter;
int startTime;
int endTime;
int logicalChannel;
bool allowSlow;
soundShaderParms_t parms; // combines shader parms and per-channel overrides
const idSoundShader * soundShader;
idSoundSample * leadinSample;
idSoundSample * loopingSample;
idSoundFade volumeFade;
float volumeDB; // last volume at which this channel will play (calculated in UpdateVolume)
float currentAmplitude; // current amplitude on the hardware voice
// hardwareVoice will be freed and NULL'd when a sound is out of range,
// and reallocated when it comes back in range
idSoundVoice * hardwareVoice;
// only allocated by the soundWorld block allocator
idSoundChannel();
~idSoundChannel();
};
// Maximum number of SoundChannels for a single SoundEmitter.
// This is probably excessive...
const int MAX_CHANNELS_PER_EMITTER = 16;
/*
===================================================================================
idSoundWorldLocal
===================================================================================
*/
class idSoundWorldLocal : public idSoundWorld {
public:
idSoundWorldLocal();
virtual ~idSoundWorldLocal();
//------------------------
// Functions from idSoundWorld, implemented in SoundWorld.cpp
//------------------------
// Called at map start
virtual void ClearAllSoundEmitters();
// stop all playing sounds
virtual void StopAllSounds();
// get a new emitter that can play sounds in this world
virtual idSoundEmitter *AllocSoundEmitter();
// for load games
virtual idSoundEmitter *EmitterForIndex( int index );
// query data from all emitters in the world
virtual float CurrentShakeAmplitude();
// where is the camera
virtual void PlaceListener( const idVec3 &origin, const idMat3 &axis, const int listenerId );
// fade all sounds in the world with a given shader soundClass
// to is in Db, over is in seconds
virtual void FadeSoundClasses( const int soundClass, const float to, const float over );
// dumps the current state and begins archiving commands
virtual void StartWritingDemo( idDemoFile *demo );
virtual void StopWritingDemo();
// read a sound command from a demo file
virtual void ProcessDemoCommand( idDemoFile *readDemo );
// menu sounds
virtual int PlayShaderDirectly( const char *name, int channel = -1 );
virtual void Skip( int time );
virtual void Pause();
virtual void UnPause();
virtual bool IsPaused() { return isPaused; }
virtual int GetSoundTime();
// avidump
virtual void AVIOpen( const char *path, const char *name );
virtual void AVIClose();
// SaveGame Support
virtual void WriteToSaveGame( idFile *savefile );
virtual void ReadFromSaveGame( idFile *savefile );
virtual void SetSlowmoSpeed( float speed );
virtual void SetEnviroSuit( bool active );
//=======================================
//------------------------
// Random stuff that's not exposed outside the sound system
//------------------------
void Update();
void OnReloadSound( const idDecl *decl );
idSoundChannel * AllocSoundChannel();
void FreeSoundChannel( idSoundChannel * );
public:
// even though all these variables are public, nobody outside the sound system includes SoundWorld_local.h
// so this is equivalent to making it private and friending all the other classes in the sound system
idSoundFade volumeFade; // master volume knob for the entire world
idSoundFade soundClassFade[SOUND_MAX_CLASSES];
idRenderWorld * renderWorld; // for debug visualization and light amplitude sampling
idDemoFile * writeDemo; // if not NULL, archive commands here
float currentCushionDB; // channels at or below this level will be faded to 0
float shakeAmp; // last calculated shake amplitude
listener_t listener;
idList<idSoundEmitterLocal *, TAG_AUDIO> emitters;
idSoundEmitter * localSound; // for PlayShaderDirectly()
idBlockAlloc<idSoundEmitterLocal, 16> emitterAllocator;
idBlockAlloc<idSoundChannel, 16> channelAllocator;
idSoundFade pauseFade;
int pausedTime;
int accumulatedPauseTime;
bool isPaused;
float slowmoSpeed;
bool enviroSuitActive;
public:
struct soundPortalTrace_t {
int portalArea;
const soundPortalTrace_t * prevStack;
};
void ResolveOrigin( const int stackDepth, const soundPortalTrace_t * prevStack, const int soundArea, const float dist, const idVec3 & soundOrigin, idSoundEmitterLocal * def );
};
/*
================================================
idSoundEmitterLocal
================================================
*/
class idSoundEmitterLocal : public idSoundEmitter {
public:
virtual void Free( bool immediate );
virtual void Reset();
virtual void UpdateEmitter( const idVec3 &origin, int listenerId, const soundShaderParms_t *parms );
virtual int StartSound( const idSoundShader *shader, const s_channelType channel, float diversity = 0, int shaderFlags = 0, bool allowSlow = true );
virtual void ModifySound( const s_channelType channel, const soundShaderParms_t *parms );
virtual void StopSound( const s_channelType channel );
virtual void FadeSound( const s_channelType channel, float to, float over );
virtual bool CurrentlyPlaying( const s_channelType channel = SCHANNEL_ANY ) const;
virtual float CurrentAmplitude();
virtual int Index() const;
//----------------------------------------------
void Init( int i, idSoundWorldLocal * sw );
// Returns true if the emitter should be freed.
bool CheckForCompletion( int currentTime );
void OverrideParms( const soundShaderParms_t * base, const soundShaderParms_t * over, soundShaderParms_t * out );
void Update( int currentTime );
void OnReloadSound( const idDecl *decl );
//----------------------------------------------
idSoundWorldLocal * soundWorld; // the world that holds this emitter
int index; // in world emitter list
bool canFree; // if true, this emitter can be canFree (once channels.Num() == 0)
// a single soundEmitter can have many channels playing from the same point
idStaticList<idSoundChannel *, MAX_CHANNELS_PER_EMITTER> channels;
//----- set by UpdateEmitter -----
idVec3 origin;
soundShaderParms_t parms;
int emitterId; // sounds will be full volume when emitterId == listenerId
//----- set by Update -----
int lastValidPortalArea;
float directDistance;
float spatializedDistance;
idVec3 spatializedOrigin;
// sound emitters are only allocated by the soundWorld block allocator
idSoundEmitterLocal();
virtual ~idSoundEmitterLocal();
};
/*
===================================================================================
idSoundSystemLocal
===================================================================================
*/
class idSoundSystemLocal : public idSoundSystem {
public:
// all non-hardware initialization
virtual void Init();
// shutdown routine
virtual void Shutdown();
virtual idSoundWorld * AllocSoundWorld( idRenderWorld *rw );
virtual void FreeSoundWorld( idSoundWorld *sw );
// specifying NULL will cause silence to be played
virtual void SetPlayingSoundWorld( idSoundWorld *soundWorld );
// some tools, like the sound dialog, may be used in both the game and the editor
// This can return NULL, so check!
virtual idSoundWorld * GetPlayingSoundWorld();
// sends the current playing sound world information to the sound hardware
virtual void Render();
// Mutes the SSG_MUSIC group
virtual void MuteBackgroundMusic( bool mute ) { musicMuted = mute; }
// sets the final output volume to 0
// This should only be used when the app is deactivated
// Since otherwise there will be problems with different subsystems muting and unmuting at different times
virtual void SetMute( bool mute ) { muted = mute; }
virtual bool IsMuted() { return muted; }
virtual void OnReloadSound( const idDecl * sound );
virtual void StopAllSounds();
virtual void InitStreamBuffers();
virtual void FreeStreamBuffers();
virtual void * GetIXAudio2() const;
// for the sound level meter window
virtual cinData_t ImageForTime( const int milliseconds, const bool waveform );
// Free all sounds loaded during the last map load
virtual void BeginLevelLoad();
// We might want to defer the loading of new sounds to this point
virtual void EndLevelLoad();
// prints memory info
virtual void PrintMemInfo( MemInfo_t *mi );
//-------------------------
// Before a sound is reloaded, any active voices using it must
// be stopped. Returns true if any were playing, and should be
// restarted after the sound is reloaded.
void StopVoicesWithSample( const idSoundSample * const sample );
void Restart();
void SetNeedsRestart() { needsRestart = true; }
int SoundTime() const;
// may return NULL if there are no more voices left
idSoundVoice * AllocateVoice( const idSoundSample * leadinSample, const idSoundSample * loopingSample );
void FreeVoice( idSoundVoice * );
idSoundSample * LoadSample( const char * name );
virtual void Preload( idPreloadManifest & preload );
struct bufferContext_t {
bufferContext_t() :
voice( NULL ),
sample( NULL ),
bufferNumber( 0 )
{ }
idSoundVoice_XAudio2 * voice;
idSoundSample_XAudio2 * sample;
int bufferNumber;
};
// Get a stream buffer from the free pool, returns NULL if none are available
bufferContext_t * ObtainStreamBufferContext();
void ReleaseStreamBufferContext( bufferContext_t * p );
idSysMutex streamBufferMutex;
idStaticList< bufferContext_t *, MAX_SOUND_BUFFERS > freeStreamBufferContexts;
idStaticList< bufferContext_t *, MAX_SOUND_BUFFERS > activeStreamBufferContexts;
idStaticList< bufferContext_t, MAX_SOUND_BUFFERS > bufferContexts;
idSoundWorldLocal * currentSoundWorld;
idStaticList<idSoundWorldLocal *, 32> soundWorlds;
idList<idSoundSample *, TAG_AUDIO> samples;
idHashIndex sampleHash;
idSoundHardware hardware;
idRandom2 random;
int soundTime;
bool muted;
bool musicMuted;
bool needsRestart;
bool insideLevelLoad;
//-------------------------
idSoundSystemLocal() :
soundTime( 0 ),
currentSoundWorld( NULL ),
muted( false ),
musicMuted( false ),
needsRestart( false )
{}
};
extern idSoundSystemLocal soundSystemLocal;
#endif /* !__SND_LOCAL_H__ */

421
neo/sound/snd_shader.cpp Normal file
View File

@@ -0,0 +1,421 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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"
#include "snd_local.h"
extern idCVar s_maxSamples;
typedef enum {
SPEAKER_LEFT = 0,
SPEAKER_RIGHT,
SPEAKER_CENTER,
SPEAKER_LFE,
SPEAKER_BACKLEFT,
SPEAKER_BACKRIGHT
} speakerLabel;
/*
===============
idSoundShader::Init
===============
*/
void idSoundShader::Init() {
leadin = false;
leadinVolume = 0;
altSound = NULL;
}
/*
===============
idSoundShader::idSoundShader
===============
*/
idSoundShader::idSoundShader() {
Init();
}
/*
===============
idSoundShader::~idSoundShader
===============
*/
idSoundShader::~idSoundShader() {
}
/*
=================
idSoundShader::Size
=================
*/
size_t idSoundShader::Size() const {
return sizeof( idSoundShader );
}
/*
===============
idSoundShader::idSoundShader::FreeData
===============
*/
void idSoundShader::FreeData() {
}
/*
===================
idSoundShader::SetDefaultText
===================
*/
bool idSoundShader::SetDefaultText() {
idStr wavname;
wavname = GetName();
wavname.DefaultFileExtension( ".wav" ); // if the name has .ogg in it, that will stay
// if there exists a wav file with the same name
if ( 1 ) { //fileSystem->ReadFile( wavname, NULL ) != -1 ) {
char generated[2048];
idStr::snPrintf( generated, sizeof( generated ),
"sound %s // IMPLICITLY GENERATED\n"
"{\n"
"%s\n"
"}\n", GetName(), wavname.c_str() );
SetText( generated );
return true;
} else {
return false;
}
}
/*
===================
DefaultDefinition
===================
*/
const char *idSoundShader::DefaultDefinition() const {
return
"{\n"
"\t" "_default.wav\n"
"}";
}
/*
===============
idSoundShader::Parse
this is called by the declManager
===============
*/
bool idSoundShader::Parse( const char *text, const int textLength, bool allowBinaryVersion ) {
idLexer src;
src.LoadMemory( text, textLength, GetFileName(), GetLineNum() );
src.SetFlags( DECL_LEXER_FLAGS );
src.SkipUntilString( "{" );
if ( !ParseShader( src ) ) {
MakeDefault();
return false;
}
return true;
}
/*
===============
idSoundShader::ParseShader
===============
*/
bool idSoundShader::ParseShader( idLexer &src ) {
idToken token;
parms.minDistance = 1;
parms.maxDistance = 10;
parms.volume = 1;
parms.shakes = 0;
parms.soundShaderFlags = 0;
parms.soundClass = 0;
speakerMask = 0;
altSound = NULL;
entries.Clear();
while ( 1 ) {
if ( !src.ExpectAnyToken( &token ) ) {
return false;
}
// end of definition
else if ( token == "}" ) {
break;
}
// minimum number of sounds
else if ( !token.Icmp( "minSamples" ) ) {
src.ParseInt();
}
// description
else if ( !token.Icmp( "description" ) ) {
src.ReadTokenOnLine( &token );
}
// mindistance
else if ( !token.Icmp( "mindistance" ) ) {
parms.minDistance = src.ParseFloat();
}
// maxdistance
else if ( !token.Icmp( "maxdistance" ) ) {
parms.maxDistance = src.ParseFloat();
}
// shakes screen
else if ( !token.Icmp( "shakes" ) ) {
src.ExpectAnyToken( &token );
if ( token.type == TT_NUMBER ) {
parms.shakes = token.GetFloatValue();
} else {
src.UnreadToken( &token );
parms.shakes = 1.0f;
}
}
// reverb
else if ( !token.Icmp( "reverb" ) ) {
src.ParseFloat();
if ( !src.ExpectTokenString( "," ) ) {
src.FreeSource();
return false;
}
src.ParseFloat();
// no longer supported
}
// volume
else if ( !token.Icmp( "volume" ) ) {
parms.volume = src.ParseFloat();
}
// leadinVolume is used to allow light breaking leadin sounds to be much louder than the broken loop
else if ( !token.Icmp( "leadinVolume" ) ) {
leadinVolume = src.ParseFloat();
leadin = true;
}
// speaker mask
else if ( !token.Icmp( "mask_center" ) ) {
speakerMask |= 1<<SPEAKER_CENTER;
}
// speaker mask
else if ( !token.Icmp( "mask_left" ) ) {
speakerMask |= 1<<SPEAKER_LEFT;
}
// speaker mask
else if ( !token.Icmp( "mask_right" ) ) {
speakerMask |= 1<<SPEAKER_RIGHT;
}
// speaker mask
else if ( !token.Icmp( "mask_backright" ) ) {
speakerMask |= 1<<SPEAKER_BACKRIGHT;
}
// speaker mask
else if ( !token.Icmp( "mask_backleft" ) ) {
speakerMask |= 1<<SPEAKER_BACKLEFT;
}
// speaker mask
else if ( !token.Icmp( "mask_lfe" ) ) {
speakerMask |= 1<<SPEAKER_LFE;
}
// soundClass
else if ( !token.Icmp( "soundClass" ) ) {
parms.soundClass = src.ParseInt();
if ( parms.soundClass < 0 || parms.soundClass >= SOUND_MAX_CLASSES ) {
src.Warning( "SoundClass out of range" );
return false;
}
}
// altSound
else if ( !token.Icmp( "altSound" ) ) {
if ( !src.ExpectAnyToken( &token ) ) {
return false;
}
altSound = declManager->FindSound( token.c_str() );
}
// ordered
else if ( !token.Icmp( "ordered" ) ) {
// no longer supported
}
// no_dups
else if ( !token.Icmp( "no_dups" ) ) {
parms.soundShaderFlags |= SSF_NO_DUPS;
}
// no_flicker
else if ( !token.Icmp( "no_flicker" ) ) {
parms.soundShaderFlags |= SSF_NO_FLICKER;
}
// plain
else if ( !token.Icmp( "plain" ) ) {
// no longer supported
}
// looping
else if ( !token.Icmp( "looping" ) ) {
parms.soundShaderFlags |= SSF_LOOPING;
}
// no occlusion
else if ( !token.Icmp( "no_occlusion" ) ) {
parms.soundShaderFlags |= SSF_NO_OCCLUSION;
}
// private
else if ( !token.Icmp( "private" ) ) {
parms.soundShaderFlags |= SSF_PRIVATE_SOUND;
}
// antiPrivate
else if ( !token.Icmp( "antiPrivate" ) ) {
parms.soundShaderFlags |= SSF_ANTI_PRIVATE_SOUND;
}
// once
else if ( !token.Icmp( "playonce" ) ) {
parms.soundShaderFlags |= SSF_PLAY_ONCE;
}
// global
else if ( !token.Icmp( "global" ) ) {
parms.soundShaderFlags |= SSF_GLOBAL;
}
// unclamped
else if ( !token.Icmp( "unclamped" ) ) {
parms.soundShaderFlags |= SSF_UNCLAMPED;
}
// omnidirectional
else if ( !token.Icmp( "omnidirectional" ) ) {
parms.soundShaderFlags |= SSF_OMNIDIRECTIONAL;
}
else if ( !token.Icmp( "onDemand" ) ) {
// no longer loading sounds on demand
}
// the wave files
else if ( !token.Icmp( "leadin" ) ) {
leadin = true;
} else if ( token.Find( ".wav", false ) != -1 || token.Find( ".ogg", false ) != -1 ) {
if ( token.IcmpPrefixPath( "sound/vo/" ) == 0 || token.IcmpPrefixPath( "sound/guis/" ) == 0 ) {
parms.soundShaderFlags |= SSF_VO;
}
if ( token.IcmpPrefixPath( "sound/musical/" ) == 0 ) {
parms.soundShaderFlags |= SSF_MUSIC;
}
// add to the wav list
if ( s_maxSamples.GetInteger() == 0 || ( s_maxSamples.GetInteger() > 0 && entries.Num() < s_maxSamples.GetInteger() ) ) {
entries.Append( soundSystemLocal.LoadSample( token.c_str() ) );
}
} else {
src.Warning( "unknown token '%s'", token.c_str() );
return false;
}
}
return true;
}
/*
===============
idSoundShader::List
===============
*/
void idSoundShader::List() const {
idStrList shaders;
common->Printf( "%4i: %s\n", Index(), GetName() );
for( int k = 0; k < entries.Num(); k++ ) {
const idSoundSample *objectp = entries[k];
if ( objectp ) {
common->Printf( " %5dms %4dKb %s\n", objectp->LengthInMsec(), (objectp->BufferSize()/1024), objectp->GetName() );
}
}
}
/*
===============
idSoundShader::GetAltSound
===============
*/
const idSoundShader *idSoundShader::GetAltSound() const {
return altSound;
}
/*
===============
idSoundShader::GetMinDistance
===============
*/
float idSoundShader::GetMinDistance() const {
return parms.minDistance;
}
/*
===============
idSoundShader::GetMaxDistance
===============
*/
float idSoundShader::GetMaxDistance() const {
return parms.maxDistance;
}
/*
===============
idSoundShader::HasDefaultSound
===============
*/
bool idSoundShader::HasDefaultSound() const {
for ( int i = 0; i < entries.Num(); i++ ) {
if ( entries[i] && entries[i]->IsDefault() ) {
return true;
}
}
return false;
}
/*
===============
idSoundShader::GetParms
===============
*/
const soundShaderParms_t *idSoundShader::GetParms() const {
return &parms;
}
/*
===============
idSoundShader::GetNumSounds
===============
*/
int idSoundShader::GetNumSounds() const {
return entries.Num();
}
/*
===============
idSoundShader::GetSound
===============
*/
const char *idSoundShader::GetSound( int index ) const {
if ( index >= 0 && index < entries.Num() ) {
return entries[index]->GetName();
}
return "";
}

596
neo/sound/snd_system.cpp Normal file
View File

@@ -0,0 +1,596 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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"
#include "snd_local.h"
idCVar s_noSound( "s_noSound", "0", CVAR_BOOL, "returns NULL for all sounds loaded and does not update the sound rendering" );
#ifdef ID_RETAIL
idCVar s_useCompression( "s_useCompression", "1", CVAR_BOOL, "Use compressed sound files (mp3/xma)" );
idCVar s_playDefaultSound( "s_playDefaultSound", "0", CVAR_BOOL, "play a beep for missing sounds" );
idCVar s_maxSamples( "s_maxSamples", "5", CVAR_INTEGER, "max samples to load per shader" );
#else
idCVar s_useCompression( "s_useCompression", "1", CVAR_BOOL, "Use compressed sound files (mp3/xma)" );
idCVar s_playDefaultSound( "s_playDefaultSound", "1", CVAR_BOOL, "play a beep for missing sounds" );
idCVar s_maxSamples( "s_maxSamples", "5", CVAR_INTEGER, "max samples to load per shader" );
#endif
idCVar preLoad_Samples( "preLoad_Samples", "1", CVAR_SYSTEM | CVAR_BOOL, "preload samples during beginlevelload" );
idSoundSystemLocal soundSystemLocal;
idSoundSystem * soundSystem = &soundSystemLocal;
/*
================================================================================================
idSoundSystemLocal
================================================================================================
*/
/*
========================
TestSound_f
This is called from the main thread.
========================
*/
void TestSound_f( const idCmdArgs & args ) {
if ( args.Argc() != 2 ) {
idLib::Printf( "Usage: testSound <file>\n" );
return;
}
if ( soundSystemLocal.currentSoundWorld ) {
soundSystemLocal.currentSoundWorld->PlayShaderDirectly( args.Argv( 1 ) );
}
}
/*
========================
RestartSound_f
========================
*/
void RestartSound_f( const idCmdArgs & args ) {
soundSystemLocal.Restart();
}
/*
========================
ListSamples_f
========================
*/
void ListSamples_f( const idCmdArgs & args ) {
idLib::Printf( "Sound samples\n-------------\n" );
int totSize = 0;
for ( int i = 0; i < soundSystemLocal.samples.Num(); i++ ) {
idLib::Printf( "%05dkb\t%s\n", soundSystemLocal.samples[ i ]->BufferSize() / 1024, soundSystemLocal.samples[ i ]->GetName() );
totSize += soundSystemLocal.samples[ i ]->BufferSize();
}
idLib::Printf( "--------------------------\n" );
idLib::Printf( "%05dkb total size\n", totSize / 1024 );
}
/*
========================
idSoundSystemLocal::Restart
========================
*/
void idSoundSystemLocal::Restart() {
// Mute all channels in all worlds
for ( int i = 0; i < soundWorlds.Num(); i++ ) {
idSoundWorldLocal * sw = soundWorlds[i];
for ( int e = 0; e < sw->emitters.Num(); e++ ) {
idSoundEmitterLocal * emitter = sw->emitters[e];
for ( int c = 0; c < emitter->channels.Num(); c++ ) {
emitter->channels[c]->Mute();
}
}
}
// Shutdown sound hardware
hardware.Shutdown();
// Reinitialize sound hardware
if ( !s_noSound.GetBool() ) {
hardware.Init();
}
InitStreamBuffers();
}
/*
========================
idSoundSystemLocal::Init
Initialize the SoundSystem.
========================
*/
void idSoundSystemLocal::Init() {
idLib::Printf( "----- Initializing Sound System ------\n" );
soundTime = Sys_Milliseconds();
random.SetSeed( soundTime );
if ( !s_noSound.GetBool() ) {
hardware.Init();
InitStreamBuffers();
}
cmdSystem->AddCommand( "testSound", TestSound_f, 0, "tests a sound", idCmdSystem::ArgCompletion_SoundName );
cmdSystem->AddCommand( "s_restart", RestartSound_f, 0, "restart sound system" );
cmdSystem->AddCommand( "listSamples", ListSamples_f, 0, "lists all loaded sound samples" );
idLib::Printf( "sound system initialized.\n" );
idLib::Printf( "--------------------------------------\n" );
}
/*
========================
idSoundSystemLocal::InitStreamBuffers
========================
*/
void idSoundSystemLocal::InitStreamBuffers() {
streamBufferMutex.Lock();
const bool empty = ( bufferContexts.Num() == 0 );
if ( empty ) {
bufferContexts.SetNum( MAX_SOUND_BUFFERS );
for ( int i = 0; i < MAX_SOUND_BUFFERS; i++ ) {
freeStreamBufferContexts.Append( &( bufferContexts[ i ] ) );
}
} else {
for ( int i = 0; i < activeStreamBufferContexts.Num(); i++ ) {
freeStreamBufferContexts.Append( activeStreamBufferContexts[ i ] );
}
activeStreamBufferContexts.Clear();
}
assert( bufferContexts.Num() == MAX_SOUND_BUFFERS );
assert( freeStreamBufferContexts.Num() == MAX_SOUND_BUFFERS );
assert( activeStreamBufferContexts.Num() == 0 );
streamBufferMutex.Unlock();
}
/*
========================
idSoundSystemLocal::FreeStreamBuffers
========================
*/
void idSoundSystemLocal::FreeStreamBuffers() {
streamBufferMutex.Lock();
bufferContexts.Clear();
freeStreamBufferContexts.Clear();
activeStreamBufferContexts.Clear();
streamBufferMutex.Unlock();
}
/*
========================
idSoundSystemLocal::Shutdown
========================
*/
void idSoundSystemLocal::Shutdown() {
hardware.Shutdown();
FreeStreamBuffers();
samples.DeleteContents( true );
sampleHash.Free();
}
/*
========================
idSoundSystemLocal::ObtainStreamBuffer
Get a stream buffer from the free pool, returns NULL if none are available
========================
*/
idSoundSystemLocal::bufferContext_t * idSoundSystemLocal::ObtainStreamBufferContext() {
bufferContext_t * bufferContext = NULL;
streamBufferMutex.Lock();
if ( freeStreamBufferContexts.Num() != 0 ) {
bufferContext = freeStreamBufferContexts[ freeStreamBufferContexts.Num() - 1 ];
freeStreamBufferContexts.SetNum( freeStreamBufferContexts.Num() - 1 );
activeStreamBufferContexts.Append( bufferContext );
}
streamBufferMutex.Unlock();
return bufferContext;
}
/*
========================
idSoundSystemLocal::ReleaseStreamBuffer
Releases a stream buffer back to the free pool
========================
*/
void idSoundSystemLocal::ReleaseStreamBufferContext( bufferContext_t * bufferContext ) {
streamBufferMutex.Lock();
if ( activeStreamBufferContexts.Remove( bufferContext ) ) {
freeStreamBufferContexts.Append( bufferContext );
}
streamBufferMutex.Unlock();
}
/*
========================
idSoundSystemLocal::AllocSoundWorld
========================
*/
idSoundWorld * idSoundSystemLocal::AllocSoundWorld( idRenderWorld *rw ) {
idSoundWorldLocal * local = new (TAG_AUDIO) idSoundWorldLocal;
local->renderWorld = rw;
soundWorlds.Append( local );
return local;
}
/*
========================
idSoundSystemLocal::FreeSoundWorld
========================
*/
void idSoundSystemLocal::FreeSoundWorld( idSoundWorld *sw ) {
idSoundWorldLocal *local = static_cast<idSoundWorldLocal*>( sw );
soundWorlds.Remove( local );
delete local;
}
/*
========================
idSoundSystemLocal::SetPlayingSoundWorld
Specifying NULL will cause silence to be played.
========================
*/
void idSoundSystemLocal::SetPlayingSoundWorld( idSoundWorld *soundWorld ) {
if ( currentSoundWorld == soundWorld ) {
return;
}
idSoundWorldLocal * oldSoundWorld = currentSoundWorld;
currentSoundWorld = static_cast<idSoundWorldLocal *>( soundWorld );
if ( oldSoundWorld != NULL ) {
oldSoundWorld->Update();
}
}
/*
========================
idSoundSystemLocal::GetPlayingSoundWorld
========================
*/
idSoundWorld * idSoundSystemLocal::GetPlayingSoundWorld() {
return currentSoundWorld;
}
/*
========================
idSoundSystemLocal::Render
========================
*/
void idSoundSystemLocal::Render() {
if ( s_noSound.GetBool() ) {
return;
}
if ( needsRestart ) {
needsRestart = false;
Restart();
}
SCOPED_PROFILE_EVENT( "SoundSystem::Render" );
if ( currentSoundWorld != NULL ) {
currentSoundWorld->Update();
}
hardware.Update();
// The sound system doesn't use game time or anything like that because the sounds are decoded in real time.
soundTime = Sys_Milliseconds();
}
/*
========================
idSoundSystemLocal::OnReloadSound
========================
*/
void idSoundSystemLocal::OnReloadSound( const idDecl* sound ) {
for ( int i = 0; i < soundWorlds.Num(); i++ ) {
soundWorlds[i]->OnReloadSound( sound );
}
}
/*
========================
idSoundSystemLocal::StopAllSounds
========================
*/
void idSoundSystemLocal::StopAllSounds() {
for ( int i = 0; i < soundWorlds.Num(); i++ ) {
idSoundWorld *sw = soundWorlds[i];
if ( sw ) {
sw->StopAllSounds();
}
}
hardware.Update();
}
/*
========================
idSoundSystemLocal::GetIXAudio2
========================
*/
void * idSoundSystemLocal::GetIXAudio2() const {
return (void *)hardware.GetIXAudio2();
}
/*
========================
idSoundSystemLocal::SoundTime
========================
*/
int idSoundSystemLocal::SoundTime() const {
return soundTime;
}
/*
========================
idSoundSystemLocal::AllocateVoice
========================
*/
idSoundVoice * idSoundSystemLocal::AllocateVoice( const idSoundSample * leadinSample, const idSoundSample * loopingSample ) {
return hardware.AllocateVoice( leadinSample, loopingSample );
}
/*
========================
idSoundSystemLocal::FreeVoice
========================
*/
void idSoundSystemLocal::FreeVoice( idSoundVoice * voice ) {
hardware.FreeVoice( voice );
}
/*
========================
idSoundSystemLocal::LoadSample
========================
*/
idSoundSample * idSoundSystemLocal::LoadSample( const char * name ) {
idStrStatic< MAX_OSPATH > canonical = name;
canonical.ToLower();
canonical.BackSlashesToSlashes();
canonical.StripFileExtension();
int hashKey = idStr::Hash( canonical );
for ( int i = sampleHash.First( hashKey ); i != -1; i = sampleHash.Next( i ) ) {
if ( idStr::Cmp( samples[i]->GetName(), canonical ) == 0 ) {
samples[i]->SetLevelLoadReferenced();
return samples[i];
}
}
idSoundSample * sample = new (TAG_AUDIO) idSoundSample;
sample->SetName( canonical );
sampleHash.Add( hashKey, samples.Append( sample ) );
if ( !insideLevelLoad ) {
// Sound sample referenced before any map is loaded
sample->SetNeverPurge();
sample->LoadResource();
} else {
sample->SetLevelLoadReferenced();
}
if ( cvarSystem->GetCVarBool( "fs_buildgame" ) ) {
fileSystem->AddSamplePreload( canonical );
}
return sample;
}
/*
========================
idSoundSystemLocal::StopVoicesWithSample
A sample is about to be freed, make sure the hardware isn't mixing from it.
========================
*/
void idSoundSystemLocal::StopVoicesWithSample( const idSoundSample * const sample ) {
for ( int w = 0; w < soundWorlds.Num(); w++ ) {
idSoundWorldLocal * sw = soundWorlds[w];
if ( sw == NULL ) {
continue;
}
for ( int e = 0; e < sw->emitters.Num(); e++ ) {
idSoundEmitterLocal * emitter = sw->emitters[e];
if ( emitter == NULL ) {
continue;
}
for ( int i = 0; i < emitter->channels.Num(); i++ ) {
if ( emitter->channels[i]->leadinSample == sample || emitter->channels[i]->loopingSample == sample ) {
emitter->channels[i]->Mute();
}
}
}
}
}
/*
========================
idSoundSystemLocal::FreeVoice
========================
*/
cinData_t idSoundSystemLocal::ImageForTime( const int milliseconds, const bool waveform ) {
cinData_t cd;
cd.imageY = NULL;
cd.imageCr = NULL;
cd.imageCb = NULL;
cd.imageWidth = 0;
cd.imageHeight = 0;
cd.status = FMV_IDLE;
return cd;
}
/*
========================
idSoundSystemLocal::BeginLevelLoad
========================
*/
void idSoundSystemLocal::BeginLevelLoad() {
insideLevelLoad = true;
for ( int i = 0; i < samples.Num(); i++ ) {
if ( samples[i]->GetNeverPurge() ) {
continue;
}
samples[i]->FreeData();
samples[i]->ResetLevelLoadReferenced();
}
}
/*
========================
idSoundSystemLocal::Preload
========================
*/
void idSoundSystemLocal::Preload( idPreloadManifest & manifest ) {
idStrStatic< MAX_OSPATH > filename;
int start = Sys_Milliseconds();
int numLoaded = 0;
idList< preloadSort_t > preloadSort;
preloadSort.Resize( manifest.NumResources() );
for ( int i = 0; i < manifest.NumResources(); i++ ) {
const preloadEntry_s & p = manifest.GetPreloadByIndex( i );
idResourceCacheEntry rc;
// FIXME: write these out sorted
if ( p.resType == PRELOAD_SAMPLE ) {
if ( p.resourceName.Find( "/vo/", false ) >= 0 ) {
continue;
}
filename = "generated/";
filename += p.resourceName;
filename.SetFileExtension( "idwav" );
if ( fileSystem->GetResourceCacheEntry( filename, rc ) ) {
preloadSort_t ps = {};
ps.idx = i;
ps.ofs = rc.offset;
preloadSort.Append( ps );
}
}
}
preloadSort.SortWithTemplate( idSort_Preload() );
for ( int i = 0; i < preloadSort.Num(); i++ ) {
const preloadSort_t & ps = preloadSort[ i ];
const preloadEntry_s & p = manifest.GetPreloadByIndex( ps.idx );
filename = p.resourceName;
filename.Replace( "generated/", "" );
numLoaded++;
idSoundSample *sample = LoadSample( filename );
if ( sample != NULL && !sample->IsLoaded() ) {
sample->LoadResource();
sample->SetLevelLoadReferenced();
}
}
int end = Sys_Milliseconds();
common->Printf( "%05d sounds preloaded in %5.1f seconds\n", numLoaded, ( end - start ) * 0.001 );
common->Printf( "----------------------------------------\n" );
}
/*
========================
idSoundSystemLocal::EndLevelLoad
========================
*/
void idSoundSystemLocal::EndLevelLoad() {
insideLevelLoad = false;
common->Printf( "----- idSoundSystemLocal::EndLevelLoad -----\n" );
int start = Sys_Milliseconds();
int keepCount = 0;
int loadCount = 0;
idList< preloadSort_t > preloadSort;
preloadSort.Resize( samples.Num() );
for ( int i = 0; i < samples.Num(); i++ ) {
common->UpdateLevelLoadPacifier();
if ( samples[i]->GetNeverPurge() ) {
continue;
}
if ( samples[i]->IsLoaded() ) {
keepCount++;
continue;
}
if ( samples[i]->GetLevelLoadReferenced() ) {
idStrStatic< MAX_OSPATH > filename = "generated/";
filename += samples[ i ]->GetName();
filename.SetFileExtension( "idwav" );
preloadSort_t ps = {};
ps.idx = i;
idResourceCacheEntry rc;
if ( fileSystem->GetResourceCacheEntry( filename, rc ) ) {
ps.ofs = rc.offset;
} else {
ps.ofs = 0;
}
preloadSort.Append( ps );
loadCount++;
}
}
preloadSort.SortWithTemplate( idSort_Preload() );
for ( int i = 0; i < preloadSort.Num(); i++ ) {
common->UpdateLevelLoadPacifier();
samples[ preloadSort[ i ].idx ]->LoadResource();
}
int end = Sys_Milliseconds();
common->Printf( "%5i sounds loaded in %5.1f seconds\n", loadCount, (end-start) * 0.001 );
common->Printf( "----------------------------------------\n" );
}
/*
========================
idSoundSystemLocal::FreeVoice
========================
*/
void idSoundSystemLocal::PrintMemInfo( MemInfo_t *mi ) {
}

1121
neo/sound/snd_world.cpp Normal file

File diff suppressed because it is too large Load Diff

335
neo/sound/sound.h Normal file
View File

@@ -0,0 +1,335 @@
/*
===========================================================================
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 <http://www.gnu.org/licenses/>.
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.
===========================================================================
*/
#ifndef __SOUND__
#define __SOUND__
/*
===============================================================================
SOUND SHADER DECL
===============================================================================
*/
// unfortunately, our minDistance / maxDistance is specified in meters, and
// we have far too many of them to change at this time.
const float DOOM_TO_METERS = 0.0254f; // doom to meters
const float METERS_TO_DOOM = (1.0f/DOOM_TO_METERS); // meters to doom
const float DB_SILENCE = -60.0f;
class idSoundSample;
// sound shader flags
static const int SSF_PRIVATE_SOUND = BIT(0); // only plays for the current listenerId
static const int SSF_ANTI_PRIVATE_SOUND =BIT(1); // plays for everyone but the current listenerId
static const int SSF_NO_OCCLUSION = BIT(2); // don't flow through portals, only use straight line
static const int SSF_GLOBAL = BIT(3); // play full volume to all speakers and all listeners
static const int SSF_OMNIDIRECTIONAL = BIT(4); // fall off with distance, but play same volume in all speakers
static const int SSF_LOOPING = BIT(5); // repeat the sound continuously
static const int SSF_PLAY_ONCE = BIT(6); // never restart if already playing on any channel of a given emitter
static const int SSF_UNCLAMPED = BIT(7); // don't clamp calculated volumes at 1.0
static const int SSF_NO_FLICKER = BIT(8); // always return 1.0 for volume queries
static const int SSF_NO_DUPS = BIT(9); // try not to play the same sound twice in a row
static const int SSF_VO = BIT(10);// VO - direct a portion of the sound through the center channel (set automatically on shaders that contain files that start with "sound/vo/")
static const int SSF_MUSIC = BIT(11);// Music - Muted when the player is playing his own music
// these options can be overriden from sound shader defaults on a per-emitter and per-channel basis
typedef struct {
float minDistance;
float maxDistance;
float volume; // in dB. Negative values get quieter
float shakes;
int soundShaderFlags; // SSF_* bit flags
int soundClass; // for global fading of sounds
} soundShaderParms_t;
// sound classes are used to fade most sounds down inside cinematics, leaving dialog
// flagged with a non-zero class full volume
const int SOUND_MAX_CLASSES = 4;
// it is somewhat tempting to make this a virtual class to hide the private
// details here, but that doesn't fit easily with the decl manager at the moment.
class idSoundShader : public idDecl {
public:
idSoundShader();
virtual ~idSoundShader();
virtual size_t Size() const;
virtual bool SetDefaultText();
virtual const char * DefaultDefinition() const;
virtual bool Parse( const char *text, const int textLength, bool allowBinaryVersion );
virtual void FreeData();
virtual void List() const;
// so the editor can draw correct default sound spheres
// this is currently defined as meters, which sucks, IMHO.
virtual float GetMinDistance() const; // FIXME: replace this with a GetSoundShaderParms()
virtual float GetMaxDistance() const;
// returns NULL if an AltSound isn't defined in the shader.
// we use this for pairing a specific broken light sound with a normal light sound
virtual const idSoundShader *GetAltSound() const;
virtual bool HasDefaultSound() const;
virtual const soundShaderParms_t *GetParms() const;
virtual int GetNumSounds() const;
virtual const char * GetSound( int index ) const;
private:
friend class idSoundWorldLocal;
friend class idSoundEmitterLocal;
friend class idSoundChannel;
// options from sound shader text
soundShaderParms_t parms; // can be overriden on a per-channel basis
int speakerMask;
const idSoundShader * altSound;
bool leadin; // true if this sound has a leadin
float leadinVolume; // allows light breaking leadin sounds to be much louder than the broken loop
idList<idSoundSample *, TAG_AUDIO> entries;
private:
void Init();
bool ParseShader( idLexer &src );
};
/*
===============================================================================
SOUND EMITTER
===============================================================================
*/
// sound channels
static const int SCHANNEL_ANY = 0; // used in queries and commands to effect every channel at once, in
// startSound to have it not override any other channel
static const int SCHANNEL_ONE = 1; // any following integer can be used as a channel number
typedef int s_channelType; // the game uses its own series of enums, and we don't want to require casts
class idSoundEmitter {
public:
virtual ~idSoundEmitter() {}
// a non-immediate free will let all currently playing sounds complete
// soundEmitters are not actually deleted, they are just marked as
// reusable by the soundWorld
virtual void Free( bool immediate ) = 0;
// the parms specified will be the default overrides for all sounds started on this emitter.
// NULL is acceptable for parms
virtual void UpdateEmitter( const idVec3 &origin, int listenerId, const soundShaderParms_t *parms ) = 0;
// returns the length of the started sound in msec
virtual int StartSound( const idSoundShader *shader, const s_channelType channel, float diversity = 0, int shaderFlags = 0, bool allowSlow = true ) = 0;
// pass SCHANNEL_ANY to effect all channels
virtual void ModifySound( const s_channelType channel, const soundShaderParms_t *parms ) = 0;
virtual void StopSound( const s_channelType channel ) = 0;
// to is in Db, over is in seconds
virtual void FadeSound( const s_channelType channel, float to, float over ) = 0;
// returns true if there are any sounds playing from this emitter. There is some conservative
// slop at the end to remove inconsistent race conditions with the sound thread updates.
// FIXME: network game: on a dedicated server, this will always be false
virtual bool CurrentlyPlaying( const s_channelType channel = SCHANNEL_ANY ) const = 0;
// returns a 0.0 to 1.0 value based on the current sound amplitude, allowing
// graphic effects to be modified in time with the audio.
// just samples the raw wav file, it doesn't account for volume overrides in the
virtual float CurrentAmplitude() = 0;
// for save games. Index will always be > 0
virtual int Index() const = 0;
};
/*
===============================================================================
SOUND WORLD
There can be multiple independent sound worlds, just as there can be multiple
independent render worlds. The prime example is the editor sound preview
option existing simultaniously with a live game.
===============================================================================
*/
class idSoundWorld {
public:
virtual ~idSoundWorld() {}
// call at each map start
virtual void ClearAllSoundEmitters() = 0;
virtual void StopAllSounds() = 0;
// get a new emitter that can play sounds in this world
virtual idSoundEmitter *AllocSoundEmitter() = 0;
// for load games, index 0 will return NULL
virtual idSoundEmitter *EmitterForIndex( int index ) = 0;
// query sound samples from all emitters reaching a given listener
virtual float CurrentShakeAmplitude() = 0;
// where is the camera/microphone
// listenerId allows listener-private and antiPrivate sounds to be filtered
virtual void PlaceListener( const idVec3 &origin, const idMat3 &axis, const int listenerId ) = 0;
// fade all sounds in the world with a given shader soundClass
// to is in Db, over is in seconds
virtual void FadeSoundClasses( const int soundClass, const float to, const float over ) = 0;
// menu sounds
virtual int PlayShaderDirectly( const char * name, int channel = -1 ) = 0;
// dumps the current state and begins archiving commands
virtual void StartWritingDemo( idDemoFile *demo ) = 0;
virtual void StopWritingDemo() = 0;
// read a sound command from a demo file
virtual void ProcessDemoCommand( idDemoFile *demo ) = 0;
// when cinematics are skipped, we need to advance sound time this much
virtual void Skip( int time ) = 0;
// pause and unpause the sound world
virtual void Pause() = 0;
virtual void UnPause() = 0;
virtual bool IsPaused() = 0;
// Write the sound output to multiple wav files. Note that this does not use the
// work done by AsyncUpdate, it mixes explicitly in the foreground every PlaceOrigin(),
// under the assumption that we are rendering out screenshots and the gameTime is going
// much slower than real time.
// path should not include an extension, and the generated filenames will be:
// <path>_left.raw, <path>_right.raw, or <path>_51left.raw, <path>_51right.raw,
// <path>_51center.raw, <path>_51lfe.raw, <path>_51backleft.raw, <path>_51backright.raw,
// If only two channel mixing is enabled, the left and right .raw files will also be
// combined into a stereo .wav file.
virtual void AVIOpen( const char *path, const char *name ) = 0;
virtual void AVIClose() = 0;
// SaveGame / demo Support
virtual void WriteToSaveGame( idFile *savefile ) = 0;
virtual void ReadFromSaveGame( idFile *savefile ) = 0;
virtual void SetSlowmoSpeed( float speed ) = 0;
virtual void SetEnviroSuit( bool active ) = 0;
};
/*
===============================================================================
SOUND SYSTEM
===============================================================================
*/
typedef struct {
idStr name;
idStr format;
int numChannels;
int numSamplesPerSecond;
int num44kHzSamples;
int numBytes;
bool looping;
float lastVolume;
int start44kHzTime;
int current44kHzTime;
} soundDecoderInfo_t;
class idSoundSystem {
public:
virtual ~idSoundSystem() {}
// All non-hardware initialization.
virtual void Init() = 0;
// Shutdown routine.
virtual void Shutdown() = 0;
// The renderWorld is used for visualization and light amplitude sampling.
virtual idSoundWorld * AllocSoundWorld( idRenderWorld *rw ) = 0;
virtual void FreeSoundWorld( idSoundWorld *sw ) = 0;
// Specifying NULL will cause silence to be played.
virtual void SetPlayingSoundWorld( idSoundWorld *soundWorld ) = 0;
// Some tools, like the sound dialog, may be used in both the game and the editor
// This can return NULL, so check!
virtual idSoundWorld * GetPlayingSoundWorld() = 0;
// Sends the current playing sound world information to the sound hardware.
virtual void Render() = 0;
virtual void MuteBackgroundMusic( bool mute ) = 0;
// Sets the final output volume to 0.
virtual void SetMute( bool mute ) = 0;
virtual bool IsMuted() = 0;
// Called by the decl system when a sound decl is reloaded
virtual void OnReloadSound( const idDecl* sound ) = 0;
// Called before freeing any sound sample resources
virtual void StopAllSounds() = 0;
// May be called to free memory for level loads
virtual void InitStreamBuffers() = 0;
virtual void FreeStreamBuffers() = 0;
// video playback needs to get this
virtual void * GetIXAudio2() const = 0;
// for the sound level meter window
virtual cinData_t ImageForTime( const int milliseconds, const bool waveform ) = 0;
// Free all sounds loaded during the last map load
virtual void BeginLevelLoad() = 0;
// Load all sounds marked as used this level
virtual void EndLevelLoad() = 0;
virtual void Preload( idPreloadManifest & preload ) = 0;
// prints memory info
virtual void PrintMemInfo( MemInfo_t *mi ) = 0;
};
extern idSoundSystem *soundSystem;
#endif /* !__SOUND__ */