mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2026-03-20 17:11:16 +01:00
Initial commit
This commit is contained in:
257
neo/sound/SoundVoice.cpp
Normal file
257
neo/sound/SoundVoice.cpp
Normal 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
101
neo/sound/SoundVoice.h
Normal 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
516
neo/sound/WaveFile.cpp
Normal 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
236
neo/sound/WaveFile.h
Normal 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__
|
||||
542
neo/sound/XAudio2/XA2_SoundHardware.cpp
Normal file
542
neo/sound/XAudio2/XA2_SoundHardware.cpp
Normal 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();
|
||||
}
|
||||
112
neo/sound/XAudio2/XA2_SoundHardware.h
Normal file
112
neo/sound/XAudio2/XA2_SoundHardware.h
Normal 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
|
||||
487
neo/sound/XAudio2/XA2_SoundSample.cpp
Normal file
487
neo/sound/XAudio2/XA2_SoundSample.cpp
Normal 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;
|
||||
}
|
||||
129
neo/sound/XAudio2/XA2_SoundSample.h
Normal file
129
neo/sound/XAudio2/XA2_SoundSample.h
Normal 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
|
||||
492
neo/sound/XAudio2/XA2_SoundVoice.cpp
Normal file
492
neo/sound/XAudio2/XA2_SoundVoice.cpp
Normal 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 );
|
||||
}
|
||||
114
neo/sound/XAudio2/XA2_SoundVoice.h
Normal file
114
neo/sound/XAudio2/XA2_SoundVoice.h
Normal 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
977
neo/sound/snd_emitter.cpp
Normal 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
483
neo/sound/snd_local.h
Normal 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
421
neo/sound/snd_shader.cpp
Normal 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
596
neo/sound/snd_system.cpp
Normal 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
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
335
neo/sound/sound.h
Normal 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__ */
|
||||
Reference in New Issue
Block a user