/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Doom 3 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ #pragma hdrstop #include "../idlib/precompiled.h" #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; } }