/*****************************************************************************
The Dark Mod GPL Source Code

This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.

The Dark Mod 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. For details, see LICENSE.TXT.

Project: The Dark Mod (http://www.thedarkmod.com/)

******************************************************************************/

#include "precompiled.h"
#pragma hdrstop



#include "snd_local.h"
#include "limits.h"

/*
==================
idSoundWorldLocal::Init
==================
*/
void idSoundWorldLocal::Init( idRenderWorld *renderWorld ) {
	rw = renderWorld;
	writeDemo = NULL;

	listenerAxis.Identity();
	listenerPos.Zero();
	listenerPrivateId = 0;
	listenerQU.Zero();
	listenerArea = 0;
	listenerAreaName = "Undefined";
	listenerEffect = AL_EFFECTSLOT_NULL;
	// nbohr1more: #5587 Reverb volume control
	listenerSlotReverbGain = 1.0f;

	if (idSoundSystemLocal::useEFXReverb) {
		if (!soundSystemLocal.alIsAuxiliaryEffectSlot(listenerSlot)) {
			alGetError();

			soundSystemLocal.alGenAuxiliaryEffectSlots(1, &listenerSlot);
			ALuint e = alGetError();
			if (e != AL_NO_ERROR) {
				common->Warning("idSoundWorldLocal::Init: alGenAuxiliaryEffectSlots failed: 0x%x", e);
				listenerSlot = AL_EFFECTSLOT_NULL;
			}
		}

		if (!soundSystemLocal.alIsFilter(listenerFilter)) {
			alGetError();

			soundSystemLocal.alGenFilters(1, &listenerFilter);
			ALuint e = alGetError();
			if (e != AL_NO_ERROR) {
				common->Warning("idSoundWorldLocal::Init: alGenFilters failed: 0x%x", e);
				listenerFilter = AL_FILTER_NULL;
			} else {
				soundSystemLocal.alFilteri(listenerFilter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
				// original EAX occusion value was -1150
				// default OCCLUSIONLFRATIO is 0.25

				// pow(10.0, (-1150*0.25)/2000.0)
				soundSystemLocal.alFilterf(listenerFilter, AL_LOWPASS_GAIN, 0.718208f);
				// pow(10.0, -1150/2000.0)
			}
			// nbohr1more: #5587 Reverb volume control
			listenerSlotReverbGain = soundSystemLocal.s_alReverbGain.GetFloat();
			soundSystemLocal.alAuxiliaryEffectSlotf(listenerSlot, AL_EFFECTSLOT_GAIN, listenerSlotReverbGain);
		}
	}

	gameMsec = 0;
	game44kHz = 0;
	pause44kHz = -1;
	lastAVI44kHz = 0;

	for ( int i = 0 ; i < SOUND_MAX_CLASSES ; i++ ) {
		soundClassFade[i].Clear();
	}

	// fill in the 0 index spot
	idSoundEmitterLocal	*placeHolder = new idSoundEmitterLocal;
	emitters.Append( placeHolder );

	fpa[0] = fpa[1] = fpa[2] = fpa[3] = fpa[4] = fpa[5] = NULL;

	aviDemoPath = "";
	aviDemoName = "";

	localSound = NULL;
	secondarySound = NULL; // grayman #4882

	slowmoActive		= false;
	slowmoSpeed			= 0;
	enviroSuitActive	= false;

	activeSubtitlesFrame = 0;
}

/*
===============
idSoundWorldLocal::idSoundWorldLocal
===============
*/
idSoundWorldLocal::idSoundWorldLocal() {
}

/*
===============
idSoundWorldLocal::~idSoundWorldLocal
===============
*/
idSoundWorldLocal::~idSoundWorldLocal() {
	Shutdown();
}

/*
===============
idSoundWorldLocal::Shutdown

  this is called from the main thread
===============
*/
void idSoundWorldLocal::Shutdown() {
	int i;

	if ( soundSystemLocal.currentSoundWorld == this ) {
		soundSystemLocal.currentSoundWorld = NULL;
	}

	AVIClose();

	if (idSoundSystemLocal::useEFXReverb) {
		if (soundSystemLocal.alIsAuxiliaryEffectSlot(listenerSlot)) {
			soundSystemLocal.alAuxiliaryEffectSloti(listenerSlot, AL_EFFECTSLOT_EFFECT, AL_EFFECTSLOT_NULL);
			soundSystemLocal.alDeleteAuxiliaryEffectSlots(1, &listenerSlot);
			listenerSlot = AL_EFFECTSLOT_NULL;
		}

		if (soundSystemLocal.alIsFilter(listenerFilter)) {
			soundSystemLocal.alDeleteFilters(1, &listenerFilter);
			listenerFilter = AL_FILTER_NULL;
		}
	}

	for ( i = 0; i < emitters.Num(); i++ ) {
		if ( emitters[i] ) {
			delete emitters[i];
			emitters[i] = NULL;
		}
		//nbohr1more: #5587 Reverb volume control
		listenerSlotReverbGain = 1.0f;
	}
	localSound = NULL;
	secondarySound = NULL; // grayman #4882

	activeSubtitles[0].ClearFree();
	activeSubtitles[1].ClearFree();
	activeSubtitlesFrame = -1;
}

/*
===================
idSoundWorldLocal::ClearAllSoundEmitters
===================
*/
void idSoundWorldLocal::ClearAllSoundEmitters() {
	int i;

	Sys_EnterCriticalSection();

	AVIClose();

	for ( i = 0; i < emitters.Num(); i++ ) {
		idSoundEmitterLocal *sound = emitters[i];
		sound->Clear();
	}
	localSound = NULL;
	secondarySound = NULL; // grayman #4882

	Sys_LeaveCriticalSection();
}

/*
===================
idSoundWorldLocal::AllocLocalSoundEmitter
===================
*/
idSoundEmitterLocal *idSoundWorldLocal::AllocLocalSoundEmitter(idVec3 loc) // grayman #4882
{
	int i, index;
	idSoundEmitterLocal *def = NULL;

	index = -1;

	// never use the 0 index spot

	for ( i = 1 ; i < emitters.Num() ; i++ ) {
		def = emitters[i];

		// check for a completed and freed spot
		if ( def->removeStatus >= REMOVE_STATUS_SAMPLEFINISHED ) {
			index = i;
			if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) {
				common->Printf( "sound: recycling sound def %d\n", i );
			}
			break;
		}
	}

	if ( index == -1 ) {
		// append a brand new one
		def = new idSoundEmitterLocal;

		// we need to protect this from the async thread
		Sys_EnterCriticalSection();
		index = emitters.Append( def );
		Sys_LeaveCriticalSection();

		if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) {
			common->Printf( "sound: appended new sound def %d\n", index );
		}
	}

	def->Clear();
	def->index = index;
	def->removeStatus = REMOVE_STATUS_ALIVE;
	def->soundWorld = this;
	def->origin = loc; // grayman #4882
	def->listenerId = -1;

	return def;
}

/*
===================
idSoundWorldLocal::AllocSoundEmitter

  this is called from the main thread
===================
*/
idSoundEmitter *idSoundWorldLocal::AllocSoundEmitter(idVec3 loc) // grayman #4882
{
	idSoundEmitterLocal *emitter = AllocLocalSoundEmitter(loc); // grayman #4882

	if ( idSoundSystemLocal::s_showStartSound.GetInteger() ) {
		common->Printf( "AllocSoundEmitter = %i\n",  emitter->index );
	}
	if ( writeDemo ) {
		writeDemo->WriteInt( DS_SOUND );
		writeDemo->WriteInt( SCMD_ALLOC_EMITTER );
		writeDemo->WriteInt( emitter->index );
	}

	return emitter;
}

/*
===================
idSoundWorldLocal::StartWritingDemo

  this is called from the main thread
===================
*/
void idSoundWorldLocal::StartWritingDemo( idDemoFile *demo ) {
	writeDemo = demo;

	writeDemo->WriteInt( DS_SOUND );
	writeDemo->WriteInt( SCMD_STATE );

	// use the normal save game code to archive all the emitters
	WriteToSaveGame( writeDemo );
}

/*
===================
idSoundWorldLocal::StopWritingDemo

  this is called from the main thread
===================
*/
void idSoundWorldLocal::StopWritingDemo() {
	writeDemo = NULL;
}

/*
===================
idSoundWorldLocal::ProcessDemoCommand

  this is called from the main thread
===================
*/
void idSoundWorldLocal::ProcessDemoCommand( idDemoFile *readDemo ) {
	int	index;
	idSoundEmitterLocal	*def;

	if ( !readDemo ) {
		return;
	}

	int dc;

	if ( !readDemo->ReadInt( dc ) ) {
		return;
	}

	switch( (soundDemoCommand_t)dc ) {
	case SCMD_STATE:
		// we need to protect this from the async thread
		// other instances of calling idSoundWorldLocal::ReadFromSaveGame do this while the sound code is muted
		// setting muted and going right in may not be good enough here, as we async thread may already be in an async tick (in which case we could still race to it)
		Sys_EnterCriticalSection();
		ReadFromSaveGame( readDemo );
		Sys_LeaveCriticalSection();
		UnPause();
		break;
	case SCMD_PLACE_LISTENER:
		{
			idVec3	origin;
			idMat3	axis;
			int		listenerId;
			int		gameTime;

			readDemo->ReadVec3( origin );
			readDemo->ReadMat3( axis );
			readDemo->ReadInt( listenerId );
			readDemo->ReadInt( gameTime );
			
			PlaceListener( origin, axis, listenerId, gameTime, "", "");
		};
		break;
	case SCMD_ALLOC_EMITTER:
		readDemo->ReadInt( index );
		if ( index < 1 || index > emitters.Num() ) {
			common->Error( "idSoundWorldLocal::ProcessDemoCommand: bad emitter number" );
		}
		if ( index == emitters.Num() ) {
			// append a brand new one
			def = new idSoundEmitterLocal;
			emitters.Append( def );
		}
		def = emitters[ index ];
		def->Clear();
		def->index = index;
		def->removeStatus = REMOVE_STATUS_ALIVE;
		def->soundWorld = this;
		break;
	case SCMD_FREE:
		{
			int	immediate;

			readDemo->ReadInt( index );
			readDemo->ReadInt( immediate );
			EmitterForIndex( index )->Free( immediate != 0 );
		}
		break;
	case SCMD_UPDATE:
		{
			idVec3 origin;
			int listenerId;
			soundShaderParms_t parms;

			readDemo->ReadInt( index );
			readDemo->ReadVec3( origin );
			readDemo->ReadInt( listenerId );
			readDemo->ReadFloat( parms.minDistance );
			readDemo->ReadFloat( parms.maxDistance );
			readDemo->ReadFloat( parms.volume );
			readDemo->ReadFloat( parms.shakes );
			readDemo->ReadInt( parms.soundShaderFlags );
			readDemo->ReadInt( parms.soundClass );
			readDemo->ReadInt( parms.overrideMode );
			EmitterForIndex( index )->UpdateEmitter( origin, listenerId, &parms );
		}
		break;
	case SCMD_START:
		{
			const idSoundShader *shader;
			int			channel;
			float		diversity;
			int			shaderFlags;

			readDemo->ReadInt( index );
			shader = declManager->FindSound( readDemo->ReadHashString() );
			readDemo->ReadInt( channel );
			readDemo->ReadFloat( diversity );
			readDemo->ReadInt( shaderFlags );
			EmitterForIndex( index )->StartSound( shader, (s_channelType)channel, diversity, shaderFlags );
		}
		break;
	case SCMD_MODIFY:
		{
			int		channel;
			soundShaderParms_t parms;

			readDemo->ReadInt( index );
			readDemo->ReadInt( channel );
			readDemo->ReadFloat( parms.minDistance );
			readDemo->ReadFloat( parms.maxDistance );
			readDemo->ReadFloat( parms.volume );
			readDemo->ReadFloat( parms.shakes );
			readDemo->ReadInt( parms.soundShaderFlags );
			readDemo->ReadInt( parms.soundClass );
			readDemo->ReadInt( parms.overrideMode );
			EmitterForIndex( index )->ModifySound( (s_channelType)channel, &parms );
		}
		break;
	case SCMD_STOP:
		{
			int		channel;

			readDemo->ReadInt( index );
			readDemo->ReadInt( channel );
			EmitterForIndex( index )->StopSound( (s_channelType)channel );
		}
		break;
	case SCMD_FADE:
		{
			int		channel;
			float	to, over;

			readDemo->ReadInt( index );
			readDemo->ReadInt( channel );
			readDemo->ReadFloat( to );
			readDemo->ReadFloat( over );
			EmitterForIndex( index )->FadeSound((s_channelType)channel, to, over );
		}
		break;
	}
}

/*
===================
idSoundWorldLocal::CurrentShakeAmplitudeForPosition

  this is called from the main thread
===================
*/
float idSoundWorldLocal::CurrentShakeAmplitudeForPosition( const int time, const idVec3 &listererPosition ) {
	float amp = 0.0f;
	int localTime;

	if ( idSoundSystemLocal::s_constantAmplitude.GetFloat() >= 0.0f ) {
		return 0.0f;
	}

	localTime = soundSystemLocal.GetCurrent44kHzTime();

	for ( int i = 1; i < emitters.Num(); i++ ) {
		idSoundEmitterLocal *sound = emitters[i];
		if ( !sound->hasShakes ) {
			continue;
		}
		amp += FindAmplitude( sound, localTime, &listererPosition, SCHANNEL_ANY, true );
	}
	return amp;
}

/*
===================
idSoundWorldLocal::MixLoopInternal

Sum all sound contributions into finalMixBuffer, an unclamped float buffer holding
all output channels.  MIXBUFFER_SAMPLES samples will be created, with each sample consisting
of 2 or 6 floats depending on numSpeakers.

this is normally called from the sound thread, but also from the main thread
for AVIdemo writing
===================
*/
void idSoundWorldLocal::MixLoopInternal( int current44kHz, int numSpeakers, float *finalMixBuffer ) {
	int i, j;
	idSoundEmitterLocal *sound;

	// if noclip flying outside the world, leave silence
	if ( listenerArea == -1 ) {
		alListenerf( AL_GAIN, 0.0f );
		return;
	} 

	// update the listener position and orientation
	ALfloat listenerPosition[3];

	listenerPosition[0] = -listenerPos.y;
	listenerPosition[1] = listenerPos.z;
	listenerPosition[2] = -listenerPos.x;

	ALfloat listenerOrientation[6];

	listenerOrientation[0] = -listenerAxis[0].y;
	listenerOrientation[1] = listenerAxis[0].z;
	listenerOrientation[2] = -listenerAxis[0].x;

	listenerOrientation[3] = -listenerAxis[2].y;
	listenerOrientation[4] = listenerAxis[2].z;
	listenerOrientation[5] = -listenerAxis[2].x;

	alListenerf(AL_GAIN, 1.0f);
	alListenerfv(AL_POSITION, listenerPosition);
	alListenerfv(AL_ORIENTATION, listenerOrientation);

	if (idSoundSystemLocal::useEFXReverb && soundSystemLocal.efxloaded) {
		ALuint effect = AL_EFFECTSLOT_NULL;
		idStr s(listenerArea);

		//nbohr1more: #5587 Reverb volume control
		float gain = soundSystemLocal.s_alReverbGain.GetFloat();
		if (listenerSlotReverbGain != gain) {
			listenerSlotReverbGain = gain;
			soundSystemLocal.alAuxiliaryEffectSlotf(listenerSlot, AL_EFFECTSLOT_GAIN, gain);
		}

		// Look for effect based on area index (1, 2, etc.). Does anyone actually do this?
		bool found = soundSystemLocal.EFXDatabase.FindEffect(s, &effect);

		// Look for effect based on area (location) name
		if (!found) {
			s = listenerAreaName;

			if (!listenerAreaEfxPreset.IsEmpty()) {
				found = soundSystemLocal.EFXDatabase.AddOrUpdatePreset(s, listenerAreaEfxPreset, &effect);
			}
			else {
				found = soundSystemLocal.EFXDatabase.FindEffect(s, &effect);
			}

		}
		if (!found) {
			s = "default";
			found = soundSystemLocal.EFXDatabase.FindEffect(s, &effect);
		}

		bool justReloaded = soundSystemLocal.EFXDatabase.IsAfterReload();

		// only update if change in settings
		if (listenerEffect != effect || justReloaded) {
			common->Printf("Switching to EFX '%s' (#%u)\n", s.c_str(), effect);
			listenerEffect = effect;
			soundSystemLocal.alAuxiliaryEffectSloti(listenerSlot, AL_EFFECTSLOT_EFFECT, effect);
		}
	}

	// debugging option to mute all but a single soundEmitter
	if ( idSoundSystemLocal::s_singleEmitter.GetInteger() > 0 && idSoundSystemLocal::s_singleEmitter.GetInteger() < emitters.Num() ) {
		sound = emitters[idSoundSystemLocal::s_singleEmitter.GetInteger()];

		if ( sound && sound->playing ) {
			// run through all the channels
			for ( j = 0; j < SOUND_MAX_CHANNELS ; j++ ) {
				idSoundChannel	*chan = &sound->channels[j];

				// see if we have a sound triggered on this channel
				if ( !chan->triggerState ) {
					chan->ALStop();
					continue;
				}

				AddChannelContribution( sound, chan, current44kHz, numSpeakers, finalMixBuffer );
			}
		}
		return;
	}

	for ( i = 1; i < emitters.Num(); i++ ) {
		sound = emitters[i];

		if ( !sound ) {
			continue;
		}
		const char *shaderName = "";
		if (auto shader = sound->channels[0].soundShader)
			shaderName = shader->GetName();
		// if no channels are active, do nothing
		if ( !sound->playing ) {
			continue;
		}
		// run through all the channels
		for ( j = 0; j < SOUND_MAX_CHANNELS ; j++ ) {
			idSoundChannel	*chan = &sound->channels[j];

			// see if we have a sound triggered on this channel
			if ( !chan->triggerState ) {
				chan->ALStop();
				continue;
			}

			AddChannelContribution( sound, chan, current44kHz, numSpeakers, finalMixBuffer );
		}
	}

	// TODO port to OpenAL
	if (false && enviroSuitActive) {
		soundSystemLocal.DoEnviroSuit( finalMixBuffer, MIXBUFFER_SAMPLES, numSpeakers );
	}
}

/*
===================
idSoundWorldLocal::MixLoop

stgatilov #2454: Simple wrapper which makes sure activeSubtitles are filled on every call
===================
*/
void idSoundWorldLocal::MixLoop( int current44kHz, int numSpeakers, float *finalMixBuffer ) {
	activeSubtitles[!activeSubtitlesFrame].Clear();

	MixLoopInternal( current44kHz, numSpeakers, finalMixBuffer );

	idScopedCriticalSection section(activeSubtitlesMutex);
	activeSubtitlesFrame ^= 1;
}

/*
===================
idSoundWorldLocal::GetSubtitles
===================
*/
void idSoundWorldLocal::GetSubtitles( idList<SubtitleMatch> &dest ) {
	idScopedCriticalSection section(activeSubtitlesMutex);
	dest = activeSubtitles[activeSubtitlesFrame];
}

//==============================================================================

/*
===================
idSoundWorldLocal::AVIOpen

	this is called by the main thread
===================
*/
void idSoundWorldLocal::AVIOpen( const char *path, const char *name ) {
	aviDemoPath = path;
	aviDemoName = name;

	lastAVI44kHz = game44kHz - game44kHz % MIXBUFFER_SAMPLES;

	if (idSoundSystemLocal::s_numberOfSpeakers.GetInteger() == 6) {
		fpa[0] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_left.raw" );
		fpa[1] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_right.raw" );
		fpa[2] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_center.raw" );
		fpa[3] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_lfe.raw" );
		fpa[4] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_backleft.raw" );
		fpa[5] = fileSystem->OpenFileWrite( aviDemoPath + "channel_51_backright.raw" );
	} else {
		fpa[0] = fileSystem->OpenFileWrite( aviDemoPath + "channel_left.raw" );
		fpa[1] = fileSystem->OpenFileWrite( aviDemoPath + "channel_right.raw" );
	}

	soundSystemLocal.SetMute( true );
}

/*
===================
idSoundWorldLocal::AVIUpdate

this is called by the main thread
writes one block of sound samples if enough time has passed
This can be used to write wave files even if no sound hardware exists
===================
*/
void idSoundWorldLocal::AVIUpdate() {
	int		numSpeakers;
	
	if ( game44kHz - lastAVI44kHz < MIXBUFFER_SAMPLES ) {
		return;
	}

	numSpeakers = idSoundSystemLocal::s_numberOfSpeakers.GetInteger();

	float	mix[MIXBUFFER_SAMPLES*6+16];
	float	*mix_p = (float *)(((intptr_t)mix + 15) & ~15);	// SIMD align

	SIMDProcessor->Memset( mix_p, 0, MIXBUFFER_SAMPLES*sizeof(float)*numSpeakers );

	MixLoop( lastAVI44kHz, numSpeakers, mix_p );

	for ( int i = 0; i < numSpeakers; i++ ) {
		short outD[MIXBUFFER_SAMPLES];

		for( int j = 0; j < MIXBUFFER_SAMPLES; j++ ) {
			float s = mix_p[ j*numSpeakers + i];
			if ( s < -32768.0f ) {
				outD[j] = -32768;
			} else if ( s > 32767.0f ) {
				outD[j] = 32767;
			} else {
				outD[j] = idMath::FtoiRound( s );
			}
		}
		// write to file
		fpa[i]->Write( outD, MIXBUFFER_SAMPLES*sizeof(short) );
	}

	lastAVI44kHz += MIXBUFFER_SAMPLES;

	return;
}

/*
===================
idSoundWorldLocal::AVIClose
===================
*/
void idSoundWorldLocal::AVIClose( void ) {
	int i;

	if ( !fpa[0] ) {
		return;
	}

	// make sure the final block is written
	game44kHz += MIXBUFFER_SAMPLES;
	AVIUpdate();
	game44kHz -= MIXBUFFER_SAMPLES;

	for ( i = 0; i < 6; i++ ) {
		if ( fpa[i] != NULL ) {
			fileSystem->CloseFile( fpa[i] );
			fpa[i] = NULL;
		}
	}
	if (idSoundSystemLocal::s_numberOfSpeakers.GetInteger() == 2) {
		// convert it to a wave file
		idFile *rL, *lL, *wO;
		idStr	name;

		name = aviDemoPath + aviDemoName + ".wav";
		wO = fileSystem->OpenFileWrite( name );
		if ( !wO ) {
			common->Error( "Couldn't write %s", name.c_str() );
		}

		name = aviDemoPath + "channel_right.raw";
		rL = fileSystem->OpenFileRead( name );
		if ( !rL ) {
			common->Error( "Couldn't open %s", name.c_str() );
		}

		name = aviDemoPath + "channel_left.raw";
		lL = fileSystem->OpenFileRead( name );
		if ( !lL ) {
			common->Error( "Couldn't open %s", name.c_str() );
		}

		int numSamples = rL->Length()/2;
		mminfo_t	info;
		pcmwaveformat_t format;
		
		info.ckid = fourcc_riff;
		info.fccType = mmioFOURCC( 'W', 'A', 'V', 'E' );
		info.cksize = (rL->Length()*2) - 8 + 4 + 16 + 8 + 8;
		info.dwDataOffset = 12;
		
		wO->Write( &info, 12 );

		info.ckid = mmioFOURCC( 'f', 'm', 't', ' ' );
		info.cksize = 16;

		wO->Write( &info, 8 );

		format.wBitsPerSample = 16;
		format.wf.nAvgBytesPerSec = 44100*4;		// sample rate * block align
		format.wf.nChannels = 2;
		format.wf.nSamplesPerSec = 44100;
		format.wf.wFormatTag = WAVE_FORMAT_TAG_PCM;
		format.wf.nBlockAlign = 4;					// channels * bits/sample / 8

		wO->Write( &format, 16 );

		info.ckid = mmioFOURCC( 'd', 'a', 't', 'a' );
		info.cksize = rL->Length() * 2;

		wO->Write( &info, 8 );

		short s0, s1;
		for( i = 0; i < numSamples; i++ ) {
			lL->Read( &s0, 2 );
			rL->Read( &s1, 2 );
			wO->Write( &s0, 2 );
			wO->Write( &s1, 2 );
		}

		fileSystem->CloseFile( wO );
		fileSystem->CloseFile( lL );
		fileSystem->CloseFile( rL );

		fileSystem->RemoveFile( aviDemoPath + "channel_right.raw" );
		fileSystem->RemoveFile( aviDemoPath + "channel_left.raw" );
	}

	soundSystemLocal.SetMute( false );
}

//==============================================================================

float idSoundWorldLocal::GetDiffractionLoss(const idVec3 p1, const idVec3 p2, const idVec3 p3)
{
	idVec3 nv = p3 - p2; // vector of forward leg
	idVec3 pv = p2 - p1; // vector of previous leg
	nv.NormalizeFast();
	pv.NormalizeFast();
	float angle = RAD2DEG(idMath::ACos(nv*pv));

	// Assume an s_diffractionMax loss at an angle of 180, and no loss at an angle of 0

	return (idSoundSystemLocal::s_diffractionMax.GetFloat()*(angle / 180.0f));
}

/*
===================
idSoundWorldLocal::ResolveOrigin

Find out if the sound is completely occluded by a closed door portal, or
the virtual sound origin position at the portal closest to the listener.
  this is called by the main thread or the front renderer thread

dist is the distance from the orignial sound origin to the current portal that enters soundArea
def->distance is the distance we are trying to reduce.

If there is no path through open portals from the sound to the listener, def->distance will remain
set at maxDistance
===================
*/
static const int MAX_PORTAL_TRACE_DEPTH = 10;

bool idSoundWorldLocal::ResolveOrigin( bool primary, const int stackDepth, const soundPortalTrace_t *prevStack, const int soundArea, const float dist, const float loss, const idVec3& soundOrigin, const idVec3& prevSoundOrigin, idSoundEmitterLocal *def , SoundChainResults *results) // grayman #3042 // grayman #4219
{
	if ( dist >= def->distance ) // compare meters to meters
	{
		// we can't possibly hear the sound through this chain of portals
		return false;
	}

	// grayman #3042 - If how far we've traveled plus the minimum distance needed to reach the listener
	// from here is greater than the sound's max distance, then there's no need to continue, because
	// the listener won't hear the sound using this chain of portals along this path.

	idVec3 listenerPosition = (primary ? listenerQU : gameLocal.GetLocalPlayer()->GetSecondaryListenerLoc()); // doom units
	float distToListener;
	distToListener = (soundOrigin - listenerPosition).LengthFast(); // min distance remaining to reach listener (doom units)
	if ( ( dist + (distToListener * DOOM_TO_METERS) ) >= def->distance )
	{
		return false;
	}

	// SteveL #4148: Also quit if the sound loss through this chain is too great.
	if ( soundSystemLocal.dB2Scale(def->parms.volume - loss) < SND_EPSILON )
	{
		return false;
	}

	// If this is a primary path, and if we've reached the sound area the listener is in, our journey is over. Place the
	// results in the "results" object and return to the level above us.

	// If this is a secondary path, and the Listener targets another entity, we need to continue,
	// treating the path from the Listener to its target as a tunnel, where no volume loss occurs
	// and no distance increase occurs.

	// If this is a secondary path, and the Listener doesn't target another entity, then our journey is over. Place the
	// results in the "results" object and return to the level above us.

	// grayman #4219 - calculate the sound loss due to the direction change at this portal

	float angularLoss = 0.0f;

	int listenArea = rw->GetAreaAtPoint(listenerPosition);

	if ( soundArea == listenArea ) // grayman #4882
	{
		angularLoss = GetDiffractionLoss(prevSoundOrigin, soundOrigin, listenerPosition); // grayman #4882

		// If this additional loss makes the sound too soft to hear at this portal,
		// don't return spatialized data.

		if ( soundSystemLocal.dB2Scale(def->parms.volume - loss - angularLoss) < SND_EPSILON )
		{
			return false;
		}
		
		results->distance = (dist * METERS_TO_DOOM) + distToListener; // found the listener, so no need to travel distances beyond this
		results->spatializedOrigin = soundOrigin;
		results->loss = loss + angularLoss; // grayman #3042 - total accumulated volume loss across portals
		results->spatialDistance = distToListener; // doom units
		return true;
	}

	// We haven't reached the listener yet, so we need to keep going.

	if ( stackDepth == MAX_PORTAL_TRACE_DEPTH )
	{
		// don't spend too much time doing these calculations in big maps
		return false;
	}

	soundPortalTrace_t newStack;
	newStack.portalArea = soundArea;
	newStack.prevStack = prevStack;

	int numPortals = rw->NumPortalsInArea( soundArea );

	idList<SoundChainResults *> chainResults;
	
	for ( int p = 0 ; p < numPortals ; p++ )
	{
		exitPortal_t re = rw->GetPortal( soundArea, p );

		// grayman #3042 - This section used to apply the default idSoundSystemLocal::s_doorDistanceAdd
		// value if the portal was closed (450). To let us use the dB sound loss values placed
		// on doors, let's calculate the door occlusion differently. Since a door will change the
		// amount of sound loss as it opens and closes, and values can also be set by other entities
		// (i.e. location separators), we'll use the current loss regardless of whether the portal
		// is open or closed, so we don't need to check for that.

		// what area are we about to go look at?
		int otherArea = re.areas[0];
		if ( re.areas[0] == soundArea )
		{
			otherArea = re.areas[1];
		}

		// if this area is already in our portal chain, don't bother looking into it
		const soundPortalTrace_t *prev;
		for ( prev = prevStack ; prev ; prev = prev->prevStack )
		{
			if ( prev->portalArea == otherArea )
			{
				break;
			}
		}
		if ( prev )
		{
			continue;
		}

		// pick a point on the portal to serve as our virtual sound origin
#if 1
		idVec3 source;

		idPlane	pl;
		re.w.GetPlane( pl );

		float  scale;
		idVec3 dir = listenerPosition - soundOrigin; // grayman #4882
		if ( !pl.RayIntersection( soundOrigin, dir, scale ) )
		{
			source = re.w.GetCenter();
		}
		else
		{
			source = soundOrigin + scale * dir;

			// if this point isn't inside the portal edges, slide it in
			for ( int i = 0 ; i < re.w.GetNumPoints() ; i++ )
			{
				int j = ( i + 1 ) % re.w.GetNumPoints();
				idVec3	edgeDir = re.w[j].ToVec3() - re.w[i].ToVec3();
				idVec3	edgeNormal;

				edgeNormal.Cross( pl.Normal(), edgeDir );

				idVec3 fromVert = source - re.w[j].ToVec3();

				float d = edgeNormal * fromVert;
				if ( d > 0 )
				{
					// move it in
					float div = edgeNormal.Normalize();
					d /= div;
					source -= d * edgeNormal;
				}
			}
		}
#else
		// clip the ray from the listener to the center of the portal by
		// all the portal edge planes, then project that point (or the original if not clipped)
		// onto the portal plane to get the spatialized origin

		idVec3	start = listenerQU;
		idVec3	mid = re.w->GetCenter();
		bool	wasClipped = false;

		for ( int i = 0 ; i < re.w->GetNumPoints() ; i++ ) {
			int j = ( i + 1 ) % re.w->GetNumPoints();
			idVec3	v1 = (*(re.w))[j].ToVec3() - soundOrigin;
			idVec3	v2 = (*(re.w))[i].ToVec3() - soundOrigin;

			v1.Normalize();
			v2.Normalize();

			idVec3	edgeNormal;

			edgeNormal.Cross( v1, v2 );

			idVec3	fromVert = start - soundOrigin;
			float	d1 = edgeNormal * fromVert;

			if ( d1 > 0.0f ) {
				fromVert = mid - (*(re.w))[j].ToVec3();
				float d2 = edgeNormal * fromVert;

				// move it in
				float	f = d1 / ( d1 - d2 );

				idVec3	clipped = start * ( 1.0f - f ) + mid * f;
				start = clipped;
				wasClipped = true;
			}
		}

		idVec3	source;
		if ( wasClipped ) {
			// now project it onto the portal plane
			idPlane	pl;
			re.w->GetPlane( pl );

			float	f1 = pl.Distance( start );
			float	f2 = pl.Distance( soundOrigin );

			float	f = f1 / ( f1 - f2 );
			source = start * ( 1.0f - f ) + soundOrigin * f;
		} else {
			source = soundOrigin;
		}
#endif

		idVec3 tlen = source - soundOrigin; // doom units
		float tlenLength = tlen.LengthFast(); // doom units
		SoundChainResults *res = new SoundChainResults();

		// grayman #4219 - determine sound loss due to direction change at the previous portal

		angularLoss = 0.0f;

		if ( !soundOrigin.Compare(prevSoundOrigin,VECTOR_EPSILON) )
		{
			angularLoss = GetDiffractionLoss(prevSoundOrigin, soundOrigin, source);
		}

		idVec3 trailingSoundOrigin = soundOrigin; // doom units

		if ( ResolveOrigin( primary, stackDepth+1, &newStack, otherArea, dist+(tlenLength*DOOM_TO_METERS), loss + re.lossPlayer + angularLoss/* + waterLoss*/, source, trailingSoundOrigin, def, res ) ) // grayman #3042
		{
			chainResults.Append(res);
		} 
		else 
		{
			delete res; // SoundChainResults for this iteration no longer required, so free it
		}
	}

	if ( chainResults.Num() > 0 ) // were there any usable results?
	{
		// get results from each chain and average

		int numChains = chainResults.Num();

		float aveLoss = 0;
		float aveDist = 0;
		idVec3 aveSpatialOrigin(0,0,0);

		// special case of only 1 chain

		if ( numChains == 1 )
		{
			aveSpatialOrigin = chainResults[0]->spatializedOrigin;
			aveDist = chainResults[0]->distance;
			aveLoss = chainResults[0]->loss;
		}

		else // 2 or more chains
		// grayman #3042 use the highest volume in this chain
		// determine effective volume at the spatialized origin
		{
			float maxVol = -100000.0f;
			int pickMe = 0;
			float mind = def->minDistance * METERS_TO_DOOM;
			float maxd = def->maxDistance * METERS_TO_DOOM;
			bool quadratic = idSoundSystemLocal::s_quadraticFalloff.GetBool();
			idList<float> volList; // list of effective chain volumes
	
			for ( int i = 0 ; i < numChains ; i++ )
			{
				SoundChainResults *scr = chainResults[i];
				float dlen = scr->distance;
				float vol = soundSystemLocal.dB2Scale(def->parms.volume - scr->loss);

				// reduce effective volume based on distance
				if ( dlen >= maxd )
				{
					vol = 0.0f;
				}
				else if ( dlen > mind )
				{
					float frac = idMath::ClampFloat( 0.0f, 1.0f, 1.0f - ((dlen - mind) / (maxd - mind)));
					if (quadratic)
					{
						frac *= frac;
					}
					vol *= frac;
				}

				volList.Append(vol); // add to list of effective chain volumes
			
				if ( vol > maxVol )
				{
					pickMe = i;
					maxVol = vol;
				}
			}

			SoundChainResults *scr = chainResults[pickMe];
			aveLoss = scr->loss;
			aveDist = scr->distance;

			// if stackDepth is 0, determine a spatial origin derived from all effective chain volumes.
			if ( stackDepth == 0 )
			{
				float totalEffectiveVolume = 0;
				for ( int i = 0 ; i < volList.Num() ; i++ )
				{
					totalEffectiveVolume += volList[i];
				}

				for ( int i = 0 ; i < volList.Num() ; i++ )
				{
					SoundChainResults *scres = chainResults[i];
					float factor = volList[i]/totalEffectiveVolume;
					aveSpatialOrigin += (scres->spatializedOrigin)*factor;
				}
			}
			else
			{
				aveSpatialOrigin = scr->spatializedOrigin; // use the spatial origin for this chain path
			}
		}

		// we're done with the SoundChainResults, so remove them from the heap
		chainResults.DeleteContents(true);

		results->spatializedOrigin = aveSpatialOrigin;
		results->distance = aveDist;
		results->loss = aveLoss;
		results->spatialDistance = (aveSpatialOrigin - listenerPosition).LengthFast(); // grayman #4882

		return true;
	}

	return false;
}


/*
===================
idSoundWorldLocal::PlaceListener

  this is called by the main thread
===================
*/
void idSoundWorldLocal::PlaceListener( const idVec3& origin, const idMat3& axis, 
									const int listenerId, const int gameTime, const idStr& areaName, const idStr& efxPreset) {

	int current44kHzTime;

	if ( !soundSystemLocal.isInitialized ) {
		return;
	}

	if ( pause44kHz >= 0 ){
		return;
	}

	if ( writeDemo ) {
		writeDemo->WriteInt( DS_SOUND );
		writeDemo->WriteInt( SCMD_PLACE_LISTENER );
		writeDemo->WriteVec3( origin );
		writeDemo->WriteMat3( axis );
		writeDemo->WriteInt( listenerId );
		writeDemo->WriteInt( gameTime );
	}

	current44kHzTime = soundSystemLocal.GetCurrent44kHzTime();

	// we usually expect gameTime to be increasing by 16 or 32 msec, but when
	// a cinematic is fast-forward skipped through, it can jump by a significant
	// amount, while the hardware 44kHz position will not have changed accordingly,
	// which would make sounds (like long character speeches) continue from the
	// old time.  Fix this by killing all non-looping sounds
	if ( gameTime > gameMsec + 500 ) {
		OffsetSoundTime( - ( gameTime - gameMsec ) * 0.001f * 44100.0f );
	}

	gameMsec = gameTime;
	if ( fpa[0] ) {
		// exactly 30 fps so the wave file can be used for exact video frames
		game44kHz = idMath::FtoiRound( gameMsec * ( ( 1000.0f / 60.0f ) / 16.0f ) * 0.001f * 44100.0f );
	} else {
		// the normal 16 msec / frame
		game44kHz = idMath::FtoiRound( gameMsec * 0.001f * 44100.0f );
	}

	listenerPrivateId = listenerId;

	listenerQU = origin;							// Doom units
	listenerPos = origin * DOOM_TO_METERS;			// meters
	listenerAxis = axis;
	listenerAreaName = areaName;
	listenerAreaEfxPreset = efxPreset;

	if ( rw ) {
		listenerArea = rw->GetAreaAtPoint( listenerQU );	// where are we?
	} else {
		listenerArea = 0;
	}

	if ( listenerArea < 0 ) {
		return;
	}

	ForegroundUpdate( current44kHzTime );
}


/*
==================
idSoundWorldLocal::ForegroundUpdate
==================
*/
void idSoundWorldLocal::ForegroundUpdate( int current44kHzTime ) {
	int j, k;
	idSoundEmitterLocal	*def;

	if ( !soundSystemLocal.isInitialized ) {
		return;
	}

	Sys_EnterCriticalSection();

	// if we are recording an AVI demo, don't use hardware time
	if ( fpa[0] ) {
		current44kHzTime = lastAVI44kHz;
	}

	//
	// check to see if each sound is visible or not
	// speed up by checking maxdistance to origin
	// although the sound may still need to play if it has
	// just become occluded so it can ramp down to 0
	//
	for ( j = 1; j < emitters.Num(); j++ ) {
		def = emitters[j];

		if ( def->removeStatus >= REMOVE_STATUS_SAMPLEFINISHED ) {
			continue;
		}

		// see if our last channel just finished
		def->CheckForCompletion( current44kHzTime );

		if ( !def->playing ) {
			continue;
		}

		/* grayman #4882
		Can we use a list of Listeners to propagate a single waveform and recognize listeners
		as we propagate? Then we'd only have to Spatialize once.
		Look at CsndProp::Propagate() Line 515.
		*/

		//Spatialize(soundWorld->listenerPos, soundWorld->listenerArea, soundWorld->rw);

		idPlayer* player = gameLocal.GetLocalPlayer();
		if ( player )
		{
			idVec3 p; // meters
			int area;
			idVec3 pSpatializedOrigin;
			float pDistance;
			float pVolumeLoss;

			bool spatializePrimary = true; // hear what's around the player (default)
			idListener* listener = player->m_Listener.GetEntity();
			if ( listener )
			{
				if ( listener->mode == 2 )
				{
					spatializePrimary = false; // don't hear what's around the player
				}
			}

			if ( spatializePrimary )
			{
				// grayman #4882 - Spatialize from primary location (player's ear)
				idVec3 p = player->GetPrimaryListenerLoc(); // doom units
				int area = rw->GetAreaAtPoint(p);
				def->Spatialize(true, p * DOOM_TO_METERS, area, rw); // to player's ear

				// save primary data
				pSpatializedOrigin = def->spatializedOrigin;
				pDistance = def->distance;
				pVolumeLoss = def->volumeLoss;
			}

			// grayman #4882 - Spatialize from secondary location (beyond door OR remote Listener)
			p = player->GetSecondaryListenerLoc(); // doom units
			if ( p != vec3_zero )
			{
				area = rw->GetAreaAtPoint(p);
				def->Spatialize(false, p * DOOM_TO_METERS, area, rw); // to active Listener

				// If we did a primary spatialize, determine whether the primary path or the secondary path provides the louder sound. Use the winner.

				if ( spatializePrimary )
				{
					float primaryEffectiveVolume = def->GetEffectiveVolume(pSpatializedOrigin, pDistance, pVolumeLoss);
					float secondaryEffectiveVolume = def->GetEffectiveVolume(def->spatializedOrigin, def->distance, def->volumeLoss);

					if ( primaryEffectiveVolume > secondaryEffectiveVolume ) // use data from higher effective volume
					{
						// put back the primary data
						def->spatializedOrigin = pSpatializedOrigin;
						def->distance = pDistance;
						def->volumeLoss = pVolumeLoss;
					}
				}
			}
		}
		else
		{
			def->Spatialize(true, vec3_zero, 0, rw);
		}
		// update virtual origin / distance, etc
//		def->Spatialize( listenerPos, listenerArea, rw ); // grayman #4882

		// per-sound debug options
		if ( s_drawSounds.GetInteger() && rw ) {
			if ( def->distance < def->maxDistance || s_drawSounds.GetInteger() > 1 ) {
				idBounds ref;
				ref.Clear();
				ref.AddPoint( idVec3( -10, -10, -10 ) );
				ref.AddPoint( idVec3(  10,  10,  10 ) );
				float vis = (1.0f - (def->distance / def->maxDistance));

				// draw a box
				rw->DebugBounds( idVec4( vis, 0.25f, vis, vis ), ref, def->origin );

				// draw an arrow to the audible position, possible a portal center
				if ( def->origin != def->spatializedOrigin ) {
					rw->DebugArrow( colorRed, def->origin, def->spatializedOrigin, 4 );
				}

				// draw the index
				idVec3	textPos = def->origin;
				textPos[2] -= 8;
				rw->DebugText( va("%i (%.1f)", def->index, def->volumeLoss), textPos, 0.1f, idVec4(1,0,0,1), listenerAxis );
				textPos[2] += 8;

				// run through all the channels
				for ( k = 0; k < SOUND_MAX_CHANNELS ; k++ ) {
					idSoundChannel	*chan = &def->channels[k];

					// see if we have a sound triggered on this channel
					if ( !chan->triggerState ) {
						continue;
					}

					char	text[1024];
					float	min = chan->parms.minDistance;
					float	max = chan->parms.maxDistance;
					const char	*defaulted = chan->leadinSample->defaultSound ? "(DEFAULTED)" : "";
					sprintf( text, "%s (%.1f/%.1f %.0f/%.0f)%s", chan->soundShader->GetName(), def->distance,
						def->realDistance, min, max, defaulted );
					rw->DebugText( text, textPos, 0.1f, idVec4(1,0,0,1), listenerAxis );
					textPos[2] += 8;
				}
			}
		}
	}

	Sys_LeaveCriticalSection();

	//
	// the sound meter
	//
	if ( idSoundSystemLocal::s_showLevelMeter.GetInteger() ) {
		const idMaterial *gui = declManager->FindMaterial( "guis/assets/soundmeter/audiobg", false );
		if ( gui ) {
			const shaderStage_t *foo = gui->GetStage(0);
			if ( !foo->texture.cinematic ) {
				((shaderStage_t *)foo)->texture.cinematic = new idSndWindow;
			}
		}
	}

	//
	// optionally dump out the generated sound
	//
	if ( fpa[0] ) {
		AVIUpdate();
	}
}

/*
===================
idSoundWorldLocal::OffsetSoundTime
===================
*/
void idSoundWorldLocal::OffsetSoundTime( int offset44kHz ) {
	int i, j;

	for ( i = 0; i < emitters.Num(); i++ ) {
		if ( emitters[i] == NULL ) {
			continue;
		}
		for ( j = 0; j < SOUND_MAX_CHANNELS; j++ ) {
			idSoundChannel *chan = &emitters[i]->channels[ j ];

			if ( !chan->triggerState ) {
				continue;
			}

			chan->trigger44kHzTime += offset44kHz;
		}
	}
}

/*
===================
idSoundWorldLocal::WriteToSaveGame
===================
*/
void idSoundWorldLocal::WriteToSaveGame( idFile *savefile ) {
	int i, j, num, currentSoundTime;
	const char *name;

	// the game soundworld is always paused at this point, save that time down
	if ( pause44kHz > 0 ) {
		currentSoundTime = pause44kHz;
	} else {
		currentSoundTime = soundSystemLocal.GetCurrent44kHzTime();
	}

	// write listener data
	savefile->WriteVec3(listenerQU);
	savefile->WriteMat3(listenerAxis);
	savefile->WriteInt(listenerPrivateId);
	savefile->WriteInt(gameMsec);
	savefile->WriteInt(game44kHz);
	savefile->WriteInt(currentSoundTime);

	num = emitters.Num();
	savefile->WriteInt(num);

	for ( i = 1; i < emitters.Num(); i++ ) {
		idSoundEmitterLocal *def = emitters[i];

		if ( def->removeStatus != REMOVE_STATUS_ALIVE ) {
			int skip = -1;
			savefile->Write( &skip, sizeof( skip ) );
			continue;
		}

		savefile->WriteInt(i);

		// Write the emitter data
		savefile->WriteVec3( def->origin );
		savefile->WriteInt( def->listenerId );
		WriteToSaveGameSoundShaderParams( savefile, &def->parms );
		savefile->WriteFloat( def->amplitude );
		savefile->WriteInt( def->ampTime );
		for (int k = 0; k < SOUND_MAX_CHANNELS; k++) 
			WriteToSaveGameSoundChannel( savefile, &def->channels[k] );
		savefile->WriteFloat( def->distance );
		savefile->WriteBool( def->hasShakes );
		savefile->WriteInt( def->lastValidPortalArea );
		savefile->WriteFloat( def->maxDistance );
		savefile->WriteBool( def->playing );
		savefile->WriteFloat( def->realDistance );
		savefile->WriteInt( def->removeStatus );
		savefile->WriteVec3( def->spatializedOrigin );

		// write the channel data
		for( j = 0; j < SOUND_MAX_CHANNELS; j++ ) {
			idSoundChannel *chan = &def->channels[ j ];

			// Write out any sound commands for this def
			if ( chan->triggerState && chan->soundShader && chan->leadinSample ) {

				savefile->WriteInt( j );

				// write the pointers out separately
				name = chan->soundShader->GetName();
				savefile->WriteString( name );
				
				name = chan->leadinSample->name;
				savefile->WriteString( name );
			}
		}

		// End active channels with -1
		int end = -1;
		savefile->WriteInt( end );
	}

	// new in Doom3 v1.2
	savefile->Write( &slowmoActive, sizeof( slowmoActive ) );
	savefile->Write( &slowmoSpeed, sizeof( slowmoSpeed ) );
	savefile->Write( &enviroSuitActive, sizeof( enviroSuitActive ) );
}

/*
 ===================
 idSoundWorldLocal::WriteToSaveGameSoundShaderParams
 ===================
 */
void idSoundWorldLocal::WriteToSaveGameSoundShaderParams( idFile *saveGame, soundShaderParms_t *params ) {
	saveGame->WriteFloat(params->minDistance);
	saveGame->WriteFloat(params->maxDistance);
	saveGame->WriteFloat(params->volume);
	saveGame->WriteFloat(params->shakes);
	saveGame->WriteInt(params->soundShaderFlags);
	saveGame->WriteInt(params->soundClass);
	saveGame->WriteInt(params->overrideMode);
}

/*
 ===================
 idSoundWorldLocal::WriteToSaveGameSoundChannel
 ===================
 */
void idSoundWorldLocal::WriteToSaveGameSoundChannel( idFile *saveGame, idSoundChannel *ch ) {
	saveGame->WriteBool( ch->triggerState );
	saveGame->WriteUnsignedChar( 0 );
	saveGame->WriteUnsignedChar( 0 );
	saveGame->WriteUnsignedChar( 0 );
	saveGame->WriteInt( ch->trigger44kHzTime );
	saveGame->WriteInt( ch->triggerGame44kHzTime );
	WriteToSaveGameSoundShaderParams( saveGame, &ch->parms );
	saveGame->WriteInt( 0 /* ch->leadinSample */ );
	saveGame->WriteInt( ch->triggerChannel );
	saveGame->WriteInt( 0 /* ch->soundShader */ );
	saveGame->WriteInt( 0 /* ch->decoder */ );
	saveGame->WriteFloat(ch->diversity );
	saveGame->WriteFloat(ch->lastVolume );
	for (int m = 0; m < 6; m++)
		saveGame->WriteFloat( ch->lastV[m] );
	saveGame->WriteInt( ch->channelFade.fadeStart44kHz );
	saveGame->WriteInt( ch->channelFade.fadeEnd44kHz );
	saveGame->WriteFloat( ch->channelFade.fadeStartVolume );
	saveGame->WriteFloat( ch->channelFade.fadeEndVolume );
}

/*
===================
idSoundWorldLocal::ReadFromSaveGame
===================
*/
void idSoundWorldLocal::ReadFromSaveGame( idFile *savefile ) {
	int i, num, handle, listenerId, gameTime, channel;
	int savedSoundTime, currentSoundTime, soundTimeOffset;
	idSoundEmitterLocal *def;
	idVec3 origin;
	idMat3 axis;
	idStr soundShader;

	ClearAllSoundEmitters();

	savefile->ReadVec3( origin );
	savefile->ReadMat3( axis );
	savefile->ReadInt( listenerId );
	savefile->ReadInt( gameTime );
	savefile->ReadInt( game44kHz );
	savefile->ReadInt( savedSoundTime );

	// we will adjust the sound starting times from those saved with the demo
	currentSoundTime = soundSystemLocal.GetCurrent44kHzTime();
	soundTimeOffset = currentSoundTime - savedSoundTime;

	// at the end of the level load we unpause the sound world and adjust the sound starting times once more
	pause44kHz = currentSoundTime;

	// place listener
	PlaceListener( origin, axis, listenerId, gameTime, "Undefined", "");

	// make sure there are enough
	// slots to read the saveGame in.  We don't shrink the list
	// if there are extras.
	savefile->ReadInt( num );

	while( emitters.Num() < num ) {
		def = new idSoundEmitterLocal;
		def->index = emitters.Append( def );
		def->soundWorld = this;
	}

	// read in the state
	for ( i = 1; i < num; i++ ) {

		savefile->ReadInt( handle );
		if ( handle < 0 ) {
			continue;
		}
		if ( handle != i ) {
			common->Error( "idSoundWorldLocal::ReadFromSaveGame: index mismatch" );
		}
		def = emitters[i];

		def->removeStatus = REMOVE_STATUS_ALIVE;
		def->playing = true;		// may be reset by the first UpdateListener

		savefile->ReadVec3( def->origin );
		savefile->ReadInt( def->listenerId );
		ReadFromSaveGameSoundShaderParams( savefile, &def->parms );
		savefile->ReadFloat( def->amplitude );
		savefile->ReadInt( def->ampTime );
		for (int k = 0; k < SOUND_MAX_CHANNELS; k++) 
			ReadFromSaveGameSoundChannel( savefile, &def->channels[k] );
		savefile->ReadFloat( def->distance );
		savefile->ReadBool( def->hasShakes );
		savefile->ReadInt( def->lastValidPortalArea );
		savefile->ReadFloat( def->maxDistance );
		savefile->ReadBool( def->playing );
		savefile->ReadFloat( def->realDistance );
		savefile->ReadInt( (int&)def->removeStatus );
		savefile->ReadVec3( def->spatializedOrigin );

		// read the individual channels
		savefile->ReadInt( channel );

		while ( channel >= 0 ) {
			if ( channel > SOUND_MAX_CHANNELS ) {
				common->Error( "idSoundWorldLocal::ReadFromSaveGame: channel > SOUND_MAX_CHANNELS" );
			}

			idSoundChannel *chan = &def->channels[channel];

			if ( !chan->decoder ) {
				// The pointer in the save file is not valid, so we grab a new one
				chan->decoder = idSampleDecoder::Alloc();
			}

			savefile->ReadString( soundShader );
			chan->soundShader = declManager->FindSound( soundShader );

			savefile->ReadString( soundShader );
			// load savegames with s_noSound 1
			if ( soundSystemLocal.soundCache ) {
				chan->leadinSample = soundSystemLocal.soundCache->FindSound( soundShader );
			} else {
				chan->leadinSample = NULL;
			}

			// adjust the hardware start time
			chan->trigger44kHzTime += soundTimeOffset;

			// make sure we start up the hardware voice if needed
			chan->triggered = chan->triggerState;
			chan->openalStreamingOffset = currentSoundTime - chan->trigger44kHzTime;

			// adjust the hardware fade time
			if ( chan->channelFade.fadeStart44kHz != 0 ) {
				chan->channelFade.fadeStart44kHz += soundTimeOffset;
				chan->channelFade.fadeEnd44kHz += soundTimeOffset;
			}

			// next command
			savefile->ReadInt( channel );
		}
	}

	if ( session->GetSaveGameVersion() >= 17 ) {
		savefile->Read( &slowmoActive, sizeof( slowmoActive ) );
		savefile->Read( &slowmoSpeed, sizeof( slowmoSpeed ) );
		savefile->Read( &enviroSuitActive, sizeof( enviroSuitActive ) );
	} else {
		slowmoActive		= false;
		slowmoSpeed			= 0;
		enviroSuitActive	= false;
	}
}

/*
 ===================
 idSoundWorldLocal::ReadFromSaveGameSoundShaderParams
 ===================
 */
void idSoundWorldLocal::ReadFromSaveGameSoundShaderParams( idFile *saveGame, soundShaderParms_t *params ) {
	saveGame->ReadFloat(params->minDistance);
	saveGame->ReadFloat(params->maxDistance);
	saveGame->ReadFloat(params->volume);
	saveGame->ReadFloat(params->shakes);
	saveGame->ReadInt(params->soundShaderFlags);
	saveGame->ReadInt(params->soundClass);
	saveGame->ReadInt(params->overrideMode);
}

/*
 ===================
 idSoundWorldLocal::ReadFromSaveGameSoundChannel
 ===================
 */
void idSoundWorldLocal::ReadFromSaveGameSoundChannel( idFile *saveGame, idSoundChannel *ch ) {
	saveGame->ReadBool( ch->triggerState );
	char tmp;
	int i;
	saveGame->ReadChar( tmp );
	saveGame->ReadChar( tmp );
	saveGame->ReadChar( tmp );
	saveGame->ReadInt( ch->trigger44kHzTime );
	saveGame->ReadInt( ch->triggerGame44kHzTime );
	ReadFromSaveGameSoundShaderParams( saveGame, &ch->parms );
	saveGame->ReadInt( i );
	ch->leadinSample = NULL;
	saveGame->ReadInt( ch->triggerChannel );
	saveGame->ReadInt( i );
	ch->soundShader = NULL;
	saveGame->ReadInt( i );
	ch->decoder = NULL;
	saveGame->ReadFloat(ch->diversity );
	saveGame->ReadFloat(ch->lastVolume );
	for (int m = 0; m < 6; m++)
		saveGame->ReadFloat( ch->lastV[m] );
	saveGame->ReadInt( ch->channelFade.fadeStart44kHz );
	saveGame->ReadInt( ch->channelFade.fadeEnd44kHz );
	saveGame->ReadFloat( ch->channelFade.fadeStartVolume );
	saveGame->ReadFloat( ch->channelFade.fadeEndVolume );
}

/*
===================
idSoundWorldLocal::EmitterForIndex
===================
*/
idSoundEmitter	*idSoundWorldLocal::EmitterForIndex( int index ) {
	if ( index == 0 ) {
		return NULL;
	}
	if ( index >= emitters.Num() ) {
		common->Error( "idSoundWorldLocal::EmitterForIndex: %i > %i", index, emitters.Num() );
	}
	return emitters[index];
}

/*
===============
idSoundWorldLocal::StopAllSounds

  this is called from the main thread
===============
*/
void idSoundWorldLocal::StopAllSounds() {

	for ( int i = 0; i < emitters.Num(); i++ ) {
		idSoundEmitterLocal * def = emitters[i];
		def->StopSound( SCHANNEL_ANY );
	}
}

/*
===============
idSoundWorldLocal::Pause
===============
*/
void idSoundWorldLocal::Pause( void ) {
	if ( pause44kHz >= 0 ) {
		common->Warning( "idSoundWorldLocal::Pause: already paused" );
		return;
	}

	pause44kHz = soundSystemLocal.GetCurrent44kHzTime();
}

/*
===============
idSoundWorldLocal::UnPause
===============
*/
void idSoundWorldLocal::UnPause( void ) {
	int offset44kHz;

	if ( pause44kHz < 0 ) {
		common->Warning( "idSoundWorldLocal::UnPause: not paused" );
		return;
	}

	offset44kHz = soundSystemLocal.GetCurrent44kHzTime() - pause44kHz;
	OffsetSoundTime( offset44kHz );

	pause44kHz = -1;
}

/*
===============
idSoundWorldLocal::IsPaused
===============
*/
bool idSoundWorldLocal::IsPaused( void ) {
	return ( pause44kHz >= 0 );
}

/*
===============
idSoundWorldLocal::PlayShaderDirectly

start a music track

  this is called from the main thread
===============
*/
void idSoundWorldLocal::PlayShaderDirectly( const char *shaderName, int channel ) {

	if ( localSound && channel == -1 ) {
		localSound->StopSound( SCHANNEL_ANY );
	} else if ( localSound ) {
		localSound->StopSound( channel );
	}

	if ( !shaderName || !shaderName[0] ) {
		return;
	}

	const idSoundShader *shader = declManager->FindSound( shaderName );
	if ( !shader ) {
		return;
	}

	if ( !localSound ) {
		// grayman #4882
		// If you have multiple listeners, then one emitter should be created
		// for each listener, and AllocLocalSoundEmitter() changes to
		// AllocLocalSoundEmitter(idVec3 listenerLocation). 
		// listenerLocation needs to be in meters, not DOOM units. The first
		// location will be the location of the player's ear, and the second
		// location will either be the other side of a door (when the player is
		// leaning against that door) OR the location of a remote listening
		// entity if one is active.

		idPlayer* player = gameLocal.GetLocalPlayer(); // grayman #4882
		if ( player )
		{
			localSound = AllocLocalSoundEmitter(player->firstPersonViewOrigin); // grayman #4882
			if ( player->m_Listener.GetEntity() )
			{
				secondarySound = AllocLocalSoundEmitter(player->m_Listener.GetEntity()->GetPhysics()->GetOrigin()); // grayman #4882
			}
			else // grayman #4882
			{
				secondarySound = NULL;
			}
		}
		else // grayman #4882
		{
			localSound = AllocLocalSoundEmitter(vec3_zero);
			secondarySound = NULL;
		}
	}

	static idRandom	rnd;
	float	diversity = rnd.RandomFloat();

	localSound->StartSound( shader, ( channel == -1 ) ? SCHANNEL_ONE : channel , diversity, SSF_GLOBAL );
	if ( secondarySound ) // grayman #4882
	{
		secondarySound->StartSound(shader, (channel == -1) ? SCHANNEL_ANY : channel, diversity, SSF_GLOBAL); // grayman #4882
	}

	// in case we are at the console without a game doing updates, force an update
	ForegroundUpdate( soundSystemLocal.GetCurrent44kHzTime() );
}

/*
===============
idSoundWorldLocal::CalcEars

Determine the volumes from each speaker for a given sound emitter
===============
*/
void idSoundWorldLocal::CalcEars( int numSpeakers, idVec3 spatializedOrigin, idVec3 listenerPos,
								 idMat3 listenerAxis, float ears[6], float spatialize ) {
	idVec3 svec = spatializedOrigin - listenerPos;
	idVec3 ovec;

	ovec[0] = svec * listenerAxis[0];
	ovec[1] = svec * listenerAxis[1];
	ovec[2] = svec * listenerAxis[2];

	ovec.Normalize();

	if ( numSpeakers == 6 ) {
		static idVec3	speakerVector[6] = {
			idVec3(  0.707f,  0.707f, 0.0f ),	// front left
			idVec3(  0.707f, -0.707f, 0.0f ),	// front right
			idVec3(  0.707f,  0.0f,   0.0f ),	// front center
			idVec3(  0.0f,    0.0f,   0.0f ),	// sub
			idVec3( -0.707f,  0.707f, 0.0f ),	// rear left
			idVec3( -0.707f, -0.707f, 0.0f )	// rear right
		};
		for ( int i = 0 ; i < 6 ; i++ ) {
			if ( i == 3 ) {
				ears[i] = idSoundSystemLocal::s_subFraction.GetFloat();		// subwoofer
				continue;
			}
			float dot = ovec * speakerVector[i];
			ears[i] = (idSoundSystemLocal::s_dotbias6.GetFloat() + dot) / ( 1.0f + idSoundSystemLocal::s_dotbias6.GetFloat() );
			if ( ears[i] < idSoundSystemLocal::s_minVolume6.GetFloat() ) {
				ears[i] = idSoundSystemLocal::s_minVolume6.GetFloat();
			}
		}
	} else {
		float dot = ovec.y;
		float dotBias = idSoundSystemLocal::s_dotbias2.GetFloat();

		// when we are inside the minDistance, start reducing the amount of spatialization
		// so NPC voices right in front of us aren't quieter that off to the side
		dotBias += ( idSoundSystemLocal::s_spatializationDecay.GetFloat() - dotBias ) * ( 1.0f - spatialize );

		ears[0] = (idSoundSystemLocal::s_dotbias2.GetFloat() + dot) / ( 1.0f + dotBias );
		ears[1] = (idSoundSystemLocal::s_dotbias2.GetFloat() - dot) / ( 1.0f + dotBias );

		if ( ears[0] < idSoundSystemLocal::s_minVolume2.GetFloat() ) {
			ears[0] = idSoundSystemLocal::s_minVolume2.GetFloat();
		}
		if ( ears[1] < idSoundSystemLocal::s_minVolume2.GetFloat() ) {
			ears[1] = idSoundSystemLocal::s_minVolume2.GetFloat();
		}

		ears[2] = 
		ears[3] = 
		ears[4] = 
		ears[5] = 0.0f;
	}
}

/*
===============
idSoundWorldLocal::AddChannelContribution

Adds the contribution of a single sound channel to finalMixBuffer
this is called from the async thread

Mixes MIXBUFFER_SAMPLES samples starting at current44kHz sample time into
finalMixBuffer
===============
*/

void idSoundWorldLocal::AddChannelContribution( idSoundEmitterLocal *sound, idSoundChannel *chan,
				   int current44kHz, int numSpeakers, float *finalMixBuffer ) {
	int j;
	float volume;

	//
	// get the sound definition and parameters from the entity
	//
	soundShaderParms_t *parms = &chan->parms;

	// assume we have a sound triggered on this channel
	assert( chan->triggerState );

	// fetch the actual wave file and see if it's valid
	idSoundSample *sample = chan->leadinSample;
	if ( sample == NULL ) {
		return;
	}

	// if you don't want to hear all the beeps from missing sounds
	if ( sample->defaultSound && !idSoundSystemLocal::s_playDefaultSound.GetBool() ) {
		return;
	}

	// get the actual shader
	const idSoundShader *shader = chan->soundShader;
	// this might happen if the foreground thread just deleted the sound emitter
	if ( !shader ) {
		return;
	}
	const char *shaderName = shader->GetName();

	float maxd = parms->maxDistance;
	float mind = parms->minDistance;
	
	int  mask = shader->speakerMask;
	bool omni = ( parms->soundShaderFlags & SSF_OMNIDIRECTIONAL) != 0;
	bool looping = ( parms->soundShaderFlags & SSF_LOOPING ) != 0;
	bool global = ( parms->soundShaderFlags & SSF_GLOBAL ) != 0;
	bool noOcclusion = ( parms->soundShaderFlags & SSF_NO_OCCLUSION ) || !idSoundSystemLocal::s_useOcclusion.GetBool();

	// speed goes from 1 to 0.2
	if ( idSoundSystemLocal::s_slowAttenuate.GetBool() && slowmoActive && !chan->disallowSlow ) {
		maxd *= slowmoSpeed;
	}

	// stereo samples are always omni
	if ( sample->objectInfo.nChannels == 2 ) {
		omni = true;
	}

	// if the sound is playing from the current listener, it will not be spatialized at all
	if ( sound->listenerId == listenerPrivateId ) {
		global = true;
	}

	//
	// see if it's in range
	//
	
	// convert volumes from decibels to float scale

	// leadin volume scale for shattering lights
	// this isn't exactly correct, because the modified volume will get applied to
	// some initial chunk of the loop as well, because the volume is scaled for the
	// entire mix buffer

	float volumeDB = 0; // grayman #3042
	if ( shader->leadinVolume && ( ( current44kHz - chan->trigger44kHzTime ) < sample->DurationIn44kHzSamples() ) )
	{
		volumeDB = shader->leadinVolume;
	}
	else
	{
		volumeDB = parms->volume;
	}

	// grayman #3042 - apply accumulated volume loss from possibly traveling through portals
	if ( !global && !noOcclusion )
	{
		volumeDB -= sound->volumeLoss;
	}

	// grayman #3556 - apply sound loss if the player's ears are underwater

	if (game->PlayerUnderwater())
	{
		volumeDB -= UNDERWATER_VOLUME_LOSS;
	}

	volume = soundSystemLocal.dB2Scale( volumeDB );
		
	// global volume scale
	volume *= soundSystemLocal.dB2Scale( idSoundSystemLocal::s_volume.GetFloat() );

	// volume fading
	float	fadeDb = chan->channelFade.FadeDbAt44kHz( current44kHz );
	volume *= soundSystemLocal.dB2Scale( fadeDb );

	fadeDb = soundClassFade[parms->soundClass].FadeDbAt44kHz( current44kHz );
	volume *= soundSystemLocal.dB2Scale( fadeDb );

	//
	// if it's a global sound then
	// it's not affected by distance or occlusion
	//
	float	spatialize = 1;
	idVec3 spatializedOriginInMeters;
	float totalSoundDistance = 0.0f;
	if ( !global )
	{
		float dlen;

		if ( noOcclusion )
		{
			// use the real origin and distance
			spatializedOriginInMeters = sound->origin * DOOM_TO_METERS;
			dlen = sound->realDistance;
		}
		else
		{
			// use the possibly portal-occluded origin and distance
			spatializedOriginInMeters = sound->spatializedOrigin * DOOM_TO_METERS;
			dlen = sound->distance;
		}

		// reduce volume based on distance
		if ( dlen >= maxd )
		{
			volume = 0.0f;
		}
		else if ( dlen > mind )
		{
			float frac = idMath::ClampFloat( 0.0f, 1.0f, 1.0f - ((dlen - mind) / (maxd - mind)));
			if ( idSoundSystemLocal::s_quadraticFalloff.GetBool() )
			{
				frac *= frac;
			}
			volume *= frac;
		}
		else if ( mind > 0.0f )
		{
			// we tweak the spatialization bias when you are inside the minDistance
			spatialize = dlen / mind;
		}

		totalSoundDistance = dlen;	// stgatilov: need this for subtitles location
	}

	//
	// if it is a private sound, set the volume to zero
	// unless we match the listenerId
	//
	if ( parms->soundShaderFlags & SSF_PRIVATE_SOUND ) {
		if ( sound->listenerId != listenerPrivateId ) {
			volume = 0;
		}
	}
	if ( parms->soundShaderFlags & SSF_ANTI_PRIVATE_SOUND ) {
		if ( sound->listenerId == listenerPrivateId ) {
			volume = 0;
		}
	}

	//
	// do we have anything to add?
	//
	if ( volume < SND_EPSILON && chan->lastVolume < SND_EPSILON ) {
		return;
	}
	chan->lastVolume = volume;

	//
	// fetch the sound from the cache as 44kHz, 16 bit samples
	//
	int offset = current44kHz - chan->trigger44kHzTime;
	ALIGNTYPE16 float inputSamplesFloat[MIXBUFFER_SAMPLES * 2 + 16];
	ALIGNTYPE16 short inputSamplesShort[MIXBUFFER_SAMPLES * 2 + 16];

	//
	// allocate and initialize hardware source
	// 
	if ( sound->removeStatus < REMOVE_STATUS_SAMPLEFINISHED )
	{
		// stgatilov #2454: add any active subtitles to soundWorld's buffer
		idList<SubtitleMatch> &destSubtitles = activeSubtitles[!activeSubtitlesFrame];
		int subsNum = chan->GatherSubtitles(
			offset * sample->objectInfo.nChannels,
			destSubtitles,
			cv_tdm_subtitles.GetInteger()
		);
		for ( int i = destSubtitles.Num() - subsNum; i < destSubtitles.Num(); i++ ) {
			destSubtitles[i].emitter = sound;
			destSubtitles[i].channel = chan;
			destSubtitles[i].spatializedDirection.Zero();
			if ( !(global || omni) )
				destSubtitles[i].spatializedDirection = ( listenerAxis.Transpose() * ( spatializedOriginInMeters - listenerPos ) ).Normalized() * totalSoundDistance;
			float unitSampleVolume = ( chan->currentSampleVolume / 32768.0f );
			assert( unitSampleVolume <= 1.01f );
			destSubtitles[i].volume = volume * unitSampleVolume;
		}

		if ( !alIsSource( chan->openalSource ) ) {
			chan->openalSource = soundSystemLocal.AllocOpenALSource( chan, !chan->leadinSample->hardwareBuffer || !chan->soundShader->entries[0]->hardwareBuffer || looping, chan->leadinSample->objectInfo.nChannels == 2 );
		}

		if ( alIsSource( chan->openalSource ) ) {
			
			// stop source if needed..
			if ( chan->triggered ) {
				alSourceStop( chan->openalSource );
			}

			// update source parameters
			if ( global || omni ) {
				alSourcei( chan->openalSource, AL_SOURCE_RELATIVE, AL_TRUE);
				alSource3f( chan->openalSource, AL_POSITION, 0.0f, 0.0f, 0.0f );
				alSourcef( chan->openalSource, AL_GAIN, ( volume ) < ( 1.0f ) ? ( volume ) : ( 1.0f ) );
			} else {
				alSourcei( chan->openalSource, AL_SOURCE_RELATIVE, AL_FALSE);
				alSource3f( chan->openalSource, AL_POSITION, -spatializedOriginInMeters.y, spatializedOriginInMeters.z, -spatializedOriginInMeters.x );
				alSourcef( chan->openalSource, AL_GAIN, ( volume ) < ( 1.0f ) ? ( volume ) : ( 1.0f ) );
			}
			alSourcei( chan->openalSource, AL_LOOPING, ( looping && chan->soundShader->entries[0]->hardwareBuffer ) ? AL_TRUE : AL_FALSE );
#if 1
			alSourcef( chan->openalSource, AL_REFERENCE_DISTANCE, mind );
			alSourcef( chan->openalSource, AL_MAX_DISTANCE, maxd );
#endif
			alSourcef( chan->openalSource, AL_PITCH, ( slowmoActive && !chan->disallowSlow ) ? ( slowmoSpeed ) : ( 1.0f ) );

			if (idSoundSystemLocal::useEFXReverb) {
				int shaderFlags = chan->soundShader->GetParms()->soundShaderFlags;
				if (shaderFlags & SSF_NO_EFX) {
					//stgatilov: disable EFX effect for sound shaders containing "no_efx" keyword
					//(see http://forums.thedarkmod.com/topic/19213-efx-discussion)
					alSource3i(chan->openalSource, AL_AUXILIARY_SEND_FILTER, AL_EFFECTSLOT_NULL, 0, AL_FILTER_NULL);
				}
				else {
					if (enviroSuitActive) {
						alSourcei(chan->openalSource, AL_DIRECT_FILTER, listenerFilter);
						alSource3i(chan->openalSource, AL_AUXILIARY_SEND_FILTER, listenerSlot, 0, listenerFilter);
					}
					else {
						//stgatilov: enable EFX effect of current area (by sending source into effect slow)
						alSource3i(chan->openalSource, AL_AUXILIARY_SEND_FILTER, listenerSlot, 0, AL_FILTER_NULL);
					}
				}
			}

			bool isStreaming = !( ( !looping && chan->leadinSample->hardwareBuffer ) || ( looping && chan->soundShader->entries[0]->hardwareBuffer ) );
			if ( !isStreaming ) {
				// handle uncompressed (non streaming) single shot and looping sounds
				if ( chan->triggered ) {
					alSourcei( chan->openalSource, AL_BUFFER, looping ? chan->soundShader->entries[0]->openalBuffer : chan->leadinSample->openalBuffer );
				}
			} else {
				ALint finishedbuffers;
				ALuint buffers[3];

				// handle streaming sounds (decode on the fly) both single shot AND looping
				if ( chan->triggered ) {
					alSourcei( chan->openalSource, AL_BUFFER, 0 );
					alDeleteBuffers( 3, &chan->lastopenalStreamingBuffer[0] );
					chan->lastopenalStreamingBuffer[0] = chan->openalStreamingBuffer[0];
					chan->lastopenalStreamingBuffer[1] = chan->openalStreamingBuffer[1];
					chan->lastopenalStreamingBuffer[2] = chan->openalStreamingBuffer[2];
					alGenBuffers( 3, &chan->openalStreamingBuffer[0] );
					buffers[0] = chan->openalStreamingBuffer[0];
					buffers[1] = chan->openalStreamingBuffer[1];
					buffers[2] = chan->openalStreamingBuffer[2];
					finishedbuffers = 3;
					if (chan->openalStreamingOffset != offset) {
						//stgatilov #4716: start playing sound from its current offset (not from the beginning)
						//this is particularly important for looping sounds which play always
						chan->openalStreamingOffset = offset;
					}
				} else {
					alGetSourcei( chan->openalSource, AL_BUFFERS_PROCESSED, &finishedbuffers );
					alSourceUnqueueBuffers( chan->openalSource, finishedbuffers, &buffers[0] );
					if ( finishedbuffers == 3 ) {
						chan->triggered = true;
					}
				}

				for ( j = 0; j < finishedbuffers; j++ ) {
					chan->GatherChannelSamples( chan->openalStreamingOffset * sample->objectInfo.nChannels, MIXBUFFER_SAMPLES * sample->objectInfo.nChannels, inputSamplesFloat );
					float maxValue = 0.0f;
					for ( int i = 0; i < ( MIXBUFFER_SAMPLES * sample->objectInfo.nChannels ); i++ ) {
						inputSamplesShort[i] = idMath::FtoiRound( idMath::ClampFloat( -32768.0f, 32767.0f, inputSamplesFloat[i] ) );
						maxValue = idMath::Fmax( maxValue, idMath::Fabs(inputSamplesFloat[i]) );
					}
					alBufferData( buffers[j], chan->leadinSample->objectInfo.nChannels == 1 ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16, inputSamplesShort, MIXBUFFER_SAMPLES * sample->objectInfo.nChannels * sizeof( short ), 44100 );
					chan->openalStreamingOffset += MIXBUFFER_SAMPLES;
					// stgatilov #6491: update current sample volume for subtitles
					chan->currentSampleVolume = chan->currentSampleVolume * 0.85f + maxValue * 0.15f;	// fades to 20% after second
				}

				if ( finishedbuffers ) {
					alSourceQueueBuffers( chan->openalSource, finishedbuffers, &buffers[0] );
				}

				// #4803 stgatilov: avoid offset overflow (which causes crash)
				if (chan->openalStreamingOffset * sample->objectInfo.nChannels > INT_MAX - (1<<20)) {
					//simply restart this sound (happens after 6.75 hours of repetition)
					chan->trigger44kHzTime = current44kHz;
					chan->openalStreamingOffset = 0;
					common->Warning("Restarted sound to avoid offset overflow: %s", chan->leadinSample->name.c_str());
				}
			}
			
			// (re)start if needed..
			bool restart = chan->triggered;
			if (isStreaming) {
				// #4540,#4949 stgatilov: force restart on streaming sounds
				ALint state = 0;
				alGetSourcei( chan->openalSource, AL_SOURCE_STATE, &state);
				if (state != AL_PLAYING)
					restart = true;
			}
			if ( restart ) {
				alSourcePlay( chan->openalSource );
				chan->triggered = false;
			}
		}
	}
	else
	{
		if ( slowmoActive && !chan->disallowSlow )
		{
			idSlowChannel slow = sound->GetSlowChannel( chan );

			slow.AttachSoundChannel( chan );

				if ( sample->objectInfo.nChannels == 2 ) {
					// need to add a stereo path, but very few samples go through this
					memset( inputSamplesFloat, 0, sizeof( inputSamplesFloat[0] ) * MIXBUFFER_SAMPLES * 2 );
				} else {
					slow.GatherChannelSamples( offset, MIXBUFFER_SAMPLES, inputSamplesFloat );
				}

			sound->SetSlowChannel( chan, slow );
		}
		else
		{
			sound->ResetSlowChannel( chan );

			// if we are getting a stereo sample adjust accordingly
			if ( sample->objectInfo.nChannels == 2 ) {
				// we should probably check to make sure any looping is also to a stereo sample...
				chan->GatherChannelSamples( offset*2, MIXBUFFER_SAMPLES*2, inputSamplesFloat );
			} else {
				chan->GatherChannelSamples( offset, MIXBUFFER_SAMPLES, inputSamplesFloat );
			}
		}

		//
		// work out the left / right ear values
		//
		float	ears[6];
		if ( global || omni )
		{
			// same for all speakers
			for ( int i = 0 ; i < 6 ; i++ )
			{
				ears[i] = idSoundSystemLocal::s_globalFraction.GetFloat() * volume;
			}
			ears[3] = idSoundSystemLocal::s_subFraction.GetFloat() * volume;		// subwoofer

		} 
		else
		{
			// grayman #4882 - listenerPos in the following needs to have separate calls for
			// the player's ear and an active Listener?
			// What about listenerAxis?

			CalcEars( numSpeakers, spatializedOriginInMeters, listenerPos, listenerAxis, ears, spatialize );

			for ( int i = 0 ; i < 6 ; i++ )
			{
				ears[i] *= volume;
			}
		}

		// if the mask is 0, it really means do every channel
		if ( !mask ) {
			mask = 255;
		}
		// cleared mask bits set the mix volume to zero
		for ( int i = 0 ; i < 6 ; i++ ) {
			if ( !(mask & ( 1 << i ) ) ) {
				ears[i] = 0;
			}
		}

		// if sounds are generally normalized, using a mixing volume over 1.0 will
		// almost always cause clipping noise.  If samples aren't normalized, there
		// is a good call to allow overvolumes
		if ( idSoundSystemLocal::s_clipVolumes.GetBool() && !( parms->soundShaderFlags & SSF_UNCLAMPED )  ) {
			for ( int i = 0 ; i < 6 ; i++ ) {
				if ( ears[i] > 1.0f ) {
					ears[i] = 1.0f;
				}
			}
		}

		// if this is the very first mixing block, set the lastV
		// to the current volume
		if ( current44kHz == chan->trigger44kHzTime ) {
			for ( j = 0 ; j < 6 ; j++ ) {
				chan->lastV[j] = ears[j];
			}
		}

		if ( numSpeakers == 6 ) {
			if ( sample->objectInfo.nChannels == 1 ) {
				SIMDProcessor->MixSoundSixSpeakerMono( finalMixBuffer, inputSamplesFloat, MIXBUFFER_SAMPLES, chan->lastV, ears );
			} else {
				SIMDProcessor->MixSoundSixSpeakerStereo( finalMixBuffer, inputSamplesFloat, MIXBUFFER_SAMPLES, chan->lastV, ears );
			}
		} else {
			if ( sample->objectInfo.nChannels == 1 ) {
				SIMDProcessor->MixSoundTwoSpeakerMono( finalMixBuffer, inputSamplesFloat, MIXBUFFER_SAMPLES, chan->lastV, ears );
			} else {
				SIMDProcessor->MixSoundTwoSpeakerStereo( finalMixBuffer, inputSamplesFloat, MIXBUFFER_SAMPLES, chan->lastV, ears );
			}
		}

		for ( j = 0 ; j < 6 ; j++ ) {
			chan->lastV[j] = ears[j];
		}

	}

	soundSystemLocal.soundStats.activeSounds++;

}

/*
===============
idSoundWorldLocal::FindAmplitude

  this is called from the main thread

  if listenerPosition is NULL, this is being used for shader parameters,
  like flashing lights and glows based on sound level.  Otherwise, it is being used for
  the screen-shake on a player.

  This doesn't do the portal-occlusion currently, because it would have to reset all the defs
  which would be problematic in multiplayer
===============
*/
float idSoundWorldLocal::FindAmplitude( idSoundEmitterLocal *sound, const int localTime, const idVec3 *listenerPosition, 
									   const s_channelType channel, bool shakesOnly ) {
	int		i, j;
	soundShaderParms_t *parms;
	float	volume;
	int		activeChannelCount;
	static const int AMPLITUDE_SAMPLES = MIXBUFFER_SAMPLES/8;
	float	sourceBuffer[AMPLITUDE_SAMPLES];
	float	sumBuffer[AMPLITUDE_SAMPLES];
	// work out the distance from the listener to the emitter
	float	dlen;

	if ( !sound->playing ) {
		return 0;
	}

	if ( listenerPosition ) {
		// this doesn't do the portal spatialization
		idVec3 dist = sound->origin - *listenerPosition;
		dlen = dist.Length();
		dlen *= DOOM_TO_METERS;
	} else {
		dlen = 1;
	}

	activeChannelCount = 0;

	for ( i = 0; i < SOUND_MAX_CHANNELS ; i++ ) {
		idSoundChannel	*chan = &sound->channels[ i ];

		if ( !chan->triggerState ) {
			continue;
		}

		if ( channel != SCHANNEL_ANY && chan->triggerChannel != channel) {
			continue;
		}

		parms = &chan->parms;

		int	localTriggerTimes = chan->trigger44kHzTime;

		bool looping = ( parms->soundShaderFlags & SSF_LOOPING ) != 0;

		// check for screen shakes
		float shakes = parms->shakes;
		if ( shakesOnly && shakes <= 0.0f ) {
			continue;
		}

		//
		// calculate volume
		//
		if ( !listenerPosition ) {
			// just look at the raw wav data for light shader evaluation
			volume = 1.0;
		} else {
			volume = parms->volume;
			volume = soundSystemLocal.dB2Scale( volume );
			if ( shakesOnly ) {
				volume *= shakes;
			}

			if ( listenerPosition && !( parms->soundShaderFlags & SSF_GLOBAL )  ) {			
				// check for overrides
				float maxd = parms->maxDistance;
				float mind = parms->minDistance;

				if ( dlen >= maxd ) {
					volume = 0.0f;
				} else if ( dlen > mind ) {
					float frac = idMath::ClampFloat( 0, 1, 1.0f - ((dlen - mind) / (maxd - mind)));
					if ( idSoundSystemLocal::s_quadraticFalloff.GetBool() ) {
						frac *= frac;
					}
					volume *= frac;
				}
			}
		}

		if ( volume <= 0 ) {
			continue;
		}

		//
		// fetch the sound from the cache
		// this doesn't handle stereo samples correctly...
		//
		if ( !listenerPosition && chan->parms.soundShaderFlags & SSF_NO_FLICKER ) {
			// the NO_FLICKER option is to allow a light to still play a sound, but
			// not have it effect the intensity
			for ( j = 0 ; j < (AMPLITUDE_SAMPLES); j++ ) {
				sourceBuffer[j] = j & 1 ? 32767.0f : -32767.0f;
			}
		} else {
			int offset = (localTime - localTriggerTimes);	// offset in samples
			int size = ( looping ? chan->soundShader->entries[0]->LengthIn44kHzSamples() : chan->leadinSample->LengthIn44kHzSamples() );
			short *amplitudeData = (short *)( looping ? chan->soundShader->entries[0]->amplitudeData : chan->leadinSample->amplitudeData );
	
			if ( amplitudeData ) {
				// when the amplitudeData is present use that fill a dummy sourceBuffer
				// this is to allow for amplitude based effect on hardware audio solutions
				if ( looping ) offset %= size;
				if ( offset < size ) {
					for ( j = 0 ; j < (AMPLITUDE_SAMPLES); j++ ) {
						sourceBuffer[j] = j & 1 ? amplitudeData[ ( offset / 512 ) * 2 ] : amplitudeData[ ( offset / 512 ) * 2 + 1 ];
					}
				}
			} else {
				// get actual sample data
				chan->GatherChannelSamples( offset, AMPLITUDE_SAMPLES, sourceBuffer );
			}
		}
		activeChannelCount++;
		if ( activeChannelCount == 1 ) {
			// store to the buffer
			for( j = 0; j < AMPLITUDE_SAMPLES; j++ ) {
				sumBuffer[ j ] = volume * sourceBuffer[ j ];
			}
		} else {
			// add to the buffer
			for( j = 0; j < AMPLITUDE_SAMPLES; j++ ) {
				sumBuffer[ j ] += volume * sourceBuffer[ j ];
			}
		}
	}

	if ( activeChannelCount == 0 ) {
		return 0.0;
	}

	float high = -32767.0f;
	float low = 32767.0f;

	// use a 20th of a second
	for( i = 0; i < (AMPLITUDE_SAMPLES); i++ ) {
		float fabval = sumBuffer[i];
		if ( high < fabval ) {
			high = fabval;
		}
		if ( low > fabval ) {
			low = fabval;
		}
	}

	float sout;
	sout = atan( (high - low) / 32767.0f) / DEG2RAD(45);

	return sout;
}

/*
=================
idSoundWorldLocal::FadeSoundClasses

fade all sounds in the world with a given shader soundClass
to is in Db (sigh), over is in seconds
=================
*/
void	idSoundWorldLocal::FadeSoundClasses( const int soundClass, const float to, const float over ) {
	if ( soundClass < 0 || soundClass >= SOUND_MAX_CLASSES ) {
		common->Error( "idSoundWorldLocal::FadeSoundClasses: bad soundClass %i", soundClass );
	}

	idSoundFade	*fade = &soundClassFade[ soundClass ];

	int	length44kHz = soundSystemLocal.MillisecondsToSamples( over * 1000 );

	// if it is already fading to this volume at this rate, don't change it
	if ( fade->fadeEndVolume == to && 
		fade->fadeEnd44kHz - fade->fadeStart44kHz == length44kHz ) {
		return;
	}

	int	start44kHz;

	if ( fpa[0] ) {
		// if we are recording an AVI demo, don't use hardware time
		start44kHz = lastAVI44kHz + MIXBUFFER_SAMPLES;
	} else {
		start44kHz = soundSystemLocal.GetCurrent44kHzTime() + MIXBUFFER_SAMPLES;
	}

	// fade it
	fade->fadeStartVolume = fade->FadeDbAt44kHz( start44kHz );
	fade->fadeStart44kHz = start44kHz;
	fade->fadeEnd44kHz = start44kHz + length44kHz;
	fade->fadeEndVolume = to;
}

/*
=================
idSoundWorldLocal::SetSlowmo
=================
*/
void idSoundWorldLocal::SetSlowmo( bool active ) {
	slowmoActive = active;
}

/*
=================
idSoundWorldLocal::SetSlowmoSpeed
=================
*/
void idSoundWorldLocal::SetSlowmoSpeed( float speed ) {
	slowmoSpeed = speed;
}

/*
=================
idSoundWorldLocal::SetEnviroSuit
=================
*/
void idSoundWorldLocal::SetEnviroSuit( bool active ) {
	enviroSuitActive = active;
}
