diff options
Diffstat (limited to 'engine/audio/private/snd_mix.cpp')
| -rw-r--r-- | engine/audio/private/snd_mix.cpp | 4293 |
1 files changed, 4293 insertions, 0 deletions
diff --git a/engine/audio/private/snd_mix.cpp b/engine/audio/private/snd_mix.cpp new file mode 100644 index 0000000..ca44cbf --- /dev/null +++ b/engine/audio/private/snd_mix.cpp @@ -0,0 +1,4293 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Portable code to mix sounds for snd_dma.cpp. +// +//=============================================================================// + +#include "audio_pch.h" + +#include "mouthinfo.h" +#include "../../cl_main.h" +#include "icliententitylist.h" +#include "icliententity.h" +#include "../../sys_dll.h" +#include "video/ivideoservices.h" +#include "engine/IEngineSound.h" + +#if defined( REPLAY_ENABLED ) +#include "demo.h" +#include "replay_internal.h" +#endif +#ifdef GNUC +// we don't suport the ASM in this file right now under GCC, fallback to C libs +#undef id386 +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if defined( REPLAY_ENABLED ) +extern IReplayMovieManager *g_pReplayMovieManager; +#endif + +#if defined(_WIN32) && id386 +// warning C4731: frame pointer register 'ebp' modified by inline assembly code +#pragma warning(disable : 4731) +#endif + +// NOTE: !!!!!! YOU MUST UPDATE SND_MIXA.S IF THIS VALUE IS CHANGED !!!!! +#define SND_SCALE_BITS 7 +#define SND_SCALE_SHIFT (8-SND_SCALE_BITS) +#define SND_SCALE_LEVELS (1<<SND_SCALE_BITS) + +#define SND_SCALE_BITS16 8 +#define SND_SCALE_SHIFT16 (8-SND_SCALE_BITS16) +#define SND_SCALE_LEVELS16 (1<<SND_SCALE_BITS16) + +void Snd_WriteLinearBlastStereo16(void); +void SND_PaintChannelFrom8( portable_samplepair_t *pOutput, int *volume, byte *pData8, int count ); +bool Con_IsVisible( void ); +void SND_RecordBuffer( void ); +bool DSP_RoomDSPIsOff( void ); +bool BChannelLowVolume( channel_t *pch, int vol_min ); +void ChannelCopyVolumes( channel_t *pch, int *pvolume_dest, int ivol_start, int cvol ); +float ChannelLoudestCurVolume( const channel_t * RESTRICT pch ); + +extern int g_soundtime; +extern float host_frametime; +extern float host_frametime_unbounded; + + +#if !defined( NO_VOICE ) +extern int g_SND_VoiceOverdriveInt; +#endif + +extern ConVar dsp_room; +extern ConVar dsp_water; +extern ConVar dsp_player; +extern ConVar dsp_facingaway; +extern ConVar snd_showstart; +extern ConVar dsp_automatic; +extern ConVar snd_pitchquality; + +extern float DSP_ROOM_MIX; +extern float DSP_NOROOM_MIX; + +portable_samplepair_t *g_paintbuffer; + +// temp paintbuffer - not included in main list of paintbuffers +// NOTE: this paintbuffer is also used as a copy buffer by interpolating pitch +// shift routines. Decreasing TEMP_COPY_BUFFER_SIZE (or PAINTBUFFER_MEM_SIZE) +// will decrease the maximum pitch level (current 4.0)! +portable_samplepair_t *g_temppaintbuffer = NULL; + +CUtlVector< paintbuffer_t > g_paintBuffers; + + +// pointer to current paintbuffer (front and reare), used by all mixing, upsampling and dsp routines +portable_samplepair_t *g_curpaintbuffer = NULL; +portable_samplepair_t *g_currearpaintbuffer = NULL; +portable_samplepair_t *g_curcenterpaintbuffer = NULL; + +bool g_bdirectionalfx; +bool g_bDspOff; +float g_dsp_volume; + +// dsp performance timing +unsigned g_snd_call_time_debug = 0; +unsigned g_snd_time_debug = 0; +unsigned g_snd_count_debug = 0; +unsigned g_snd_samplecount = 0; +unsigned g_snd_frametime = 0; +unsigned g_snd_frametime_total = 0; +int g_snd_profile_type = 0; // type 1 dsp, type 2 mixer, type 3 load sound, type 4 all sound + +#define FILTERTYPE_NONE 0 +#define FILTERTYPE_LINEAR 1 +#define FILTERTYPE_CUBIC 2 + +// filter memory for upsampling +portable_samplepair_t cubicfilter1[3] = {{0,0},{0,0},{0,0}}; +portable_samplepair_t cubicfilter2[3] = {{0,0},{0,0},{0,0}}; + +portable_samplepair_t linearfilter1[1] = {{0,0}}; +portable_samplepair_t linearfilter2[1] = {{0,0}}; +portable_samplepair_t linearfilter3[1] = {{0,0}}; +portable_samplepair_t linearfilter4[1] = {{0,0}}; +portable_samplepair_t linearfilter5[1] = {{0,0}}; +portable_samplepair_t linearfilter6[1] = {{0,0}}; +portable_samplepair_t linearfilter7[1] = {{0,0}}; +portable_samplepair_t linearfilter8[1] = {{0,0}}; + +int snd_scaletable[SND_SCALE_LEVELS][256]; // 32k*4 = 128K + +int *snd_p, snd_linear_count, snd_vol; +short *snd_out; + +extern int DSP_Alloc( int ipset, float xfade, int cchan ); + +bool DSP_CheckDspAutoEnabled( void ); +int Get_idsp_room ( void ); +int dsp_room_GetInt ( void ); +void DSP_SetDspAuto( int dsp_preset ); +bool DSP_CheckDspAutoEnabled( void ); + +void MIX_ScalePaintBuffer( int bufferIndex, int count, float fgain ); + +bool IsReplayRendering() +{ +#if defined( REPLAY_ENABLED ) + return g_pReplayMovieManager && g_pReplayMovieManager->IsRendering(); +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +// Free allocated memory buffers +//----------------------------------------------------------------------------- +void MIX_FreeAllPaintbuffers(void) +{ + if ( g_paintBuffers.Count() ) + { + if ( g_temppaintbuffer ) + { + _aligned_free( g_temppaintbuffer ); + g_temppaintbuffer = NULL; + } + + for ( int i = 0; i < g_paintBuffers.Count(); i++ ) + { + if ( g_paintBuffers[i].pbuf ) + { + _aligned_free( g_paintBuffers[i].pbuf ); + } + if ( g_paintBuffers[i].pbufrear ) + { + _aligned_free( g_paintBuffers[i].pbufrear ); + } + if ( g_paintBuffers[i].pbufcenter ) + { + _aligned_free( g_paintBuffers[i].pbufcenter ); + } + } + + g_paintBuffers.RemoveAll(); + } +} + +void MIX_InitializePaintbuffer( paintbuffer_t *pPaintBuffer, bool bSurround, bool bSurroundCenter ) +{ + V_memset( pPaintBuffer, 0, sizeof( *pPaintBuffer ) ); + + pPaintBuffer->pbuf = (portable_samplepair_t *)_aligned_malloc( PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t), 16 ); + V_memset( pPaintBuffer->pbuf, 0, PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t) ); + + if ( bSurround ) + { + pPaintBuffer->pbufrear = (portable_samplepair_t *)_aligned_malloc( PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t), 16 ); + V_memset( pPaintBuffer->pbufrear, 0, PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t) ); + } + if ( bSurroundCenter ) + { + pPaintBuffer->pbufcenter = (portable_samplepair_t *)_aligned_malloc( PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t), 16 ); + V_memset( pPaintBuffer->pbufcenter, 0, PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t) ); + } +} + +//----------------------------------------------------------------------------- +// Allocate memory buffers +// Initialize paintbuffers array, set current paint buffer to main output buffer SOUND_BUFFER_PAINT +//----------------------------------------------------------------------------- +bool MIX_InitAllPaintbuffers(void) +{ + bool bSurround; + bool bSurroundCenter; + + bSurroundCenter = g_AudioDevice->IsSurroundCenter(); + bSurround = g_AudioDevice->IsSurround() || bSurroundCenter; + + g_temppaintbuffer = (portable_samplepair_t*)_aligned_malloc( TEMP_COPY_BUFFER_SIZE*sizeof(portable_samplepair_t), 16 ); + V_memset( g_temppaintbuffer, 0, TEMP_COPY_BUFFER_SIZE*sizeof(portable_samplepair_t) ); + + while ( g_paintBuffers.Count() < SOUND_BUFFER_BASETOTAL ) + { + int nIndex = g_paintBuffers.AddToTail(); + MIX_InitializePaintbuffer( &(g_paintBuffers[ nIndex ]), bSurround, bSurroundCenter ); + } + + g_paintbuffer = g_paintBuffers[SOUND_BUFFER_PAINT].pbuf; + + // buffer flags + g_paintBuffers[SOUND_BUFFER_ROOM].flags = SOUND_BUSS_ROOM; + g_paintBuffers[SOUND_BUFFER_FACING].flags = SOUND_BUSS_FACING; + g_paintBuffers[SOUND_BUFFER_FACINGAWAY].flags = SOUND_BUSS_FACINGAWAY; + g_paintBuffers[SOUND_BUFFER_SPEAKER].flags = SOUND_BUSS_SPEAKER; + g_paintBuffers[SOUND_BUFFER_DRY].flags = SOUND_BUSS_DRY; + + // buffer surround sound flag + g_paintBuffers[SOUND_BUFFER_PAINT].fsurround = bSurround; + g_paintBuffers[SOUND_BUFFER_FACING].fsurround = bSurround; + g_paintBuffers[SOUND_BUFFER_FACINGAWAY].fsurround = bSurround; + g_paintBuffers[SOUND_BUFFER_DRY].fsurround = bSurround; + + // buffer 5 channel surround sound flag + g_paintBuffers[SOUND_BUFFER_PAINT].fsurround_center = bSurroundCenter; + g_paintBuffers[SOUND_BUFFER_FACING].fsurround_center = bSurroundCenter; + g_paintBuffers[SOUND_BUFFER_FACINGAWAY].fsurround_center = bSurroundCenter; + g_paintBuffers[SOUND_BUFFER_DRY].fsurround_center = bSurroundCenter; + + // room buffer mixes down to mono or stereo, never to 4 or 5 ch + g_paintBuffers[SOUND_BUFFER_ROOM].fsurround = false; + g_paintBuffers[SOUND_BUFFER_ROOM].fsurround_center = false; + + // speaker buffer mixes to mono + g_paintBuffers[SOUND_BUFFER_SPEAKER].fsurround = false; + g_paintBuffers[SOUND_BUFFER_SPEAKER].fsurround_center = false; + + MIX_SetCurrentPaintbuffer( SOUND_BUFFER_PAINT ); + + return true; +} + +// called before loading samples to mix - cap the mix rate (ie: pitch) so that +// we never overflow the mix copy buffer. + +double MIX_GetMaxRate( double rate, int sampleCount ) +{ + if (rate <= 2.0) + return rate; + + // copybuf_bytes = rate_max * samples_max * samplesize_max + // so: + // rate_max = copybuf_bytes / (samples_max * samplesize_max ) + + double samplesize_max = 4.0; // stereo 16bit samples + double copybuf_bytes = (double)(TEMP_COPY_BUFFER_SIZE * sizeof(portable_samplepair_t)); + double samples_max = (double)(PAINTBUFFER_SIZE); + + double rate_max = copybuf_bytes / (samples_max * samplesize_max); + + // make sure sampleCount is never greater than paintbuffer samples + // (this should have been set up in MIX_PaintChannels) + + Assert (sampleCount <= PAINTBUFFER_SIZE); + + return fpmin( rate, rate_max ); +} + + +// Transfer (endtime - lpaintedtime) stereo samples in pfront out to hardware +// pfront - pointer to stereo paintbuffer - 32 bit samples, interleaved stereo +// lpaintedtime - total number of 32 bit stereo samples previously output to hardware +// endtime - total number of 32 bit stereo samples currently mixed in paintbuffer +void S_TransferStereo16( void *pOutput, const portable_samplepair_t *pfront, int lpaintedtime, int endtime ) +{ + int lpos; + + if ( IsX360() ) + { + // not the right path for 360 + Assert( 0 ); + return; + } + + Assert( pOutput ); + + snd_vol = S_GetMasterVolume()*256; + snd_p = (int *)pfront; + + // get size of output buffer in full samples (LR pairs) + int samplePairCount = g_AudioDevice->DeviceSampleCount() >> 1; + int sampleMask = samplePairCount - 1; + + bool bShouldPlaySound = !cl_movieinfo.IsRecording() && !IsReplayRendering(); + + while ( lpaintedtime < endtime ) + { + // pbuf can hold 16384, 16 bit L/R samplepairs. + // lpaintedtime - where to start painting into dma buffer. + // (modulo size of dma buffer for current position). + // handle recirculating buffer issues + // lpos - samplepair index into dma buffer. First samplepair from paintbuffer to be xfered here. + lpos = lpaintedtime & sampleMask; + + // snd_out is L/R sample index into dma buffer. First L sample from paintbuffer goes here. + snd_out = (short *)pOutput + (lpos<<1); + + // snd_linear_count is number of samplepairs between end of dma buffer and xfer start index. + snd_linear_count = samplePairCount - lpos; + + // clamp snd_linear_count to be only as many samplepairs premixed + if ( snd_linear_count > endtime - lpaintedtime ) + { + // endtime - lpaintedtime = number of premixed sample pairs ready for xfer. + snd_linear_count = endtime - lpaintedtime; + } + + // snd_linear_count is now number of mono 16 bit samples (L and R) to xfer. + snd_linear_count <<= 1; + + // write a linear blast of samples + SND_RecordBuffer(); + if ( bShouldPlaySound ) + { + // transfer 16bit samples from snd_p into snd_out, multiplying each sample by volume. + Snd_WriteLinearBlastStereo16(); + } + + // advance paintbuffer pointer + snd_p += snd_linear_count; + + // advance lpaintedtime by number of samplepairs just xfered. + lpaintedtime += (snd_linear_count>>1); + } +} + +// Transfer contents of main paintbuffer pfront out to +// device. Perform volume multiply on each sample. +void S_TransferPaintBuffer(void *pOutput, const portable_samplepair_t *pfront, int lpaintedtime, int endtime) +{ + int out_idx; // mono sample index + int count; // number of mono samples to output + int out_mask; + int step; + int val; + int nSoundVol; + const int *p; + + if ( IsX360() ) + { + // not the right path for 360 + Assert( 0 ); + return; + } + + Assert( pOutput ); + + p = (const int *) pfront; + + count = ((endtime - lpaintedtime) * g_AudioDevice->DeviceChannels()); + + out_mask = g_AudioDevice->DeviceSampleCount() - 1; + + // 44k: remove old 22k sound support << HISPEED_DMA + // out_idx = ((paintedtime << HISPEED_DMA) * g_AudioDevice->DeviceChannels()) & out_mask; + + out_idx = (lpaintedtime * g_AudioDevice->DeviceChannels()) & out_mask; + + step = 3 - g_AudioDevice->DeviceChannels(); // mono output buffer - step 2, stereo - step 1 + nSoundVol = S_GetMasterVolume()*256; + + if (g_AudioDevice->DeviceSampleBits() == 16) + { + short *out = (short *) pOutput; + while (count--) + { + val = (*p * nSoundVol) >> 8; + p+= step; + val = CLIP(val); + + out[out_idx] = val; + out_idx = (out_idx + 1) & out_mask; + } + } + else if (g_AudioDevice->DeviceSampleBits() == 8) + { + unsigned char *out = (unsigned char *) pOutput; + while (count--) + { + val = (*p * nSoundVol) >> 8; + p+= step; + val = CLIP(val); + + out[out_idx] = (val>>8) + 128; + out_idx = (out_idx + 1) & out_mask; + } + } +} + +/* +=============================================================================== + +CHANNEL MIXING + +=============================================================================== +*/ + + +// free channel so that it may be allocated by the +// next request to play a sound. If sound is a +// word in a sentence, release the sentence. +// Works for static, dynamic, sentence and stream sounds + +void S_FreeChannel(channel_t *ch) +{ + // Don't reenter in here (can happen inside voice code). + if ( ch->flags.m_bIsFreeingChannel ) + return; + ch->flags.m_bIsFreeingChannel = true; + + SND_CloseMouth(ch); + + g_pSoundServices->OnSoundStopped( ch->guid, ch->soundsource, ch->entchannel, ch->sfx->getname() ); + + ch->flags.isSentence = false; +// Msg("End sound %s\n", ch->sfx->getname() ); + + delete ch->pMixer; + ch->pMixer = NULL; + ch->sfx = NULL; + + // zero all data in channel + g_ActiveChannels.Remove( ch ); + Q_memset(ch, 0, sizeof(channel_t)); +} + + +// Mix all channels into active paintbuffers until paintbuffer is full or 'endtime' is reached. +// endtime: time in 44khz samples to mix +// rate: ignore samples which are not natively at this rate (for multipass mixing/filtering) +// if rate == SOUND_ALL_RATES then mix all samples this pass +// flags: if SOUND_MIX_DRY, then mix only samples with channel flagged as 'dry' +// outputRate: target mix rate for all samples. Note, if outputRate = SOUND_DMA_SPEED, then +// this routine will fill the paintbuffer to endtime. Otherwise, fewer samples are mixed. +// if (endtime - paintedtime) is not aligned on boundaries of 4, +// we'll miss data if outputRate < SOUND_DMA_SPEED! +void MIX_MixChannelsToPaintbuffer( CChannelList &list, int endtime, int flags, int rate, int outputRate ) +{ + VPROF( "MixChannelsToPaintbuffer" ); + int i; + int sampleCount; + + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s c:%d %d/%d", __FUNCTION__, list.Count(), rate, outputRate ); + + // mix each channel into paintbuffer + // validate parameters + Assert( outputRate <= SOUND_DMA_SPEED ); + Assert( !((endtime - g_paintedtime) & 0x3) || (outputRate == SOUND_DMA_SPEED) ); // make sure we're not discarding data + + // 44k: try to mix this many samples at outputRate + sampleCount = ( endtime - g_paintedtime ) / ( SOUND_DMA_SPEED / outputRate ); + if ( sampleCount <= 0 ) + return; + + // Apply a global pitch shift if we're playing back a time-scaled replay + float flGlobalPitchScale = 1.0f; + +#if defined( REPLAY_ENABLED ) + extern IDemoPlayer *g_pReplayDemoPlayer; + if ( demoplayer->IsPlayingBack() && demoplayer == g_pReplayDemoPlayer ) + { + // adjust time scale if playing back demo + flGlobalPitchScale = demoplayer->GetPlaybackTimeScale(); + } +#endif + + for ( i = list.Count(); --i >= 0; ) + { + channel_t *ch = list.GetChannel( i ); + Assert( ch->sfx ); + // must never have a 'dry' and 'speaker' set - causes double mixing & double data reading + Assert ( !( ( ch->flags.bdry && ch->flags.bSpeaker ) || ( ch->flags.bdry && ch->special_dsp != 0 ) ) ); + + // if mixing with SOUND_MIX_DRY flag, ignore (don't even load) all channels not flagged as 'dry' + if ( flags == SOUND_MIX_DRY ) + { + if ( !ch->flags.bdry ) + continue; + } + + // if mixing with SOUND_MIX_WET flag, ignore (don't even load) all channels flagged as 'dry' or 'speaker' + if ( flags == SOUND_MIX_WET ) + { + if ( ch->flags.bdry || ch->flags.bSpeaker || ch->special_dsp != 0 ) + continue; + } + + // if mixing with SOUND_MIX_SPEAKER flag, ignore (don't even load) all channels not flagged as 'speaker' + if ( flags == SOUND_MIX_SPEAKER ) + { + if ( !ch->flags.bSpeaker ) + continue; + } + + // if mixing with SOUND_MIX_SPEAKER flag, ignore (don't even load) all channels not flagged as 'speaker' + if ( flags == SOUND_MIX_SPECIAL_DSP ) + { + if ( ch->special_dsp == 0 ) + continue; + } + + // multipass mixing - only mix samples of specified sample rate + switch ( rate ) + { + case SOUND_11k: + case SOUND_22k: + case SOUND_44k: + if ( rate != ch->sfx->pSource->SampleRate() ) + continue; + break; + default: + case SOUND_ALL_RATES: + break; + } + + // Tracker 20771, if breen is speaking through the monitor, the client doesn't have an entity + // for the "soundsource" but we still need the lipsync to pause if the game is paused. Therefore + // I changed SND_IsMouth to look for any .wav on any channels which has sentence data + bool bIsMouth = SND_IsMouth(ch); + bool bShouldPause = IsX360() ? !ch->sfx->m_bIsUISound : bIsMouth; + + // Tracker 14637: Pausing the game pauses voice sounds, but not other sounds... + if ( bShouldPause && g_pSoundServices->IsGamePaused() ) + { + continue; + } + + if ( bIsMouth ) + { + if ( ( ch->soundsource == SOUND_FROM_UI_PANEL ) || entitylist->GetClientEntity(ch->soundsource) || + ( ch->flags.bSpeaker && entitylist->GetClientEntity( ch->speakerentity ) ) ) + { + // UNDONE: recode this as a member function of CAudioMixer + SND_MoveMouth8(ch, ch->sfx->pSource, sampleCount); + } + } + + // mix channel to all active paintbuffers: + // mix 'dry' sounds only to dry paintbuffer. + // mix 'speaker' sounds only to speaker paintbuffer. + // mix all other sounds between room, facing & facingaway paintbuffers + // NOTE: must be called once per channel only - consecutive calls retrieve additional data. + float flPitch = ch->pitch; + ch->pitch *= flGlobalPitchScale; + + if (list.IsQuashed(i)) + { + // If the sound has been silenced as a performance heuristic, quash it. + ch->pMixer->SkipSamples( ch, sampleCount, outputRate, 0 ); + // DevMsg("Quashed channel %d (%s)\n", i, ch->sfx->GetFileName()); + } + else + { + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "MixDataToDevice" ); + ch->pMixer->MixDataToDevice( g_AudioDevice, ch, sampleCount, outputRate, 0 ); + } + + // restore to original pitch settings + ch->pitch = flPitch; + + if ( !ch->pMixer->ShouldContinueMixing() ) + { + S_FreeChannel( ch ); + list.RemoveChannelFromList(i); + } + if ( (ch->nFreeChannelAtSampleTime > 0 && (int)ch->nFreeChannelAtSampleTime <= endtime) ) + { + S_FreeChannel( ch ); + list.RemoveChannelFromList(i); + } + } +} + +// pass in index -1...count+2, return pointer to source sample in either paintbuffer or delay buffer +inline portable_samplepair_t * S_GetNextpFilter(int i, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem) +{ + // The delay buffer is assumed to precede the paintbuffer by 6 duplicated samples + if (i == -1) + return (&(pfiltermem[0])); + if (i == 0) + return (&(pfiltermem[1])); + if (i == 1) + return (&(pfiltermem[2])); + + // return from paintbuffer, where samples are doubled. + // even samples are to be replaced with interpolated value. + + return (&(pbuffer[(i-2)*2 + 1])); +} + +// pass forward over passed in buffer and cubic interpolate all odd samples +// pbuffer: buffer to filter (in place) +// prevfilter: filter memory. NOTE: this must match the filtertype ie: filtercubic[] for FILTERTYPE_CUBIC +// if NULL then perform no filtering. UNDONE: should have a filter memory array type +// count: how many samples to upsample. will become count*2 samples in buffer, in place. + +void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) +{ + +// implement cubic interpolation on 2x upsampled buffer. Effectively delays buffer contents by 2 samples. +// pbuffer: contains samples at 0, 2, 4, 6... +// temppaintbuffer is temp buffer, of same or larger size than a paintbuffer, used to store processed values +// count: number of samples to process in buffer ie: how many samples at 0, 2, 4, 6... + +// finpos is the fractional, inpos the integer part. +// finpos = 0.5 for upsampling by 2x +// inpos is the position of the sample + +// xm1 = x [inpos - 1]; +// x0 = x [inpos + 0]; +// x1 = x [inpos + 1]; +// x2 = x [inpos + 2]; +// a = (3 * (x0-x1) - xm1 + x2) / 2; +// b = 2*x1 + xm1 - (5*x0 + x2) / 2; +// c = (x1 - xm1) / 2; +// y [outpos] = (((a * finpos) + b) * finpos + c) * finpos + x0; + + int i, upCount = count << 1; + int a, b, c; + int xm1, x0, x1, x2; + portable_samplepair_t *psamp0; + portable_samplepair_t *psamp1; + portable_samplepair_t *psamp2; + portable_samplepair_t *psamp3; + int outpos = 0; + + Assert (upCount <= PAINTBUFFER_SIZE); + + // pfiltermem holds 6 samples from previous buffer pass + + // process 'count' samples + + for ( i = 0; i < count; i++) + { + + // get source sample pointer + + psamp0 = S_GetNextpFilter(i-1, pbuffer, pfiltermem); + psamp1 = S_GetNextpFilter(i, pbuffer, pfiltermem); + psamp2 = S_GetNextpFilter(i+1, pbuffer, pfiltermem); + psamp3 = S_GetNextpFilter(i+2, pbuffer, pfiltermem); + + // write out original sample to interpolation buffer + + g_temppaintbuffer[outpos++] = *psamp1; + + // get all left samples for interpolation window + + xm1 = psamp0->left; + x0 = psamp1->left; + x1 = psamp2->left; + x2 = psamp3->left; + + // interpolate + + a = (3 * (x0-x1) - xm1 + x2) / 2; + b = 2*x1 + xm1 - (5*x0 + x2) / 2; + c = (x1 - xm1) / 2; + + // write out interpolated sample + + g_temppaintbuffer[outpos].left = a/8 + b/4 + c/2 + x0; + + // get all right samples for window + + xm1 = psamp0->right; + x0 = psamp1->right; + x1 = psamp2->right; + x2 = psamp3->right; + + // interpolate + + a = (3 * (x0-x1) - xm1 + x2) / 2; + b = 2*x1 + xm1 - (5*x0 + x2) / 2; + c = (x1 - xm1) / 2; + + // write out interpolated sample, increment output counter + g_temppaintbuffer[outpos++].right = a/8 + b/4 + c/2 + x0; + + Assert( outpos <= TEMP_COPY_BUFFER_SIZE ); + } + + Assert(cfltmem >= 3); + + // save last 3 samples from paintbuffer + + pfiltermem[0] = pbuffer[upCount - 5]; + pfiltermem[1] = pbuffer[upCount - 3]; + pfiltermem[2] = pbuffer[upCount - 1]; + + // copy temppaintbuffer back into paintbuffer + + for (i = 0; i < upCount; i++) + pbuffer[i] = g_temppaintbuffer[i]; +} + +// pass forward over passed in buffer and linearly interpolate all odd samples +// pbuffer: buffer to filter (in place) +// prevfilter: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR +// if NULL then perform no filtering. +// count: how many samples to upsample. will become count*2 samples in buffer, in place. + +void S_Interpolate2xLinear( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) +{ + int i, upCount = count<<1; + + Assert (upCount <= PAINTBUFFER_SIZE); + Assert (cfltmem >= 1); + + // use interpolation value from previous mix + + pbuffer[0].left = (pfiltermem->left + pbuffer[0].left) >> 1; + pbuffer[0].right = (pfiltermem->right + pbuffer[0].right) >> 1; + + for ( i = 2; i < upCount; i+=2) + { + // use linear interpolation for upsampling + + pbuffer[i].left = (pbuffer[i].left + pbuffer[i-1].left) >> 1; + pbuffer[i].right = (pbuffer[i].right + pbuffer[i-1].right) >> 1; + } + + // save last value to be played out in buffer + + *pfiltermem = pbuffer[upCount - 1]; +} + + +// Optimized routine. 2.27X faster than the above routine +void S_Interpolate2xLinear_2( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem ) +{ + Assert (cfltmem >= 1); + + int sample = count-1; + int end = (count*2)-1; + portable_samplepair_t *pwrite = &pbuffer[end]; + portable_samplepair_t *pread = &pbuffer[sample]; + portable_samplepair_t last = pread[0]; + pread--; + + // PERFORMANCE: Unroll the loop 8 times. This improves speed quite a bit + for ( ;sample >= 8; sample -= 8 ) + { + pwrite[0] = last; + pwrite[-1].left = (pread[0].left + last.left)>>1; + pwrite[-1].right = (pread[0].right + last.right)>>1; + last = pread[0]; + + pwrite[-2] = last; + pwrite[-3].left = (pread[-1].left + last.left)>>1; + pwrite[-3].right = (pread[-1].right + last.right)>>1; + last = pread[-1]; + + pwrite[-4] = last; + pwrite[-5].left = (pread[-2].left + last.left)>>1; + pwrite[-5].right = (pread[-2].right + last.right)>>1; + last = pread[-2]; + + pwrite[-6] = last; + pwrite[-7].left = (pread[-3].left + last.left)>>1; + pwrite[-7].right = (pread[-3].right + last.right)>>1; + last = pread[-3]; + + pwrite[-8] = last; + pwrite[-9].left = (pread[-4].left + last.left)>>1; + pwrite[-9].right = (pread[-4].right + last.right)>>1; + last = pread[-4]; + + pwrite[-10] = last; + pwrite[-11].left = (pread[-5].left + last.left)>>1; + pwrite[-11].right = (pread[-5].right + last.right)>>1; + last = pread[-5]; + + pwrite[-12] = last; + pwrite[-13].left = (pread[-6].left + last.left)>>1; + pwrite[-13].right = (pread[-6].right + last.right)>>1; + last = pread[-6]; + + pwrite[-14] = last; + pwrite[-15].left = (pread[-7].left + last.left)>>1; + pwrite[-15].right = (pread[-7].right + last.right)>>1; + last = pread[-7]; + + pread -= 8; + pwrite -= 16; + } + while ( pread >= pbuffer ) + { + pwrite[0] = last; + pwrite[-1].left = (pread[0].left + last.left)>>1; + pwrite[-1].right = (pread[0].right + last.right)>>1; + last = pread[0]; + pread--; + pwrite-=2; + } + pbuffer[1] = last; + pbuffer[0].left = (pfiltermem->left + last.left) >> 1; + pbuffer[0].right = (pfiltermem->right + last.right) >> 1; + *pfiltermem = pbuffer[end]; +} + + +// upsample by 2x, optionally using interpolation +// count: how many samples to upsample. will become count*2 samples in buffer, in place. +// pbuffer: buffer to upsample into (in place) +// pfiltermem: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR +// if NULL then perform no filtering. +// cfltmem: max number of sample pairs filter can use +// filtertype: FILTERTYPE_NONE, _LINEAR, _CUBIC etc. Must match prevfilter. +void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int filtertype ) +{ + // JAY: Optimized this routine. Test then remove old routine. + // NOTE: Has been proven equivalent by comparing output. + if ( filtertype == FILTERTYPE_LINEAR ) + { + S_Interpolate2xLinear_2( count, pbuffer, pfiltermem, cfltmem ); + return; + } + int i, j, upCount = count<<1; + + // reverse through buffer, duplicating contents for 'count' samples + + for (i = upCount - 1, j = count - 1; j >= 0; i-=2, j--) + { + pbuffer[i] = pbuffer[j]; + pbuffer[i-1] = pbuffer[j]; + } + + // pass forward through buffer, interpolate all even slots + + switch (filtertype) + { + default: + break; + case FILTERTYPE_LINEAR: + S_Interpolate2xLinear(pbuffer, pfiltermem, cfltmem, count); + break; + case FILTERTYPE_CUBIC: + S_Interpolate2xCubic(pbuffer, pfiltermem, cfltmem, count); + break; + } +} + +//=============================================================================== +// PAINTBUFFER ROUTINES +//=============================================================================== + + +// Set current paintbuffer to pbuf. +// The set paintbuffer is used by all subsequent mixing, upsampling and dsp routines. +// Also sets the rear paintbuffer if paintbuffer has fsurround true. +// (otherwise, rearpaintbuffer is NULL) + +void MIX_SetCurrentPaintbuffer(int ipaintbuffer) +{ + // set front and rear paintbuffer + + Assert(ipaintbuffer < g_paintBuffers.Count()); + + g_curpaintbuffer = g_paintBuffers[ipaintbuffer].pbuf; + + if ( g_paintBuffers[ipaintbuffer].fsurround ) + { + g_currearpaintbuffer = g_paintBuffers[ipaintbuffer].pbufrear; + + g_curcenterpaintbuffer = NULL; + + if ( g_paintBuffers[ipaintbuffer].fsurround_center ) + g_curcenterpaintbuffer = g_paintBuffers[ipaintbuffer].pbufcenter; + } + else + { + g_currearpaintbuffer = NULL; + g_curcenterpaintbuffer = NULL; + } + + Assert(g_curpaintbuffer != NULL); +} + +// return index to current paintbuffer + +int MIX_GetCurrentPaintbufferIndex( void ) +{ + int i; + + for ( i = 0; i < g_paintBuffers.Count(); i++ ) + { + if (g_curpaintbuffer == g_paintBuffers[i].pbuf) + return i; + } + + return 0; +} + + +// return pointer to current paintbuffer struct + +paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void ) +{ + int ipaint = MIX_GetCurrentPaintbufferIndex(); + + Assert( ipaint < g_paintBuffers.Count() ); + + return &g_paintBuffers[ipaint]; +} + + +// return pointer to front paintbuffer pbuf, given index + +inline portable_samplepair_t *MIX_GetPFrontFromIPaint(int ipaintbuffer) +{ + return g_paintBuffers[ipaintbuffer].pbuf; +} + +paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaintbuffer ) +{ + Assert( ipaintbuffer < g_paintBuffers.Count() ); + + return &g_paintBuffers[ipaintbuffer]; +} + + +// return pointer to rear buffer, given index. +// returns null if fsurround is false; + +inline portable_samplepair_t *MIX_GetPRearFromIPaint(int ipaintbuffer) +{ + if ( g_paintBuffers[ipaintbuffer].fsurround ) + return g_paintBuffers[ipaintbuffer].pbufrear; + + return NULL; +} + +// return pointer to center buffer, given index. +// returns null if fsurround_center is false; + +inline portable_samplepair_t *MIX_GetPCenterFromIPaint(int ipaintbuffer) +{ + if ( g_paintBuffers[ipaintbuffer].fsurround_center ) + return g_paintBuffers[ipaintbuffer].pbufcenter; + + return NULL; +} + +// return index to paintbuffer, given buffer pointer + +inline int MIX_GetIPaintFromPFront( portable_samplepair_t *pbuf ) +{ + int i; + + for ( i = 0; i < g_paintBuffers.Count(); i++ ) + { + if ( pbuf == g_paintBuffers[i].pbuf ) + return i; + } + + return 0; +} + +// return pointer to paintbuffer struct, given ptr to buffer data + +inline paintbuffer_t *MIX_GetPPaintFromPFront( portable_samplepair_t *pbuf ) +{ + int i; + i = MIX_GetIPaintFromPFront( pbuf ); + + return &g_paintBuffers[i]; +} + + +// up convert mono buffer to full surround + +inline void MIX_ConvertBufferToSurround( int ipaintbuffer ) +{ + paintbuffer_t *ppaint = &g_paintBuffers[ipaintbuffer]; + + // duplicate channel data as needed + + if ( g_AudioDevice->IsSurround() ) + { + // set buffer flags + + ppaint->fsurround = g_AudioDevice->IsSurround(); + ppaint->fsurround_center = g_AudioDevice->IsSurroundCenter(); + + portable_samplepair_t *pfront = MIX_GetPFrontFromIPaint( ipaintbuffer ); + portable_samplepair_t *prear = MIX_GetPRearFromIPaint( ipaintbuffer ); + portable_samplepair_t *pcenter = MIX_GetPCenterFromIPaint( ipaintbuffer ); + + // copy front to rear + Q_memcpy(prear, pfront, sizeof(portable_samplepair_t) * PAINTBUFFER_SIZE); + + // copy front to center + if ( g_AudioDevice->IsSurroundCenter() ) + Q_memcpy(pcenter, pfront, sizeof(portable_samplepair_t) * PAINTBUFFER_SIZE); + } +} + +// Activate a paintbuffer. All active paintbuffers are mixed in parallel within +// MIX_MixChannelsToPaintbuffer, according to flags + +inline void MIX_ActivatePaintbuffer(int ipaintbuffer) +{ + Assert( ipaintbuffer < g_paintBuffers.Count() ); + g_paintBuffers[ipaintbuffer].factive = true; +} + +// Don't mix into this paintbuffer + +inline void MIX_DeactivatePaintbuffer(int ipaintbuffer) +{ + Assert( ipaintbuffer < g_paintBuffers.Count() ); + g_paintBuffers[ipaintbuffer].factive = false; +} + +// Don't mix into any paintbuffers + +inline void MIX_DeactivateAllPaintbuffers(void) +{ + int i; + for ( i = 0; i < g_paintBuffers.Count(); i++ ) + g_paintBuffers[i].factive = false; +} + +// set upsampling filter indexes back to 0 + +inline void MIX_ResetPaintbufferFilterCounters( void ) + +{ + int i; + for ( i = 0; i < g_paintBuffers.Count(); i++ ) + g_paintBuffers[i].ifilter = 0; +} + +inline void MIX_ResetPaintbufferFilterCounter( int ipaintbuffer ) +{ + Assert ( ipaintbuffer < g_paintBuffers.Count() ); + g_paintBuffers[ipaintbuffer].ifilter = 0; +} + +// Change paintbuffer's flags + +inline void MIX_SetPaintbufferFlags(int ipaintbuffer, int flags) +{ + Assert( ipaintbuffer < g_paintBuffers.Count() ); + g_paintBuffers[ipaintbuffer].flags = flags; +} + + +// zero out all paintbuffers + +void MIX_ClearAllPaintBuffers( int SampleCount, bool clearFilters ) +{ + // g_paintBuffers can be NULL with -nosound + if ( g_paintBuffers.Count() <= 0 ) + { + return; + } + + int i; + int count = min(SampleCount, PAINTBUFFER_SIZE); + + // zero out all paintbuffer data (ignore sampleCount) + + for ( i = 0; i < g_paintBuffers.Count(); i++ ) + { + if (g_paintBuffers[i].pbuf != NULL) + Q_memset(g_paintBuffers[i].pbuf, 0, (count+1) * sizeof(portable_samplepair_t)); + + if (g_paintBuffers[i].pbufrear != NULL) + Q_memset(g_paintBuffers[i].pbufrear, 0, (count+1) * sizeof(portable_samplepair_t)); + + if (g_paintBuffers[i].pbufcenter != NULL) + Q_memset(g_paintBuffers[i].pbufcenter, 0, (count+1) * sizeof(portable_samplepair_t)); + + if ( clearFilters ) + { + Q_memset( g_paintBuffers[i].fltmem, 0, sizeof(g_paintBuffers[i].fltmem) ); + Q_memset( g_paintBuffers[i].fltmemrear, 0, sizeof(g_paintBuffers[i].fltmemrear) ); + Q_memset( g_paintBuffers[i].fltmemcenter, 0, sizeof(g_paintBuffers[i].fltmemcenter) ); + } + } + + if ( clearFilters ) + { + MIX_ResetPaintbufferFilterCounters(); + } +} + +#define SWAP(a,b,t) {(t) = (a); (a) = (b); (b) = (t);} +#define AVG(a,b) (((a) + (b)) >> 1 ) +#define AVG4(a,b,c,d) (((a) + (b) + (c) + (d)) >> 2 ) + +// Synthesize center channel from left/right values (average). +// Currently just averages, but could actually remove +// the center signal from the l/r channels... + +inline void MIX_CenterFromLeftRight( int *pl, int *pr, int *pc ) +{ + int l = *pl; + int r = *pr; + int c = 0; + + + c = (l + r) / 2; + +/* + l = l - c/2; + r = r - c/2; + + if (l < 0) + { + l = 0; + r += (-l); + c += (-l); + } + else if (r < 0) + { + r = 0; + l += (-r); + c += (-r); + } +*/ + *pc = c; +// *pl = l; +// *pr = r; +} + +// mixes pbuf1 + pbuf2 into pbuf3, count samples +// fgain is output gain 0-1.0 +// NOTE: pbuf3 may equal pbuf1 or pbuf2! + +// mixing algorithms: + +// destination 2ch: +// pb1 2ch + pb2 2ch -> pb3 2ch +// pb1 (4ch->2ch) + pb2 2ch -> pb3 2ch +// pb1 2ch + pb2 (4ch->2ch) -> pb3 2ch +// pb1 (4ch->2ch) + pb2 (4ch->2ch) -> pb3 2ch + +// destination 4ch: +// pb1 4ch + pb2 4ch -> pb3 4ch +// pb1 (2ch->4ch) + pb2 4ch -> pb3 4ch +// pb1 4ch + pb2 (2ch->4ch) -> pb3 4ch +// pb1 (2ch->4ch) + pb2 (2ch->4ch) -> pb3 4ch + +// if all buffers are 4 or 5 ch surround, mix rear & center channels into ibuf3 as well. + +// NOTE: for performance, conversion and mixing are done in a single pass instead of +// a two pass channel convert + mix scheme. + +void MIX_MixPaintbuffers(int ibuf1, int ibuf2, int ibuf3, int count, float fgain_out) +{ + VPROF("Mixpaintbuffers"); + int i; + portable_samplepair_t *pbuf1, *pbuf2, *pbuf3, *pbuft; + portable_samplepair_t *pbufrear1, *pbufrear2, *pbufrear3, *pbufreart; + portable_samplepair_t *pbufcenter1, *pbufcenter2, *pbufcenter3, *pbufcentert; + int cchan1, cchan2, cchan3, cchant; + int xl,xr; + int l,r,l2,r2,c, c2; + int gain_out; + + gain_out = 256 * fgain_out; + + Assert (count <= PAINTBUFFER_SIZE); + Assert (ibuf1 < g_paintBuffers.Count()); + Assert (ibuf2 < g_paintBuffers.Count()); + Assert (ibuf3 < g_paintBuffers.Count()); + + pbuf1 = g_paintBuffers[ibuf1].pbuf; + pbuf2 = g_paintBuffers[ibuf2].pbuf; + pbuf3 = g_paintBuffers[ibuf3].pbuf; + + pbufrear1 = g_paintBuffers[ibuf1].pbufrear; + pbufrear2 = g_paintBuffers[ibuf2].pbufrear; + pbufrear3 = g_paintBuffers[ibuf3].pbufrear; + + pbufcenter1 = g_paintBuffers[ibuf1].pbufcenter; + pbufcenter2 = g_paintBuffers[ibuf2].pbufcenter; + pbufcenter3 = g_paintBuffers[ibuf3].pbufcenter; + + cchan1 = 2 + (g_paintBuffers[ibuf1].fsurround ? 2 : 0) + (g_paintBuffers[ibuf1].fsurround_center ? 1 : 0); + cchan2 = 2 + (g_paintBuffers[ibuf2].fsurround ? 2 : 0) + (g_paintBuffers[ibuf2].fsurround_center ? 1 : 0); + cchan3 = 2 + (g_paintBuffers[ibuf3].fsurround ? 2 : 0) + (g_paintBuffers[ibuf3].fsurround_center ? 1 : 0); + + // make sure pbuf1 always has fewer or equal channels than pbuf2 + // NOTE: pbuf3 may equal pbuf1 or pbuf2! + + if ( cchan2 < cchan1 ) + { + SWAP( cchan1, cchan2, cchant ); + SWAP( pbuf1, pbuf2, pbuft ); + SWAP( pbufrear1, pbufrear2, pbufreart ); + SWAP( pbufcenter1, pbufcenter2, pbufcentert); + } + + + // UNDONE: implement fast mixing routines for each of the following sections + + // destination buffer stereo - average n chans down to stereo + + if ( cchan3 == 2 ) + { + // destination 2ch: + // pb1 2ch + pb2 2ch -> pb3 2ch + // pb1 2ch + pb2 (4ch->2ch) -> pb3 2ch + // pb1 (4ch->2ch) + pb2 (4ch->2ch) -> pb3 2ch + + if ( cchan1 == 2 && cchan2 == 2 ) + { + // mix front channels + + for (i = 0; i < count; i++) + { + pbuf3[i].left = pbuf1[i].left + pbuf2[i].left; + pbuf3[i].right = pbuf1[i].right + pbuf2[i].right; + } + goto gain2ch; + } + + if ( cchan1 == 2 && cchan2 == 4 ) + { + // avg rear chan l/r + + for (i = 0; i < count; i++) + { + pbuf3[i].left = pbuf1[i].left + AVG( pbuf2[i].left, pbufrear2[i].left ); + pbuf3[i].right = pbuf1[i].right + AVG( pbuf2[i].right, pbufrear2[i].right ); + } + goto gain2ch; + } + + if ( cchan1 == 4 && cchan2 == 4 ) + { + // avg rear chan l/r + + for (i = 0; i < count; i++) + { + pbuf3[i].left = AVG( pbuf1[i].left, pbufrear1[i].left) + AVG( pbuf2[i].left, pbufrear2[i].left ); + pbuf3[i].right = AVG( pbuf1[i].right, pbufrear1[i].right) + AVG( pbuf2[i].right, pbufrear2[i].right ); + } + goto gain2ch; + } + + if ( cchan1 == 2 && cchan2 == 5 ) + { + // avg rear chan l/r + center split into left/right + + for (i = 0; i < count; i++) + { + l = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); + r = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); + + pbuf3[i].left = pbuf1[i].left + AVG( l, pbufrear2[i].left ); + pbuf3[i].right = pbuf1[i].right + AVG( r, pbufrear2[i].right ); + } + goto gain2ch; + } + + if ( cchan1 == 4 && cchan2 == 5) + { + for (i = 0; i < count; i++) + { + l = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); + r = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); + + pbuf3[i].left = AVG( pbuf1[i].left, pbufrear1[i].left) + AVG( l, pbufrear2[i].left ); + pbuf3[i].right = AVG( pbuf1[i].right, pbufrear1[i].right) + AVG( r, pbufrear2[i].right ); + } + goto gain2ch; + } + + if ( cchan1 == 5 && cchan2 == 5) + { + for (i = 0; i < count; i++) + { + l = pbuf1[i].left + ((pbufcenter1[i].left) >> 1); + r = pbuf1[i].right + ((pbufcenter1[i].left) >> 1); + + l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); + r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); + + pbuf3[i].left = AVG( l, pbufrear1[i].left) + AVG( l2, pbufrear2[i].left ); + pbuf3[i].right = AVG( r, pbufrear1[i].right) + AVG( r2, pbufrear2[i].right ); + } goto gain2ch; + } + + } + + // destination buffer quad - duplicate n chans up to quad + + if ( cchan3 == 4 ) + { + + // pb1 4ch + pb2 4ch -> pb3 4ch + // pb1 (2ch->4ch) + pb2 4ch -> pb3 4ch + // pb1 (2ch->4ch) + pb2 (2ch->4ch) -> pb3 4ch + + if ( cchan1 == 4 && cchan2 == 4) + { + // mix front -> front, rear -> rear + + for (i = 0; i < count; i++) + { + pbuf3[i].left = pbuf1[i].left + pbuf2[i].left; + pbuf3[i].right = pbuf1[i].right + pbuf2[i].right; + + pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; + pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; + } + goto gain4ch; + } + + if ( cchan1 == 2 && cchan2 == 4) + { + + for (i = 0; i < count; i++) + { + // split 2 ch left -> front left, rear left + // split 2 ch right -> front right, rear right + + xl = pbuf1[i].left; + xr = pbuf1[i].right; + + pbuf3[i].left = xl + pbuf2[i].left; + pbuf3[i].right = xr + pbuf2[i].right; + + pbufrear3[i].left = xl + pbufrear2[i].left; + pbufrear3[i].right = xr + pbufrear2[i].right; + } + goto gain4ch; + } + + if ( cchan1 == 2 && cchan2 == 2) + { + // mix l,r, split into front l, front r + + for (i = 0; i < count; i++) + { + xl = pbuf1[i].left + pbuf2[i].left; + xr = pbuf1[i].right + pbuf2[i].right; + + pbufrear3[i].left = pbuf3[i].left = xl; + pbufrear3[i].right = pbuf3[i].right = xr; + } + goto gain4ch; + } + + + if ( cchan1 == 2 && cchan2 == 5 ) + { + for (i = 0; i < count; i++) + { + // split center of chan2 into left/right + + l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); + r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); + + xl = pbuf1[i].left; + xr = pbuf1[i].right; + + pbuf3[i].left = xl + l2; + pbuf3[i].right = xr + r2; + + pbufrear3[i].left = xl + pbufrear2[i].left; + pbufrear3[i].right = xr + pbufrear2[i].right; + } + goto gain4ch; + } + + if ( cchan1 == 4 && cchan2 == 5) + { + + for (i = 0; i < count; i++) + { + l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); + r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); + + pbuf3[i].left = pbuf1[i].left + l2; + pbuf3[i].right = pbuf1[i].right + r2; + + pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; + pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; + } + goto gain4ch; + } + + if ( cchan1 == 5 && cchan2 == 5 ) + { + for (i = 0; i < count; i++) + { + l = pbuf1[i].left + ((pbufcenter1[i].left) >> 1); + r = pbuf1[i].right + ((pbufcenter1[i].left) >> 1); + + l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); + r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); + + pbuf3[i].left = l + l2; + pbuf3[i].right = r + r2; + + pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; + pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; + } + goto gain4ch; + } + } + + // 5 channel destination + + if (cchan3 == 5) + { + // up convert from 2 or 4 ch buffer to 5 ch buffer: + // center channel is synthesized from front left, front right + + if (cchan1 == 2 && cchan2 == 2) + { + for (i = 0; i < count; i++) + { + // split 2 ch left -> front left, center, rear left + // split 2 ch right -> front right, center, rear right + + l = pbuf1[i].left; + r = pbuf1[i].right; + + MIX_CenterFromLeftRight(&l, &r, &c); + + l2 = pbuf2[i].left; + r2 = pbuf2[i].right; + + MIX_CenterFromLeftRight(&l2, &r2, &c2); + + pbuf3[i].left = l + l2; + pbuf3[i].right = r + r2; + + pbufrear3[i].left = pbuf1[i].left + pbuf2[i].left; + pbufrear3[i].right = pbuf1[i].right + pbuf2[i].right; + + pbufcenter3[i].left = c + c2; + } + goto gain5ch; + } + + if (cchan1 == 2 && cchan2 == 4) + { + for (i = 0; i < count; i++) + { + l = pbuf1[i].left; + r = pbuf1[i].right; + + MIX_CenterFromLeftRight(&l, &r, &c); + + l2 = pbuf2[i].left; + r2 = pbuf2[i].right; + + MIX_CenterFromLeftRight(&l2, &r2, &c2); + + pbuf3[i].left = l + l2; + pbuf3[i].right = r + r2; + + pbufrear3[i].left = pbuf1[i].left + pbufrear2[i].left; + pbufrear3[i].right = pbuf1[i].right + pbufrear2[i].right; + + pbufcenter3[i].left = c + c2; + } + goto gain5ch; + } + + if (cchan1 == 2 && cchan2 == 5) + { + for (i = 0; i < count; i++) + { + l = pbuf1[i].left; + r = pbuf1[i].right; + + MIX_CenterFromLeftRight(&l, &r, &c); + + pbuf3[i].left = l + pbuf2[i].left; + pbuf3[i].right = r + pbuf2[i].right; + + pbufrear3[i].left = pbuf1[i].left + pbufrear2[i].left; + pbufrear3[i].right = pbuf1[i].right + pbufrear2[i].right; + + pbufcenter3[i].left = c + pbufcenter2[i].left; + } + goto gain5ch; + } + + if (cchan1 == 4 && cchan2 == 4) + { + for (i = 0; i < count; i++) + { + l = pbuf1[i].left; + r = pbuf1[i].right; + + MIX_CenterFromLeftRight(&l, &r, &c); + + l2 = pbuf2[i].left; + r2 = pbuf2[i].right; + + MIX_CenterFromLeftRight(&l2, &r2, &c2); + + pbuf3[i].left = l + l2; + pbuf3[i].right = r + r2; + + pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; + pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; + + pbufcenter3[i].left = c + c2; + } + goto gain5ch; + } + + + if (cchan1 == 4 && cchan2 == 5) + { + for (i = 0; i < count; i++) + { + l = pbuf1[i].left; + r = pbuf1[i].right; + + MIX_CenterFromLeftRight(&l, &r, &c); + + pbuf3[i].left = l + pbuf2[i].left; + pbuf3[i].right = r + pbuf2[i].right; + + pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; + pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; + + pbufcenter3[i].left = c + pbufcenter2[i].left; + } + goto gain5ch; + } + + if ( cchan2 == 5 && cchan1 == 5 ) + { + for (i = 0; i < count; i++) + { + pbuf3[i].left = pbuf1[i].left + pbuf2[i].left; + pbuf3[i].right = pbuf1[i].right + pbuf2[i].right; + pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; + pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; + pbufcenter3[i].left = pbufcenter1[i].left + pbufcenter2[i].left; + } + goto gain5ch; + } + } + +gain2ch: + if ( gain_out == 256) // KDB: perf + return; + + for (i = 0; i < count; i++) + { + pbuf3[i].left = (pbuf3[i].left * gain_out) >> 8; + pbuf3[i].right = (pbuf3[i].right * gain_out) >> 8; + } + return; + +gain4ch: + if ( gain_out == 256) // KDB: perf + return; + + for (i = 0; i < count; i++) + { + pbuf3[i].left = (pbuf3[i].left * gain_out) >> 8; + pbuf3[i].right = (pbuf3[i].right * gain_out) >> 8; + pbufrear3[i].left = (pbufrear3[i].left * gain_out) >> 8; + pbufrear3[i].right = (pbufrear3[i].right * gain_out) >> 8; + } + return; + +gain5ch: + if ( gain_out == 256) // KDB: perf + return; + + for (i = 0; i < count; i++) + { + pbuf3[i].left = (pbuf3[i].left * gain_out) >> 8; + pbuf3[i].right = (pbuf3[i].right * gain_out) >> 8; + pbufrear3[i].left = (pbufrear3[i].left * gain_out) >> 8; + pbufrear3[i].right = (pbufrear3[i].right * gain_out) >> 8; + pbufcenter3[i].left = (pbufcenter3[i].left * gain_out) >> 8; + } + return; +} + +// multiply all values in paintbuffer by fgain + +void MIX_ScalePaintBuffer( int bufferIndex, int count, float fgain ) +{ + portable_samplepair_t *pbuf = g_paintBuffers[bufferIndex].pbuf; + portable_samplepair_t *pbufrear = g_paintBuffers[bufferIndex].pbufrear; + portable_samplepair_t *pbufcenter = g_paintBuffers[bufferIndex].pbufcenter; + + int gain = 256 * fgain; + int i; + + if (gain == 256) + return; + + if ( !g_paintBuffers[bufferIndex].fsurround ) + { + for (i = 0; i < count; i++) + { + pbuf[i].left = (pbuf[i].left * gain) >> 8; + pbuf[i].right = (pbuf[i].right * gain) >> 8; + } + } + else + { + for (i = 0; i < count; i++) + { + pbuf[i].left = (pbuf[i].left * gain) >> 8; + pbuf[i].right = (pbuf[i].right * gain) >> 8; + pbufrear[i].left = (pbufrear[i].left * gain) >> 8; + pbufrear[i].right = (pbufrear[i].right * gain) >> 8; + } + + if (g_paintBuffers[bufferIndex].fsurround_center) + { + for (i = 0; i < count; i++) + { + pbufcenter[i].left = (pbufcenter[i].left * gain) >> 8; + // pbufcenter[i].right = (pbufcenter[i].right * gain) >> 8; mono center channel + } + } + } +} + +// DEBUG peak detection values +#define _SDEBUG 1 + +#ifdef _SDEBUG +float sdebug_avg_in = 0.0; +float sdebug_in_count = 0.0; +float sdebug_avg_out = 0.0; +float sdebug_out_count = 0.0; +#define SDEBUG_TOTAL_COUNT (3*44100) +#endif // DEBUG + +// DEBUG code - get and show peak value of specified paintbuffer +// DEBUG code - ibuf is buffer index, count is # samples to test, pppeakprev stores peak + + +void SDEBUG_GetAvgValue( int ibuf, int count, float *pav ) +{ +#ifdef _SDEBUG + if (snd_showstart.GetInt() != 4 ) + return; + + float av = 0.0; + + for (int i = 0; i < count; i++) + av += (float)(abs(g_paintBuffers[ibuf].pbuf->left) + abs(g_paintBuffers[ibuf].pbuf->right))/2.0; + + *pav = av / count; +#endif // DEBUG +} + + +void SDEBUG_GetAvgIn( int ibuf, int count) +{ + float av = 0.0; + SDEBUG_GetAvgValue( ibuf, count, &av ); + + sdebug_avg_in = ((av * count ) + (sdebug_avg_in * sdebug_in_count)) / (count + sdebug_in_count); + sdebug_in_count += count; +} + +void SDEBUG_GetAvgOut( int ibuf, int count) +{ + float av = 0.0; + SDEBUG_GetAvgValue( ibuf, count, &av ); + + sdebug_avg_out = ((av * count ) + (sdebug_avg_out * sdebug_out_count)) / (count + sdebug_out_count); + sdebug_out_count += count; +} + + +void SDEBUG_ShowAvgValue() +{ +#ifdef _SDEBUG + if (sdebug_in_count > SDEBUG_TOTAL_COUNT) + { + if ((int)sdebug_avg_in > 20.0 && (int)sdebug_avg_out > 20.0) + DevMsg("dsp avg gain:%1.2f in:%1.2f out:%1.2f 1/gain:%1.2f\n", sdebug_avg_out/sdebug_avg_in, sdebug_avg_in, sdebug_avg_out, sdebug_avg_in/sdebug_avg_out); + + sdebug_avg_in = 0.0; + sdebug_avg_out = 0.0; + sdebug_in_count = 0.0; + sdebug_out_count = 0.0; + } +#endif // DEBUG +} + +// clip all values in paintbuffer to 16bit. +// if fsurround is set for paintbuffer, also process rear buffer samples + +void MIX_CompressPaintbuffer(int ipaint, int count) +{ + VPROF("CompressPaintbuffer"); + int i; + paintbuffer_t *ppaint = MIX_GetPPaintFromIPaint(ipaint); + portable_samplepair_t *pbf; + portable_samplepair_t *pbr; + portable_samplepair_t *pbc; + + pbf = ppaint->pbuf; + pbr = ppaint->pbufrear; + pbc = ppaint->pbufcenter; + + for (i = 0; i < count; i++) + { + pbf->left = CLIP(pbf->left); + pbf->right = CLIP(pbf->right); + pbf++; + } + + if ( ppaint->fsurround ) + { + Assert (pbr); + + for (i = 0; i < count; i++) + { + pbr->left = CLIP(pbr->left); + pbr->right = CLIP(pbr->right); + pbr++; + } + } + + if ( ppaint->fsurround_center ) + { + Assert (pbc); + + for (i = 0; i < count; i++) + { + pbc->left = CLIP(pbc->left); + //pbc->right = CLIP(pbc->right); mono center channel + pbc++; + } + } +} + + +// mix and upsample channels to 44khz 'ipaintbuffer' +// mix channels matching 'flags' (SOUND_MIX_DRY, SOUND_MIX_WET, SOUND_MIX_SPEAKER) into specified paintbuffer +// upsamples 11khz, 22khz channels to 44khz. + +// NOTE: only call this on channels that will be mixed into only 1 paintbuffer +// and that will not be mixed until the next mix pass! otherwise, MIX_MixChannelsToPaintbuffer +// will advance any internal pointers on mixed channels; subsequent calls will be at +// incorrect offset. + +void MIX_MixUpsampleBuffer( CChannelList &list, int ipaintbuffer, int end, int count, int flags ) +{ + VPROF("MixUpsampleBuffer"); + int ipaintcur = MIX_GetCurrentPaintbufferIndex(); // save current paintbuffer + + // reset paintbuffer upsampling filter index + MIX_ResetPaintbufferFilterCounter( ipaintbuffer ); + + // prevent other paintbuffers from being mixed + MIX_DeactivateAllPaintbuffers(); + + MIX_ActivatePaintbuffer( ipaintbuffer ); // operates on MIX_MixChannelsToPaintbuffer + MIX_SetCurrentPaintbuffer( ipaintbuffer ); // operates on MixUpSample + + // mix 11khz channels to buffer + if ( list.m_has11kChannels ) + { + MIX_MixChannelsToPaintbuffer( list, end, flags, SOUND_11k, SOUND_11k ); + + // upsample 11khz buffer by 2x + g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); + } + + if ( list.m_has22kChannels || list.m_has11kChannels ) + { + // mix 22khz channels to buffer + MIX_MixChannelsToPaintbuffer( list, end, flags, SOUND_22k, SOUND_22k ); + +#if (SOUND_DMA_SPEED > SOUND_22k) + // upsample 22khz buffer by 2x + g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR ); +#endif + } + + // mix 44khz channels to buffer + MIX_MixChannelsToPaintbuffer( list, end, flags, SOUND_44k, SOUND_DMA_SPEED); + + MIX_DeactivateAllPaintbuffers(); + + // restore previous paintbuffer + MIX_SetCurrentPaintbuffer( ipaintcur ); +} + +// upsample and mix sounds into final 44khz versions of the following paintbuffers: +// SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, IFACINGAWAY, SOUND_BUFFER_DRY, SOUND_BUFFER_SPEAKER, SOUND_BUFFER_SPECIALs +// dsp fx are then applied to these buffers by the caller. +// caller also remixes all into final SOUND_BUFFER_PAINT output. + +void MIX_UpsampleAllPaintbuffers( CChannelList &list, int end, int count ) +{ + VPROF( "MixUpsampleAll" ); + + // 'dry' and 'speaker' channel sounds mix 100% into their corresponding buffers + + // mix and upsample all 'dry' sounds (channels) to 44khz SOUND_BUFFER_DRY paintbuffer + + if ( list.m_hasDryChannels ) + MIX_MixUpsampleBuffer( list, SOUND_BUFFER_DRY, end, count, SOUND_MIX_DRY ); + + // mix and upsample all 'speaker' sounds (channels) to 44khz SOUND_BUFFER_SPEAKER paintbuffer + + if ( list.m_hasSpeakerChannels ) + MIX_MixUpsampleBuffer( list, SOUND_BUFFER_SPEAKER, end, count, SOUND_MIX_SPEAKER ); + + // mix and upsample all 'special dsp' sounds (channels) to 44khz SOUND_BUFFER_SPECIALs paintbuffer + + for ( int iDSP = 0; iDSP < list.m_nSpecialDSPs.Count(); ++iDSP ) + { + for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i ) + { + paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i ); + if ( pSpecialBuffer->nSpecialDSP == list.m_nSpecialDSPs[ iDSP ] && pSpecialBuffer->idsp_specialdsp != -1 ) + { + MIX_MixUpsampleBuffer( list, i, end, count, SOUND_MIX_SPECIAL_DSP ); + break; + } + } + } + + // 'room', 'facing' 'facingaway' sounds are mixed into up to 3 buffers: + + // 11khz sounds are mixed into 3 buffers based on distance from listener, and facing direction + // These buffers are room, facing, facingaway + // These 3 mixed buffers are then each upsampled to 22khz. + + // 22khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction + // These 3 mixed buffers are then each upsampled to 44khz. + + // 44khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction + MIX_DeactivateAllPaintbuffers(); + + // set paintbuffer upsample filter indices to 0 + MIX_ResetPaintbufferFilterCounters(); + + if ( !g_bDspOff ) + { + // only mix to roombuffer if dsp fx are on KDB: perf + MIX_ActivatePaintbuffer(SOUND_BUFFER_ROOM); // operates on MIX_MixChannelsToPaintbuffer + } + + MIX_ActivatePaintbuffer(SOUND_BUFFER_FACING); + + if ( g_bdirectionalfx ) + { + // mix to facing away buffer only if directional presets are set + + MIX_ActivatePaintbuffer(SOUND_BUFFER_FACINGAWAY); + } + + // mix 11khz sounds: + // pan sounds between 3 busses: facing, facingaway and room buffers + + MIX_MixChannelsToPaintbuffer( list, end, SOUND_MIX_WET, SOUND_11k, SOUND_11k); + + // upsample all 11khz buffers by 2x + if ( !g_bDspOff ) + { + // only upsample roombuffer if dsp fx are on KDB: perf + MIX_SetCurrentPaintbuffer(SOUND_BUFFER_ROOM); // operates on MixUpSample + g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); + } + + MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACING); + g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); + + if ( g_bdirectionalfx ) + { + MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACINGAWAY); + g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); + } + + // mix 22khz sounds: + // pan sounds between 3 busses: facing, facingaway and room buffers + MIX_MixChannelsToPaintbuffer( list, end, SOUND_MIX_WET, SOUND_22k, SOUND_22k); + + // upsample all 22khz buffers by 2x +#if ( SOUND_DMA_SPEED > SOUND_22k ) + if ( !g_bDspOff ) + { + // only upsample roombuffer if dsp fx are on KDB: perf + + MIX_SetCurrentPaintbuffer(SOUND_BUFFER_ROOM); + g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR ); + } + + MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACING); + g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR ); + + if ( g_bdirectionalfx ) + { + MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACINGAWAY); + g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR ); + } +#endif + + // mix all 44khz sounds to all active paintbuffers + MIX_MixChannelsToPaintbuffer( list, end, SOUND_MIX_WET, SOUND_44k, SOUND_DMA_SPEED); + + MIX_DeactivateAllPaintbuffers(); + + MIX_SetCurrentPaintbuffer(SOUND_BUFFER_PAINT); +} + +ConVar snd_cull_duplicates("snd_cull_duplicates","0",FCVAR_ALLOWED_IN_COMPETITIVE,"If nonzero, aggressively cull duplicate sounds during mixing. The number specifies the number of duplicates allowed to be played."); + + +// Helper class for determining whether a given channel number should be culled from +// mixing, if snd_cull_duplicates is enabled (psychoacoustic quashing). +class CChannelCullList +{ +public: + // default constructor + CChannelCullList() : m_numChans(0) {}; + + // call if you plan on culling channels - and not otherwise, it's a little expensive + // (that's why it's not in the constructor) + void Initialize( CChannelList &list ); + + // returns true if a given channel number has been marked for culling + inline bool ShouldCull( int channelNum ) + { + return (m_numChans > channelNum) ? m_bShouldCull[channelNum] : false; + } + + // an array of sound names and their volumes + // TODO: there may be a way to do this faster on 360 (eg, pad to 128bit, use SIMD) + struct sChannelVolData + { + int m_channelNum; + int m_vol; // max volume of sound. -1 means "do not cull, ever, do not even do the math" + unsigned int m_nameHash; // a unique id for a sound file + }; +protected: + sChannelVolData m_channelInfo[MAX_CHANNELS]; + + bool m_bShouldCull[MAX_CHANNELS]; // in ChannelList order, not sorted order + int m_numChans; +}; + +// comparator for qsort as used below (eg a lambda) +// returns < 0 if a should come before b, > 0 if a should come after, 0 otherwise +static int __cdecl ChannelVolComparator ( const void * a, const void * b ) +{ + // greater numbers come first. + return static_cast<const CChannelCullList::sChannelVolData *>(b)->m_vol - static_cast<const CChannelCullList::sChannelVolData *>(a)->m_vol; +} + + +void CChannelCullList::Initialize( CChannelList &list ) +{ + VPROF("CChannelCullList::Initialize"); + // First, build a sorted list of channels by decreasing volume, and by a hash of their wavname. + m_numChans = list.Count(); + + for ( int i = m_numChans - 1 ; i >= 0 ; --i ) + { + channel_t *ch = list.GetChannel(i); + m_channelInfo[i].m_channelNum = i; + if ( ch && ch->pMixer->IsReadyToMix() ) + { + m_channelInfo[i].m_vol = ChannelLoudestCurVolume(ch); + AssertMsg(m_channelInfo[i].m_vol >= 0, "Sound channel has a negative volume?"); + m_channelInfo[i].m_nameHash = (unsigned int) ch->sfx; + } + else + { + m_channelInfo[i].m_vol = -1; + m_channelInfo[i].m_nameHash = NULL; // doesn't matter + } + } + + // set the unused channels to invalid data + for ( int i = m_numChans ; i < MAX_CHANNELS ; ++i ) + { + m_channelInfo[i].m_channelNum = -1; + m_channelInfo[i].m_vol = -1; + } + + // Sort the list. + qsort( m_channelInfo, MAX_CHANNELS, sizeof(sChannelVolData), ChannelVolComparator ); + + // Then, determine if the given sound is less than the nth loudest of its hash. If so, mark its flag + // for removal. + // TODO: use an actual algorithm rather than this bogus quadratic technique. + // (I'm using it for now because we don't have convenient/fast hash table + // classes, which would be the linear-time way to deal with this). + const int cutoff = snd_cull_duplicates.GetInt(); + for ( int i = 0 ; i < m_numChans ; ++i ) // i is index in original channel list + { + channel_t *ch = list.GetChannel(i); + // for each sound, determine where it ranks in loudness + int howManyLouder = 0; + for ( int j = 0 ; + m_channelInfo[j].m_channelNum != i && m_channelInfo[j].m_vol >= 0 && j < MAX_CHANNELS ; + ++j ) + { + // j steps through the sorted list until we find ourselves: + if (m_channelInfo[j].m_nameHash == (unsigned int)(ch->sfx)) + { + // that's another channel playing this sound but louder than me + ++howManyLouder; + } + } + if (howManyLouder >= cutoff) + { + // this sound should be culled + m_bShouldCull[i] = true; + } + else + { + // this sound should not be culled + m_bShouldCull[i] = false; + } + } +} + +ConVar snd_mute_losefocus("snd_mute_losefocus", "1", FCVAR_ARCHIVE); + +// build a list of channels that will actually do mixing in this update +// remove all active channels that won't mix for some reason +void MIX_BuildChannelList( CChannelList &list ) +{ + VPROF("MIX_BuildChannelList"); + g_ActiveChannels.GetActiveChannels( list ); + list.m_nSpecialDSPs.RemoveAll(); + list.m_hasDryChannels = false; + list.m_hasSpeakerChannels = false; + list.m_has11kChannels = false; + list.m_has22kChannels = false; + list.m_has44kChannels = false; + bool delayStartServer = false; + bool delayStartClient = false; + bool bPaused = g_pSoundServices->IsGamePaused(); +#ifdef POSIX + bool bActive = g_pSoundServices->IsGameActive(); + bool bStopOnFocusLoss = !bActive && snd_mute_losefocus.GetBool(); +#endif + + CChannelCullList cullList; + if (snd_cull_duplicates.GetInt() > 0) + { + cullList.Initialize(list); + } + + // int numQuashed = 0; + for ( int i = list.Count(); --i >= 0; ) + { + channel_t *ch = list.GetChannel(i); + bool bRemove = false; + // Certain async loaded sounds lazily load into memory in the background, use this to determine + // if the sound is ready for mixing + CAudioSource *pSource = NULL; + if ( ch->pMixer->IsReadyToMix() ) + { + pSource = S_LoadSound( ch->sfx, ch ); + + // Don't mix sound data for sounds with 'zero' volume. If it's a non-looping sound, + // just remove the sound when its volume goes to zero. If it's a 'dry' channel sound (ie: music) + // then assume bZeroVolume is fade in - don't restart + + // To be 'zero' volume, all target volume and current volume values must all be less than 5 + + bool bZeroVolume = BChannelLowVolume( ch, 1 ); + + if ( !pSource || ( bZeroVolume && !pSource->IsLooped() && !ch->flags.bdry ) ) + { + // NOTE: Since we've loaded the sound, check to see if it's a sentence. Play them at zero anyway + // to keep the character's lips moving and the captions happening. + if ( !pSource || pSource->GetSentence() == NULL ) + { + S_FreeChannel( ch ); + bRemove = true; + } + } + else if ( bZeroVolume ) + { + bRemove = true; + } + // If the sound wants to stop when the game pauses, do so + if ( bPaused && SND_ShouldPause(ch) ) + { + bRemove = true; + } +#ifdef POSIX + // If we aren't the active app and the option for background audio isn't on, mute the audio + // Windows has it's own system for background muting + if ( !bRemove && bStopOnFocusLoss ) + { + bRemove = true; + + // Free up the sound channels otherwise they start filling up + if ( pSource && ( !pSource->IsLooped() && !pSource->IsStreaming() ) ) + { + S_FreeChannel( ch ); + } + + } +#endif + // On lowend, aggressively cull duplicate sounds. + if ( !bRemove && snd_cull_duplicates.GetInt() > 0 ) + { + // We can't simply remove them, because then sounds will pile up waiting to finish later. + // We need to flag them for not mixing. + list.m_quashed[i] = cullList.ShouldCull(i); + /* + if (list.m_quashed[i]) + { + numQuashed++; + // Msg("removed %i\n", i); + } + */ + } + else + { + list.m_quashed[i] = false; + } + } + else + { + bRemove = true; + } + + if ( bRemove ) + { + list.RemoveChannelFromList(i); + continue; + } + if ( ch->flags.bSpeaker ) + { + list.m_hasSpeakerChannels = true; + } + if ( ch->special_dsp != 0 ) + { + if ( list.m_nSpecialDSPs.Find( ch->special_dsp ) == -1 ) + { + list.m_nSpecialDSPs.AddToTail( ch->special_dsp ); + } + } + if ( ch->flags.bdry ) + { + list.m_hasDryChannels = true; + } + int rate = pSource->SampleRate(); + if ( rate == SOUND_11k ) + { + list.m_has11kChannels = true; + } + else if ( rate == SOUND_22k ) + { + list.m_has22kChannels = true; + } + else if ( rate == SOUND_44k ) + { + list.m_has44kChannels = true; + } + if ( ch->flags.delayed_start && !SND_IsMouth(ch) ) + { + if ( ch->flags.fromserver ) + { + delayStartServer = true; + } + else + { + delayStartClient = true; + } + } + + // get playback pitch + ch->pitch = ch->pMixer->ModifyPitch( ch->basePitch * 0.01f ); + } + // DevMsg( "%d channels quashed.\n", numQuashed ); + + // This code will resync the delay calculation clock really often + // any time there are no scheduled waves or the game is paused + // we go ahead and reset the clock + // That way the clock is only used for short periods of time + // and we need no solution for drift + if ( bPaused || (host_frametime_unbounded > host_frametime) ) + { + delayStartClient = false; + delayStartServer = false; + } + if (!delayStartServer) + { + S_SyncClockAdjust(CLOCK_SYNC_SERVER); + } + if (!delayStartClient) + { + S_SyncClockAdjust(CLOCK_SYNC_CLIENT); + } +} + +// main mixing rountine - mix up to 'endtime' samples. +// All channels are mixed in a paintbuffer and then sent to +// hardware. + +// A mix pass is performed, resulting in mixed sounds in SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, SOUND_BUFFER_FACINGAWAY, SOUND_BUFFER_DRY, SOUND_BUFFER_SPEAKER, SOUND_BUFFER_SPECIALs + + // directional sounds are panned and mixed between SOUND_BUFFER_FACING and SOUND_BUFFER_FACINGAWAY + // omnidirectional sounds are panned 100% into SOUND_BUFFER_FACING + // sound sources far from player (ie: near back of room ) are mixed in proportion to this distance + // into SOUND_BUFFER_ROOM + // sounds with ch->bSpeaker set are mixed in mono into SOUND_BUFFER_SPEAKER + // sounds with ch->bSpecialDSP set are mixed in mono into SOUND_BUFFER_SPECIALs + +// dsp_facingaway fx (2 or 4ch filtering) are then applied to the SOUND_BUFFER_FACINGAWAY +// dsp_speaker fx (1ch) are then applied to the SOUND_BUFFER_SPEAKER +// dsp_specialdsp fx (1ch) are then applied to the SOUND_BUFFER_SPECIALs +// dsp_room fx (1ch reverb) are then applied to the SOUND_BUFFER_ROOM + +// All buffers are recombined into the SOUND_BUFFER_PAINT + +// The dsp_water and dsp_player fx are applied in series to the SOUND_BUFFER_PAINT + +// Finally, the SOUND_BUFFER_DRY buffer is mixed into the SOUND_BUFFER_PAINT + +extern ConVar dsp_off; +extern ConVar snd_profile; +extern void DEBUG_StartSoundMeasure(int type, int samplecount ); +extern void DEBUG_StopSoundMeasure(int type, int samplecount ); +extern ConVar dsp_enhance_stereo; + +extern ConVar dsp_volume; +extern ConVar dsp_vol_5ch; +extern ConVar dsp_vol_4ch; +extern ConVar dsp_vol_2ch; + +extern void MXR_SetCurrentSoundMixer( const char *szsoundmixer ); +extern ConVar snd_soundmixer; + +void MIX_PaintChannels( int endtime, bool bIsUnderwater ) +{ + VPROF("MIX_PaintChannels"); + tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); + + int end; + int count; + bool b_spatial_delays = dsp_enhance_stereo.GetInt() != 0 ? true : false; + bool room_fsurround_sav; + bool room_fsurround_center_sav; + paintbuffer_t *proom = MIX_GetPPaintFromIPaint(SOUND_BUFFER_ROOM); + + CheckNewDspPresets(); + + MXR_SetCurrentSoundMixer( snd_soundmixer.GetString() ); + + // dsp performance tuning + + g_snd_profile_type = snd_profile.GetInt(); + + // dsp_off is true if no dsp processing is to run + // directional dsp processing is enabled if dsp_facingaway is non-zero + + g_bDspOff = dsp_off.GetInt() ? 1 : 0; + CChannelList list; + + MIX_BuildChannelList(list); + + // get master dsp volume + g_dsp_volume = dsp_volume.GetFloat(); + + // attenuate master dsp volume by 2,4 or 5 ch settings + if ( g_AudioDevice->IsSurround() ) + { + g_dsp_volume *= ( g_AudioDevice->IsSurroundCenter() ? dsp_vol_5ch.GetFloat() : dsp_vol_4ch.GetFloat() ); + } + else + { + g_dsp_volume *= dsp_vol_2ch.GetFloat(); + } + + if ( !g_bDspOff ) + { + g_bdirectionalfx = dsp_facingaway.GetInt() ? 1 : 0; + } + else + { + g_bdirectionalfx = 0; + } + + // get dsp preset gain values, update gain crossfaders, used when mixing dsp processed buffers into paintbuffer + SDEBUG_ShowAvgValue(); + + // the cache needs to hold the audio in memory during mixing, so tell it that mixing is starting + wavedatacache->OnMixBegin(); + + while ( g_paintedtime < endtime ) + { + VPROF("MIX_PaintChannels inner loop"); + // mix a full 'paintbuffer' of sound + + // clamp at paintbuffer size + end = endtime; + if (endtime - g_paintedtime > PAINTBUFFER_SIZE) + { + end = g_paintedtime + PAINTBUFFER_SIZE; + } + + // number of 44khz samples to mix into paintbuffer, up to paintbuffer size + count = end - g_paintedtime; + + // clear all mix buffers + g_AudioDevice->MixBegin( count ); + + // upsample all mix buffers. + // results in 44khz versions of: + // SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, SOUND_BUFFER_FACINGAWAY, SOUND_BUFFER_DRY, SOUND_BUFFER_SPEAKER, SOUND_BUFFER_SPECIALs + MIX_UpsampleAllPaintbuffers( list, end, count ); + + // apply appropriate dsp fx to each buffer, remix buffers into single quad output buffer + // apply 2 or 4ch filtering to IFACINGAWAY buffer + if ( g_bdirectionalfx ) + { + g_AudioDevice->ApplyDSPEffects( idsp_facingaway, MIX_GetPFrontFromIPaint(SOUND_BUFFER_FACINGAWAY), MIX_GetPRearFromIPaint(SOUND_BUFFER_FACINGAWAY), MIX_GetPCenterFromIPaint(SOUND_BUFFER_FACINGAWAY), count ); + } + + if ( !g_bDspOff && list.m_hasSpeakerChannels ) + { + // apply 1ch filtering to SOUND_BUFFER_SPEAKER + g_AudioDevice->ApplyDSPEffects( idsp_speaker, MIX_GetPFrontFromIPaint(SOUND_BUFFER_SPEAKER), MIX_GetPRearFromIPaint(SOUND_BUFFER_SPEAKER), MIX_GetPCenterFromIPaint(SOUND_BUFFER_SPEAKER), count ); + + // mix SOUND_BUFFER_SPEAKER with SOUND_BUFFER_ROOM and SOUND_BUFFER_FACING + MIX_ScalePaintBuffer( SOUND_BUFFER_SPEAKER, count, 0.7 ); + + MIX_MixPaintbuffers( SOUND_BUFFER_SPEAKER, SOUND_BUFFER_FACING, SOUND_BUFFER_FACING, count, 1.0 ); // +70% dry speaker + + MIX_ScalePaintBuffer( SOUND_BUFFER_SPEAKER, count, 0.43 ); + + MIX_MixPaintbuffers( SOUND_BUFFER_SPEAKER, SOUND_BUFFER_ROOM, SOUND_BUFFER_ROOM, count, 1.0 ); // +30% wet speaker + } + + if ( !g_bDspOff ) + { + // apply 1ch filtering to SOUND_BUFFER_SPECIALs + for ( int iDSP = 0; iDSP < list.m_nSpecialDSPs.Count(); ++iDSP ) + { + bool bFoundMixer = false; + + for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i ) + { + paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i ); + if ( pSpecialBuffer->nSpecialDSP == list.m_nSpecialDSPs[ iDSP ] && pSpecialBuffer->idsp_specialdsp != -1 ) + { + g_AudioDevice->ApplyDSPEffects( pSpecialBuffer->idsp_specialdsp, MIX_GetPFrontFromIPaint( i ), MIX_GetPRearFromIPaint( i ), MIX_GetPCenterFromIPaint( i ), count ); + + // mix SOUND_BUFFER_SPECIALs with SOUND_BUFFER_ROOM and SOUND_BUFFER_FACING + MIX_ScalePaintBuffer( i, count, 0.7 ); + + MIX_MixPaintbuffers( i, SOUND_BUFFER_FACING, SOUND_BUFFER_FACING, count, 1.0 ); // +70% dry speaker + + MIX_ScalePaintBuffer( i, count, 0.43 ); + + MIX_MixPaintbuffers( i, SOUND_BUFFER_ROOM, SOUND_BUFFER_ROOM, count, 1.0 ); // +30% wet speaker + + bFoundMixer = true; + + break; + } + } + + // Couldn't find a mixer with the correct DSP, so make a new one! + if ( !bFoundMixer ) + { + bool bSurroundCenter = g_AudioDevice->IsSurroundCenter(); + bool bSurround = g_AudioDevice->IsSurround() || bSurroundCenter; + + int nIndex = g_paintBuffers.AddToTail(); + MIX_InitializePaintbuffer( &(g_paintBuffers[ nIndex ]), bSurround, bSurroundCenter ); + + g_paintBuffers[ nIndex ].flags = SOUND_BUSS_SPECIAL_DSP; + + // special dsp buffer mixes to mono + g_paintBuffers[ nIndex ].fsurround = false; + g_paintBuffers[ nIndex ].fsurround_center = false; + + g_paintBuffers[ nIndex ].idsp_specialdsp = -1; + g_paintBuffers[ nIndex ].nSpecialDSP = list.m_nSpecialDSPs[ iDSP ]; + + g_paintBuffers[ nIndex ].nPrevSpecialDSP = g_paintBuffers[ nIndex ].nSpecialDSP; + g_paintBuffers[ nIndex ].idsp_specialdsp = DSP_Alloc( g_paintBuffers[ nIndex ].nSpecialDSP, 300, 1 ); + } + } + } + + // apply dsp_room effects to room buffer + g_AudioDevice->ApplyDSPEffects( Get_idsp_room(), MIX_GetPFrontFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPRearFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPCenterFromIPaint(SOUND_BUFFER_ROOM), count ); + + // save room buffer surround status, in case we upconvert it + room_fsurround_sav = proom->fsurround; + room_fsurround_center_sav = proom->fsurround_center; + + // apply left/center/right/lrear/rrear spatial delays to room buffer + if ( b_spatial_delays && !g_bDspOff && !DSP_RoomDSPIsOff() ) + { + // upgrade mono room buffer to surround status so we can apply spatial delays to all channels + MIX_ConvertBufferToSurround( SOUND_BUFFER_ROOM ); + g_AudioDevice->ApplyDSPEffects( idsp_spatial, MIX_GetPFrontFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPRearFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPCenterFromIPaint(SOUND_BUFFER_ROOM), count ); + } + + if ( g_bdirectionalfx ) // KDB: perf + { + // Recombine IFACING and IFACINGAWAY buffers into SOUND_BUFFER_PAINT + MIX_MixPaintbuffers( SOUND_BUFFER_FACING, SOUND_BUFFER_FACINGAWAY, SOUND_BUFFER_PAINT, count, DSP_NOROOM_MIX ); + + // Add in dsp room fx to paintbuffer, mix at 75% + MIX_MixPaintbuffers( SOUND_BUFFER_ROOM, SOUND_BUFFER_PAINT, SOUND_BUFFER_PAINT, count, DSP_ROOM_MIX ); + } + else + { + // Mix IFACING buffer with SOUND_BUFFER_ROOM + // (SOUND_BUFFER_FACINGAWAY contains no data, IFACINGBBUFFER has full dry mix based on distance from listener) + // if dsp disabled, mix 100% facingbuffer, otherwise, mix 75% facingbuffer + roombuffer + float mix = g_bDspOff ? 1.0 : DSP_ROOM_MIX; + MIX_MixPaintbuffers( SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, SOUND_BUFFER_PAINT, count, mix ); + } + + // restore room buffer surround status, in case we upconverted it + proom->fsurround = room_fsurround_sav; + proom->fsurround_center = room_fsurround_center_sav; + + // Apply underwater fx dsp_water (serial in-line) + if ( bIsUnderwater ) + { + // BUG: if out of water, previous delays will be heard. must clear dly buffers. + g_AudioDevice->ApplyDSPEffects( idsp_water, MIX_GetPFrontFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPRearFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPCenterFromIPaint(SOUND_BUFFER_PAINT), count ); + } + + // find dsp gain + SDEBUG_GetAvgIn(SOUND_BUFFER_PAINT, count); + + // Apply player fx dsp_player (serial in-line) - does nothing if dsp fx are disabled + g_AudioDevice->ApplyDSPEffects( idsp_player, MIX_GetPFrontFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPRearFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPCenterFromIPaint(SOUND_BUFFER_PAINT), count ); + + // display dsp gain + SDEBUG_GetAvgOut(SOUND_BUFFER_PAINT, count); + +/* + // apply left/center/right/lrear/rrear spatial delays to paint buffer + + if ( b_spatial_delays ) + g_AudioDevice->ApplyDSPEffects( idsp_spatial, MIX_GetPFrontFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPRearFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPCenterFromIPaint(SOUND_BUFFER_PAINT), count ); +*/ + // Add dry buffer, set output gain to water * player dsp gain (both 1.0 if not active) + + MIX_MixPaintbuffers( SOUND_BUFFER_PAINT, SOUND_BUFFER_DRY, SOUND_BUFFER_PAINT, count, 1.0); + + // clip all values > 16 bit down to 16 bit + // NOTE: This is required - the hardware buffer transfer routines no longer perform clipping. + MIX_CompressPaintbuffer( SOUND_BUFFER_PAINT, count ); + + // transfer SOUND_BUFFER_PAINT paintbuffer out to DMA buffer + MIX_SetCurrentPaintbuffer( SOUND_BUFFER_PAINT ); + + g_AudioDevice->TransferSamples( end ); + + g_paintedtime = end; + } + + // the cache needs to hold the audio in memory during mixing, so tell it that mixing is complete + wavedatacache->OnMixEnd(); +} + +// Applies volume scaling (evenly) to all fl,fr,rl,rr volumes +// used for voice ducking and panning between various mix busses +// Ensures if mixing to speaker buffer, only speaker sounds pass through + +// Called just before mixing wav data to current paintbuffer. +// a) if another player in a multiplayer game is speaking, scale all volumes down. +// b) if mixing to SOUND_BUFFER_ROOM, scale all volumes by ch.dspmix and dsp_room gain +// c) if mixing to SOUND_BUFFER_FACINGAWAY, scale all volumes by ch.dspface and dsp_facingaway gain +// d) If SURROUND_ON, but buffer is not surround, recombined front/rear volumes + +// returns false if channel is to be entirely skipped. + +bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans ) +{ + int i; + int mixflag = ppaint->flags; + float scale; + char wavtype = pChannel->wavtype; + float dspmix; + + // copy current channel volumes into output array + + ChannelCopyVolumes( pChannel, volume, 0, CCHANVOLUMES ); + + dspmix = pChannel->dspmix; + + // if dsp is off, or room dsp is off, mix 0% to mono room buffer, 100% to facing buffer + + if ( g_bDspOff || DSP_RoomDSPIsOff() ) + dspmix = 0.0; + + // duck all sound volumes except speaker's voice +#if !defined( NO_VOICE ) + int duckScale = min((int)(g_DuckScale * 256), g_SND_VoiceOverdriveInt); +#else + int duckScale = (int)(g_DuckScale * 256); +#endif + if( duckScale < 256 ) + { + if( pChannel->pMixer ) + { + CAudioSource *pSource = pChannel->pMixer->GetSource(); + if( !pSource->IsVoiceSource() ) + { + // Apply voice overdrive.. + for (i = 0; i < CCHANVOLUMES; i++) + volume[i] = (volume[i] * duckScale) >> 8; + } + } + } + + // If mixing to the room buss, adjust volume based on channel's dspmix setting. + // dspmix is DSP_MIX_MAX (~0.78) if sound is far from player, DSP_MIX_MIN (~0.24) if sound is near player + + if ( mixflag & SOUND_BUSS_ROOM ) + { + // set dsp mix volume, scaled by global dsp_volume + + float dspmixvol = fpmin(dspmix * g_dsp_volume, 1.0f); + + // if dspmix is 1.0, 100% of sound goes to SOUND_BUFFER_ROOM and 0% to SOUND_BUFFER_FACING + + for (i = 0; i < CCHANVOLUMES; i++) + volume[i] = (int)((float)(volume[i]) * dspmixvol); + } + + // If global dsp volume is less than 1, reduce dspmix (ie: increase dry volume) + // If gloabl dsp volume is greater than 1, do not reduce dspmix + + if (g_dsp_volume < 1.0) + dspmix *= g_dsp_volume; + + // If mixing to facing/facingaway buss, adjust volume based on sound entity's facing direction. + + // If sound directly faces player, ch->dspface = 1.0. If facing directly away, ch->dspface = -1.0. + // mix to lowpass buffer if facing away, to allpass if facing + + // scale 1.0 - facing player, scale 0, facing away + + scale = (pChannel->dspface + 1.0) / 2.0; + + // UNDONE: get front cone % from channel to set this. + + // bias scale such that 1.0 to 'cone' is considered facing. Facing cone narrows as cone -> 1.0 + // and 'cone' -> 0.0 becomes 1.0 -> 0.0 + + float cone = 0.6f; + + scale = scale * (1/cone); + + scale = clamp( scale, 0.0f, 1.0f ); + + // pan between facing and facing away buffers + + // if ( !g_bdirectionalfx || wavtype == CHAR_DOPPLER || wavtype == CHAR_OMNI || (wavtype == CHAR_DIRECTIONAL && mixchans == 2) ) + if ( !g_bdirectionalfx || wavtype != CHAR_DIRECTIONAL ) + { + // if no directional fx mix 0% to facingaway buffer + // if wavtype is DOPPLER, mix 0% to facingaway buffer - DOPPLER wavs have a custom mixer + // if wavtype is OMNI, mix 0% to facingaway buffer - OMNI wavs have no directionality + // if wavtype is DIRECTIONAL and stereo encoded, mix 0% to facingaway buffer - DIRECTIONAL STEREO wavs have a custom mixer + + scale = 1.0; + } + + if ( mixflag & SOUND_BUSS_FACING ) + { + // facing player + // if dspface is 1.0, 100% of sound goes to SOUND_BUFFER_FACING + + for (i = 0; i < CCHANVOLUMES; i++) + volume[i] = (int)((float)(volume[i]) * scale * (1.0 - dspmix)); + } + else if ( mixflag & SOUND_BUSS_FACINGAWAY ) + { + // facing away from player + // if dspface is 0.0, 100% of sound goes to SOUND_BUFFER_FACINGAWAY + + for (i = 0; i < CCHANVOLUMES; i++) + volume[i] = (int)((float)(volume[i]) * (1.0 - scale) * (1.0 - dspmix)); + } + + // NOTE: this must occur last in this routine: + + if ( g_AudioDevice->IsSurround() && !ppaint->fsurround ) + { + // if 4ch or 5ch spatialization on, but current mix buffer is 2ch, + // recombine front + rear volumes (revert to 2ch spatialization) + + volume[IFRONT_RIGHT] += volume[IREAR_RIGHT]; + volume[IFRONT_LEFT] += volume[IREAR_LEFT]; + + volume[IFRONT_RIGHTD] += volume[IREAR_RIGHTD]; + volume[IFRONT_LEFTD] += volume[IREAR_LEFTD]; + + // if 5 ch, recombine center channel vol + + if ( g_AudioDevice->IsSurroundCenter() ) + { + volume[IFRONT_RIGHT] += volume[IFRONT_CENTER] / 2; + volume[IFRONT_LEFT] += volume[IFRONT_CENTER] / 2; + + volume[IFRONT_RIGHTD] += volume[IFRONT_CENTERD] / 2; + volume[IFRONT_LEFTD] += volume[IFRONT_CENTERD] / 2; + } + + // clear rear & center volumes + + volume[IREAR_RIGHT] = 0; + volume[IREAR_LEFT] = 0; + volume[IFRONT_CENTER] = 0; + + volume[IREAR_RIGHTD] = 0; + volume[IREAR_LEFTD] = 0; + volume[IFRONT_CENTERD] = 0; + + } + + bool fzerovolume = true; + + for (i = 0; i < CCHANVOLUMES; i++) + { + volume[i] = clamp(volume[i], 0, 255); + + if (volume[i]) + fzerovolume = false; + } + + + if ( fzerovolume ) + { + // DevMsg ("Skipping mix of 0 volume sound! \n"); + return false; + } + + return true; +} + + +//=============================================================================== +// Low level mixing routines +//=============================================================================== +void Snd_WriteLinearBlastStereo16( void ) +{ +#if !id386 + int i; + int val; + + for ( i=0; i<snd_linear_count; i+=2 ) + { + // scale and clamp left 16bit signed: [0x8000, 0x7FFF] + val = ( snd_p[i] * snd_vol )>>8; + if ( val > 32767 ) + snd_out[i] = 32767; + else if ( val < -32768 ) + snd_out[i] = -32768; + else + snd_out[i] = val; + + // scale and clamp right 16bit signed: [0x8000, 0x7FFF] + val = ( snd_p[i+1] * snd_vol )>>8; + if ( val > 32767 ) + snd_out[i+1] = 32767; + else if ( val < -32768 ) + snd_out[i+1] = -32768; + else + snd_out[i+1] = val; + } +#else + __asm + { + // input data + mov ebx,snd_p + + // output data + mov edi,snd_out + + // iterate from end to beginning + mov ecx,snd_linear_count + + // scale table + mov esi,snd_vol + + // scale and clamp 16bit signed lsw: [0x8000, 0x7FFF] +WLBS16_LoopTop: + mov eax,[ebx+ecx*4-8] + imul eax,esi + sar eax,0x08 + cmp eax,0x7FFF + jg WLBS16_ClampHigh + cmp eax,0xFFFF8000 + jnl WLBS16_ClampDone + mov eax,0xFFFF8000 + jmp WLBS16_ClampDone +WLBS16_ClampHigh: + mov eax,0x7FFF +WLBS16_ClampDone: + + // scale and clamp 16bit signed msw: [0x8000, 0x7FFF] + mov edx,[ebx+ecx*4-4] + imul edx,esi + sar edx,0x08 + cmp edx,0x7FFF + jg WLBS16_ClampHigh2 + cmp edx,0xFFFF8000 + jnl WLBS16_ClampDone2 + mov edx,0xFFFF8000 + jmp WLBS16_ClampDone2 +WLBS16_ClampHigh2: + mov edx,0x7FFF +WLBS16_ClampDone2: + shl edx,0x10 + and eax,0xFFFF + or edx,eax + mov [edi+ecx*2-4],edx + + // two shorts per iteration + sub ecx,0x02 + jnz WLBS16_LoopTop + } +#endif +} + +void SND_InitScaletable (void) +{ + int i, j; + + for (i=0 ; i<SND_SCALE_LEVELS; i++) + for (j=0 ; j<256 ; j++) + snd_scaletable[i][j] = ((signed char)j) * i * (1<<SND_SCALE_SHIFT); +} + +void SND_PaintChannelFrom8(portable_samplepair_t *pOutput, int *volume, byte *pData8, int count) +{ +#if !id386 + int data; + int *lscale, *rscale; + int i; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + for (i=0 ; i<count ; i++) + { + data = pData8[i]; + + pOutput[i].left += lscale[data]; + pOutput[i].right += rscale[data]; + } +#else + // portable_samplepair_t structure +#define psp_left 0 +#define psp_right 4 +#define psp_size 8 + static int tempStore; + + __asm + { + // prologue + push ebp + + // esp = pOutput + mov eax, pOutput + mov tempStore, eax + xchg esp,tempStore + // ebx = volume + mov ebx,volume + // esi = pData8 + mov esi,pData8 + // ecx = count + mov ecx,count + + // These values depend on the setting of SND_SCALE_BITS + // The mask must mask off all the lower bits you aren't using in the multiply + // so for 7 bits, the mask is 0xFE, 6 bits 0xFC, etc. + // The shift must multiply by the table size. There are 256 4-byte values in the table at each level. + // So each index must be shifted left by 10, but since the bits we use are in the MSB rather than LSB + // they must be shifted right by 8 - SND_SCALE_BITS. e.g., for a 7 bit number the left shift is: + // 10 - (8-7) = 9. For a 5 bit number it's 10 - (8-5) = 7. + mov eax,[ebx] + mov edx,[ebx + 4] + and eax,0xFE + and edx,0xFE + + // shift up by 10 to index table, down by 1 to make the 7 MSB of the bytes an index + // eax = lscale + // edx = rscale + shl eax,0x09 + shl edx,0x09 + add eax,OFFSET snd_scaletable + add edx,OFFSET snd_scaletable + + // ebx = data byte + sub ebx,ebx + mov bl,[esi+ecx-1] + + // odd or even number of L/R samples + test ecx,0x01 + jz PCF8_Loop + + // process odd L/R sample + mov edi,[eax+ebx*4] + mov ebp,[edx+ebx*4] + add edi,[esp+ecx*psp_size-psp_size+psp_left] + add ebp,[esp+ecx*psp_size-psp_size+psp_right] + mov [esp+ecx*psp_size-psp_size+psp_left],edi + mov [esp+ecx*psp_size-psp_size+psp_right],ebp + mov bl,[esi+ecx-1-1] + + dec ecx + jz PCF8_Done + +PCF8_Loop: + // process L/R sample N + mov edi,[eax+ebx*4] + mov ebp,[edx+ebx*4] + add edi,[esp+ecx*psp_size-psp_size+psp_left] + add ebp,[esp+ecx*psp_size-psp_size+psp_right] + mov [esp+ecx*psp_size-psp_size+psp_left],edi + mov [esp+ecx*psp_size-psp_size+psp_right],ebp + mov bl,[esi+ecx-1-1] + + // process L/R sample N-1 + mov edi,[eax+ebx*4] + mov ebp,[edx+ebx*4] + add edi,[esp+ecx*psp_size-psp_size*2+psp_left] + add ebp,[esp+ecx*psp_size-psp_size*2+psp_right] + mov [esp+ecx*psp_size-psp_size*2+psp_left],edi + mov [esp+ecx*psp_size-psp_size*2+psp_right],ebp + mov bl,[esi+ecx-1-2] + + // two L/R samples per iteration + sub ecx,0x02 + jnz PCF8_Loop + +PCF8_Done: + // epilogue + xchg esp,tempStore + pop ebp + } +#endif +} + +//=============================================================================== +// SOFTWARE MIXING ROUTINES +//=============================================================================== + +// UNDONE: optimize these + +// grab samples from left source channel only and mix as if mono. +// volume array contains appropriate spatialization volumes for doppler left (incoming sound) +void SW_Mix8StereoDopplerLeft( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + for ( int i = 0; i < outCount; i++ ) + { + pOutput[i].left += lscale[pData[sampleIndex]]; + pOutput[i].right += rscale[pData[sampleIndex]]; + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + +// grab samples from right source channel only and mix as if mono. +// volume array contains appropriate spatialization volumes for doppler right (outgoing sound) +void SW_Mix8StereoDopplerRight( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + for ( int i = 0; i < outCount; i++ ) + { + pOutput[i].left += lscale[pData[sampleIndex+1]]; + pOutput[i].right += rscale[pData[sampleIndex+1]]; + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } + +} + + +// grab samples from left source channel only and mix as if mono. +// volume array contains appropriate spatialization volumes for doppler left (incoming sound) + +void SW_Mix16StereoDopplerLeft( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + + for ( int i = 0; i < outCount; i++ ) + { + pOutput[i].left += (volume[0] * (int)(pData[sampleIndex]))>>8; + pOutput[i].right += (volume[1] * (int)(pData[sampleIndex]))>>8; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + + +// grab samples from right source channel only and mix as if mono. +// volume array contains appropriate spatialization volumes for doppler right (outgoing sound) + +void SW_Mix16StereoDopplerRight( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + + for ( int i = 0; i < outCount; i++ ) + { + pOutput[i].left += (volume[0] * (int)(pData[sampleIndex+1]))>>8; + pOutput[i].right += (volume[1] * (int)(pData[sampleIndex+1]))>>8; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + +// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction +void SW_Mix8StereoDirectional( float soundfacing, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + int x; + int l,r; + signed char lb,rb; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + // if soundfacing -1.0, sound source is facing away from player + // if soundfacing 0.0, sound source is perpendicular to player + // if soundfacing 1.0, sound source is facing player + + int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256 + + for ( int i = 0; i < outCount; i++ ) + { + lb = (pData[sampleIndex]); // get left byte + rb = (pData[sampleIndex+1]); // get right byte + + l = ((int)lb); + r = ((int)rb); + + x = ( r + ((( l - r ) * frontmix) >> 8) ); + + pOutput[i].left += lscale[x & 0xFF]; // multiply by volume and convert to 16 bit + pOutput[i].right += rscale[x & 0xFF]; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + + +// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction +// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in +// pData buffer, ensuring we can always provide 'outCount' samples. +void SW_Mix8StereoDirectional_Interp( float soundfacing, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + fixedint sampleIndex = 0; + fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point + fixedint sampleFrac14 = FIX_28TO14(inputOffset); + + int first, second, interpl, interpr; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + int x; + + // if soundfacing -1.0, sound source is facing away from player + // if soundfacing 0.0, sound source is perpendicular to player + // if soundfacing 1.0, sound source is facing player + + int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256 + + for ( int i = 0; i < outCount; i++ ) + { + // interpolate between first & second sample (the samples bordering sampleFrac12 fraction) + + first = (int)((signed char)(pData[sampleIndex])); // left byte + second = (int)((signed char)(pData[sampleIndex+2])); + + interpl = first + ( ((second - first) * (int)sampleFrac14) >> 14 ); + + first = (int)((signed char)(pData[sampleIndex+1])); // right byte + second = (int)((signed char)(pData[sampleIndex+3])); + + interpr = first + ( ((second - first) * (int)sampleFrac14) >> 14 ); + + // crossfade between right/left based on directional mix + + x = ( interpr + ((( interpl - interpr ) * frontmix) >> 8) ); + + pOutput[i].left += lscale[x & 0xFF]; // scale and convert to 16 bit + pOutput[i].right += rscale[x & 0xFF]; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } +} + + +// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction + +void SW_Mix16StereoDirectional( float soundfacing, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + fixedint sampleIndex = 0; + fixedint sampleFrac = inputOffset; + + int x; + int l, r; + + // if soundfacing -1.0, sound source is facing away from player + // if soundfacing 0.0, sound source is perpendicular to player + // if soundfacing 1.0, sound source is facing player + + int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256 + + for ( int i = 0; i < outCount; i++ ) + { + // get left, right samples + + l = (int)(pData[sampleIndex]); + r = (int)(pData[sampleIndex+1]); + + // crossfade between left & right based on front/rear facing + + x = ( r + ((( l - r ) * frontmix) >> 8) ); + + pOutput[i].left += (volume[0] * x) >> 8; + pOutput[i].right += (volume[1] * x) >> 8; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + +// mix left wav (front facing) with right wav (rear facing) based on soundfacing direction +// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in +// pData buffer, ensuring we can always provide 'outCount' samples. + +void SW_Mix16StereoDirectional_Interp( float soundfacing, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + fixedint sampleIndex = 0; + fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point + fixedint sampleFrac14 = FIX_28TO14(inputOffset); + + int x; + int first, second, interpl, interpr; + + // if soundfacing -1.0, sound source is facing away from player + // if soundfacing 0.0, sound source is perpendicular to player + // if soundfacing 1.0, sound source is facing player + + int frontmix = (int)(256.0f * ((1.f + soundfacing) / 2.f)); // 0 -> 256 + + for ( int i = 0; i < outCount; i++ ) + { + // get interpolated left, right samples + + first = (int)(pData[sampleIndex]); + second = (int)(pData[sampleIndex+2]); + + interpl = first + (((second - first) * (int)sampleFrac14) >> 14); + + first = (int)(pData[sampleIndex+1]); + second = (int)(pData[sampleIndex+3]); + + interpr = first + (((second - first) * (int)sampleFrac14) >> 14); + + // crossfade between left & right based on front/rear facing + + x = ( interpr + ((( interpl - interpr ) * frontmix) >> 8) ); + + pOutput[i].left += (volume[0] * x) >> 8; + pOutput[i].right += (volume[1] * x) >> 8; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } +} + + +// distance variant wav (left is close, right is far) +void SW_Mix8StereoDistVar( float distmix, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + int x; + int l,r; + signed char lb, rb; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + // distmix 0 - sound is near player (100% wav left) + // distmix 1.0 - sound is far from player (100% wav right) + + int nearmix = (int)(256.0f * (1.0f - distmix)); + int farmix = (int)(256.0f * distmix); + + // if mixing at max or min range, skip crossfade (KDB: perf) + + if (!nearmix) + { + for ( int i = 0; i < outCount; i++ ) + { + rb = (pData[sampleIndex+1]); // get right byte + x = (int) rb; + + pOutput[i].left += lscale[x & 0xFF]; // multiply by volume and convert to 16 bit + pOutput[i].right += rscale[x & 0xFF]; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } + return; + } + + if (!farmix) + { + for ( int i = 0; i < outCount; i++ ) + { + + lb = (pData[sampleIndex]); // get left byte + x = (int) lb; + + pOutput[i].left += lscale[x & 0xFF]; // multiply by volume and convert to 16 bit + pOutput[i].right += rscale[x & 0xFF]; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } + return; + } + + // crossfade left/right + + for ( int i = 0; i < outCount; i++ ) + { + + lb = (pData[sampleIndex]); // get left byte + rb = (pData[sampleIndex+1]); // get right byte + + l = (int)lb; + r = (int)rb; + + x = ( l + (((r - l) * farmix ) >> 8) ); + + pOutput[i].left += lscale[x & 0xFF]; // multiply by volume and convert to 16 bit + pOutput[i].right += rscale[x & 0xFF]; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + + +// distance variant wav (left is close, right is far) +// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in +// pData buffer, ensuring we can always provide 'outCount' samples. +void SW_Mix8StereoDistVar_Interp( float distmix, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int x; + + // distmix 0 - sound is near player (100% wav left) + // distmix 1.0 - sound is far from player (100% wav right) + + int nearmix = (int)(256.0f * (1.0f - distmix)); + int farmix = (int)(256.0f * distmix); + + fixedint sampleIndex = 0; + fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point + fixedint sampleFrac14 = FIX_28TO14(inputOffset); + + int first, second, interpl, interpr; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + // if mixing at max or min range, skip crossfade (KDB: perf) + + if (!nearmix) + { + for ( int i = 0; i < outCount; i++ ) + { + first = (int)((signed char)(pData[sampleIndex+1])); // right sample + second = (int)((signed char)(pData[sampleIndex+3])); + + interpr = first + ( ((second - first) * (int)sampleFrac14) >> 14 ); + + pOutput[i].left += lscale[interpr & 0xFF]; // scale and convert to 16 bit + pOutput[i].right += rscale[interpr & 0xFF]; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + + } + return; + } + + if (!farmix) + { + for ( int i = 0; i < outCount; i++ ) + { + first = (int)((signed char)(pData[sampleIndex])); // left sample + second = (int)((signed char)(pData[sampleIndex+2])); + + interpl = first + ( ((second - first) * (int)sampleFrac14) >> 14 ); + + pOutput[i].left += lscale[interpl & 0xFF]; // scale and convert to 16 bit + pOutput[i].right += rscale[interpl & 0xFF]; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } + return; + } + + // crossfade left/right + + for ( int i = 0; i < outCount; i++ ) + { + // interpolate between first & second sample (the samples bordering sampleFrac14 fraction) + + first = (int)((signed char)(pData[sampleIndex])); + second = (int)((signed char)(pData[sampleIndex+2])); + + interpl = first + ( ((second - first) * (int)sampleFrac14) >> 14 ); + + first = (int)((signed char)(pData[sampleIndex+1])); + second = (int)((signed char)(pData[sampleIndex+3])); + + interpr = first + ( ((second - first) * (int)sampleFrac14) >> 14 ); + + // crossfade between left and right based on distance mix + + x = ( interpl + (((interpr - interpl) * farmix ) >> 8) ); + + pOutput[i].left += lscale[x & 0xFF]; // scale and convert to 16 bit + pOutput[i].right += rscale[x & 0xFF]; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } +} + + +// distance variant wav (left is close, right is far) + +void SW_Mix16StereoDistVar( float distmix, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + int x; + int l,r; + + // distmix 0 - sound is near player (100% wav left) + // distmix 1.0 - sound is far from player (100% wav right) + + int nearmix = Float2Int(256.0f * (1.f - distmix)); + int farmix = Float2Int(256.0f * distmix); + + // if mixing at max or min range, skip crossfade (KDB: perf) + + if (!nearmix) + { + for ( int i = 0; i < outCount; i++ ) + { + x = pData[sampleIndex+1]; // right sample + + pOutput[i].left += (volume[0] * x)>>8; + pOutput[i].right += (volume[1] * x)>>8; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } + return; + } + + if (!farmix) + { + for ( int i = 0; i < outCount; i++ ) + { + x = pData[sampleIndex]; // left sample + + pOutput[i].left += (volume[0] * x)>>8; + pOutput[i].right += (volume[1] * x)>>8; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } + return; + } + + // crossfade left/right + + for ( int i = 0; i < outCount; i++ ) + { + l = pData[sampleIndex]; + r = pData[sampleIndex+1]; + + x = ( l + (((r - l) * farmix) >> 8) ); + + pOutput[i].left += (volume[0] * x)>>8; + pOutput[i].right += (volume[1] * x)>>8; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + +// distance variant wav (left is close, right is far) +// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in +// pData buffer, ensuring we can always provide 'outCount' samples. + +void SW_Mix16StereoDistVar_Interp( float distmix, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int x; + + fixedint sampleIndex = 0; + fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point + fixedint sampleFrac14 = FIX_28TO14(inputOffset); + + int first, second, interpl, interpr; + + + // distmix 0 - sound is near player (100% wav left) + // distmix 1.0 - sound is far from player (100% wav right) + + int nearmix = Float2Int(256.0f * (1.f - distmix)); + int farmix = Float2Int(256.0f * distmix); + + // if mixing at max or min range, skip crossfade (KDB: perf) + + if (!nearmix) + { + for ( int i = 0; i < outCount; i++ ) + { + first = (int)(pData[sampleIndex+1]); // right sample + second = (int)(pData[sampleIndex+3]); + interpr = first + (((second - first) * (int)sampleFrac14) >> 14); + + pOutput[i].left += (volume[0] * interpr)>>8; + pOutput[i].right += (volume[1] * interpr)>>8; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } + return; + } + + if (!farmix) + { + for ( int i = 0; i < outCount; i++ ) + { + first = (int)(pData[sampleIndex]); // left sample + second = (int)(pData[sampleIndex+2]); + interpl = first + (((second - first) * (int)sampleFrac14) >> 14); + + pOutput[i].left += (volume[0] * interpl)>>8; + pOutput[i].right += (volume[1] * interpl)>>8; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } + return; + } + + // crossfade left/right + + for ( int i = 0; i < outCount; i++ ) + { + first = (int)(pData[sampleIndex]); + second = (int)(pData[sampleIndex+2]); + interpl = first + (((second - first) * (int)sampleFrac14) >> 14); + + first = (int)(pData[sampleIndex+1]); + second = (int)(pData[sampleIndex+3]); + interpr = first + (((second - first) * (int)sampleFrac14) >> 14); + + // crossfade between left & right samples + + x = ( interpl + (((interpr - interpl) * farmix) >> 8) ); + + pOutput[i].left += (volume[0] * x) >> 8; + pOutput[i].right += (volume[1] * x) >> 8; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } +} + +void SW_Mix8Mono( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + // Not using pitch shift? + if ( rateScaleFix == FIX(1) ) + { + // native code + SND_PaintChannelFrom8( pOutput, volume, (byte *)pData, outCount ); + return; + } + + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + for ( int i = 0; i < outCount; i++ ) + { + pOutput[i].left += lscale[pData[sampleIndex]]; + pOutput[i].right += rscale[pData[sampleIndex]]; + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac); + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + + +// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in +// pData buffer, ensuring we can always provide 'outCount' samples. +void SW_Mix8Mono_Interp( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount) +{ + fixedint sampleIndex = 0; + fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point + fixedint sampleFrac14 = FIX_28TO14(inputOffset); + + int first, second, interp; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + // iterate 0th sample to outCount-1 sample + + for (int i = 0; i < outCount; i++ ) + { + // interpolate between first & second sample (the samples bordering sampleFrac12 fraction) + + first = (int)((signed char)(pData[sampleIndex])); + second = (int)((signed char)(pData[sampleIndex+1])); + + interp = first + ( ((second - first) * (int)sampleFrac14) >> 14 ); + + pOutput[i].left += lscale[interp & 0xFF]; // multiply by volume and convert to 16 bit + pOutput[i].right += rscale[interp & 0xFF]; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14); + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } +} + +void SW_Mix8Stereo( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + for ( int i = 0; i < outCount; i++ ) + { + pOutput[i].left += lscale[pData[sampleIndex]]; + pOutput[i].right += rscale[pData[sampleIndex+1]]; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + + +// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in +// pData buffer, ensuring we can always provide 'outCount' samples. +void SW_Mix8Stereo_Interp( portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount) +{ + fixedint sampleIndex = 0; + fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point + fixedint sampleFrac14 = FIX_28TO14(inputOffset); + + int first, second, interpl, interpr; + int *lscale, *rscale; + + lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT]; + rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; + + // iterate 0th sample to outCount-1 sample + + for (int i = 0; i < outCount; i++ ) + { + // interpolate between first & second sample (the samples bordering sampleFrac12 fraction) + + first = (int)((signed char)(pData[sampleIndex])); // left + second = (int)((signed char)(pData[sampleIndex+2])); + + interpl = first + ( ((second - first) * (int)sampleFrac14) >> 14 ); + + first = (int)((signed char)(pData[sampleIndex+1])); // right + second = (int)((signed char)(pData[sampleIndex+3])); + + interpr = first + ( ((second - first) * (int)sampleFrac14) >> 14 ); + + pOutput[i].left += lscale[interpl & 0xFF]; // multiply by volume and convert to 16 bit + pOutput[i].right += rscale[interpr & 0xFF]; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } +} + +void SW_Mix16Mono_Shift( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int vol0 = volume[0]; + int vol1 = volume[1]; + +#if !id386 + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + + for ( int i = 0; i < outCount; i++ ) + { + pOutput[i].left += (vol0 * (int)(pData[sampleIndex]))>>8; + pOutput[i].right += (vol1 * (int)(pData[sampleIndex]))>>8; + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac); + sampleFrac = FIX_FRACPART(sampleFrac); + } +#else + // in assembly, you can make this 32.32 instead of 4.28 and use the carry flag instead of masking + int rateScaleInt = FIX_INTPART(rateScaleFix); + unsigned int rateScaleFrac = FIX_FRACPART(rateScaleFix) << (32-FIX_BITS); + + __asm + { + mov eax, volume ; + movq mm0, DWORD PTR [eax] ; vol1, vol0 (32-bits each) + packssdw mm0, mm0 ; pack and replicate... vol1, vol0, vol1, vol0 (16-bits each) + //pxor mm7, mm7 ; mm7 is my zero register... + + xor esi, esi + mov eax, DWORD PTR [pOutput] ; store initial output ptr + mov edx, DWORD PTR [pData] ; store initial input ptr + mov ebx, inputOffset; + mov ecx, outCount; + +BEGINLOAD: + movd mm2, WORD PTR [edx+2*esi] ; load first piece of data from pData + punpcklwd mm2, mm2 ; 0, 0, pData_1st, pData_1st + + add ebx, rateScaleFrac ; do the crazy fixed integer math + adc esi, rateScaleInt + + movd mm3, WORD PTR [edx+2*esi] ; load second piece of data from pData + punpcklwd mm3, mm3 ; 0, 0, pData_2nd, pData_2nd + punpckldq mm2, mm3 ; pData_2nd, pData_2nd, pData_2nd, pData_2nd + + add ebx, rateScaleFrac ; do the crazy fixed integer math + adc esi, rateScaleInt + + movq mm3, mm2 ; copy the goods + pmullw mm2, mm0 ; pData_2nd*vol1, pData_2nd*vol0, pData_1st*vol1, pData_1st*vol0 (bits 0-15) + pmulhw mm3, mm0 ; pData_2nd*vol1, pData_2nd*vol0, pData_1st*vol1, pData_1st*vol0 (bits 16-31) + + movq mm4, mm2 ; copy + movq mm5, mm3 ; copy + + punpcklwd mm2, mm3 ; pData_1st*vol1, pData_1st*vol0 (bits 0-31) + punpckhwd mm4, mm5 ; pData_2nd*vol1, pData_2nd*vol0 (bits 0-31) + psrad mm2, 8 ; shift right by 8 + psrad mm4, 8 ; shift right by 8 + + add ecx, -2 ; decrement i-value + paddd mm2, QWORD PTR [eax] ; add to existing vals + paddd mm4, QWORD PTR [eax+8] ; + + movq QWORD PTR [eax], mm2 ; store back + movq QWORD PTR [eax+8], mm4 ; + + add eax, 10h ; + cmp ecx, 01h ; see if we can quit + jg BEGINLOAD ; Kipp Owens is a doof... + jl END ; Nick Shaffner is killing me... + + movsx edi, WORD PTR [edx+2*esi] ; load first 16 bit val and zero-extend + imul edi, vol0 ; multiply pData[sampleIndex] by volume[0] + sar edi, 08h ; divide by 256 + add DWORD PTR [eax], edi ; add to pOutput[i].left + + movsx edi, WORD PTR [edx+2*esi] ; load same 16 bit val and zero-extend (cuz I thrashed the reg) + imul edi, vol1 ; multiply pData[sampleIndex] by volume[1] + sar edi, 08h ; divide by 256 + add DWORD PTR [eax+04h], edi ; add to pOutput[i].right +END: + emms; + } +#endif +} + +void SW_Mix16Mono_NoShift( portable_samplepair_t *pOutput, int *volume, short *pData, int outCount ) +{ + int vol0 = volume[0]; + int vol1 = volume[1]; +#if !id386 + for ( int i = 0; i < outCount; i++ ) + { + int x = *pData++; + pOutput[i].left += (x * vol0) >> 8; + pOutput[i].right += (x * vol1) >> 8; + } +#else + __asm + { + mov eax, volume ; + movq mm0, DWORD PTR [eax] ; vol1, vol0 (32-bits each) + packssdw mm0, mm0 ; pack and replicate... vol1, vol0, vol1, vol0 (16-bits each) + //pxor mm7, mm7 ; mm7 is my zero register... + + mov eax, DWORD PTR [pOutput] ; store initial output ptr + mov edx, DWORD PTR [pData] ; store initial input ptr + mov ecx, outCount; + +BEGINLOAD: + movd mm2, WORD PTR [edx] ; load first piece o data from pData + punpcklwd mm2, mm2 ; 0, 0, pData_1st, pData_1st + add edx,2 ; move to the next sample + + movd mm3, WORD PTR [edx] ; load second piece o data from pData + punpcklwd mm3, mm3 ; 0, 0, pData_2nd, pData_2nd + punpckldq mm2, mm3 ; pData_2nd, pData_2nd, pData_2nd, pData_2nd + + add edx,2 ; move to the next sample + + movq mm3, mm2 ; copy the goods + pmullw mm2, mm0 ; pData_2nd*vol1, pData_2nd*vol0, pData_1st*vol1, pData_1st*vol0 (bits 0-15) + pmulhw mm3, mm0 ; pData_2nd*vol1, pData_2nd*vol0, pData_1st*vol1, pData_1st*vol0 (bits 16-31) + + movq mm4, mm2 ; copy + movq mm5, mm3 ; copy + + punpcklwd mm2, mm3 ; pData_1st*vol1, pData_1st*vol0 (bits 0-31) + punpckhwd mm4, mm5 ; pData_2nd*vol1, pData_2nd*vol0 (bits 0-31) + psrad mm2, 8 ; shift right by 8 + psrad mm4, 8 ; shift right by 8 + + add ecx, -2 ; decrement i-value + paddd mm2, QWORD PTR [eax] ; add to existing vals + paddd mm4, QWORD PTR [eax+8] ; + + movq QWORD PTR [eax], mm2 ; store back + movq QWORD PTR [eax+8], mm4 ; + + add eax, 10h ; + cmp ecx, 01h ; see if we can quit + jg BEGINLOAD ; I can cut and paste code! + jl END ; + + movsx edi, WORD PTR [edx] ; load first 16 bit val and zero-extend + mov esi,edi ; save a copy for the other channel + imul edi, vol0 ; multiply pData[sampleIndex] by volume[0] + sar edi, 08h ; divide by 256 + add DWORD PTR [eax], edi ; add to pOutput[i].left + + ; esi has a copy, use it now + imul esi, vol1 ; multiply pData[sampleIndex] by volume[1] + sar esi, 08h ; divide by 256 + add DWORD PTR [eax+04h], esi ; add to pOutput[i].right +END: + emms; + } +#endif +} + +void SW_Mix16Mono( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + if ( rateScaleFix == FIX(1) ) + { + SW_Mix16Mono_NoShift( pOutput, volume, pData, outCount ); + } + else + { + SW_Mix16Mono_Shift( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + } +} + +// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in +// pData buffer, ensuring we can always provide 'outCount' samples. + +void SW_Mix16Mono_Interp( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + fixedint sampleIndex = 0; + fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point + fixedint sampleFrac14 = FIX_28TO14(inputOffset); + + int first, second, interp; + + for ( int i = 0; i < outCount; i++ ) + { + first = (int)(pData[sampleIndex]); + second = (int)(pData[sampleIndex+1]); + + interp = first + (((second - first) * (int)sampleFrac14) >> 14); + + pOutput[i].left += (volume[0] * interp) >> 8; + pOutput[i].right += (volume[1] * interp) >> 8; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14); + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } +} + +void SW_Mix16Stereo( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + int sampleIndex = 0; + fixedint sampleFrac = inputOffset; + + for ( int i = 0; i < outCount; i++ ) + { + pOutput[i].left += (volume[0] * (int)(pData[sampleIndex]))>>8; + pOutput[i].right += (volume[1] * (int)(pData[sampleIndex+1]))>>8; + + sampleFrac += rateScaleFix; + sampleIndex += FIX_INTPART(sampleFrac)<<1; + sampleFrac = FIX_FRACPART(sampleFrac); + } +} + +// interpolating pitch shifter - sample(s) from preceding buffer are preloaded in +// pData buffer, ensuring we can always provide 'outCount' samples. + +void SW_Mix16Stereo_Interp( portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + fixedint sampleIndex = 0; + fixedint rateScaleFix14 = FIX_28TO14(rateScaleFix); // convert 28 bit fixed point to 14 bit fixed point + fixedint sampleFrac14 = FIX_28TO14(inputOffset); + + int first, second, interpl, interpr; + + for ( int i = 0; i < outCount; i++ ) + { + first = (int)(pData[sampleIndex]); + second = (int)(pData[sampleIndex+2]); + + interpl = first + (((second - first) * (int)sampleFrac14) >> 14); + + first = (int)(pData[sampleIndex+1]); + second = (int)(pData[sampleIndex+3]); + + interpr = first + (((second - first) * (int)sampleFrac14) >> 14); + + pOutput[i].left += (volume[0] * interpl) >> 8; + pOutput[i].right += (volume[1] * interpr) >> 8; + + sampleFrac14 += rateScaleFix14; + sampleIndex += FIX_INTPART14(sampleFrac14)<<1; + sampleFrac14 = FIX_FRACPART14(sampleFrac14); + } +} +// return true if mixer should use high quality pitch interpolation for this sound + +bool FUseHighQualityPitch( channel_t *pChannel ) +{ + // do not use interpolating pitch shifter if: + // low quality flag set on sound (ie: wave name is prepended with CHAR_FAST_PITCH) + // or pitch has no fractional part + // or snd_pitchquality is 0 + if ( !snd_pitchquality.GetInt() || pChannel->flags.bfast_pitch ) + return false; + + return ( (pChannel->pitch != floor(pChannel->pitch)) ); +} + +//=============================================================================== +// DISPATCHERS FOR MIXING ROUTINES +//=============================================================================== +void Mix8MonoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix8Mono_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + else + SW_Mix8Mono( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); +} + +void Mix16MonoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix16Mono_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + else + // fast native coded mixers with lower quality pitch shift + SW_Mix16Mono( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); +} + +void Mix8StereoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, byte *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + switch ( pChannel->wavtype ) + { + case CHAR_DOPPLER: + SW_Mix8StereoDopplerLeft( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + SW_Mix8StereoDopplerRight( pOutput, &volume[IFRONT_LEFTD], pData, inputOffset, rateScaleFix, outCount ); + break; + + case CHAR_DIRECTIONAL: + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix8StereoDirectional_Interp( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + else + SW_Mix8StereoDirectional( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + break; + + case CHAR_DISTVARIANT: + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix8StereoDistVar_Interp( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount); + else + SW_Mix8StereoDistVar( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount); + break; + + case CHAR_OMNI: + // non directional stereo - all channel volumes are the same + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix8Stereo_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + else + SW_Mix8Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + break; + + default: + case CHAR_SPATIALSTEREO: + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix8Stereo_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + else + SW_Mix8Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + break; + } +} + + +void Mix16StereoWavtype( channel_t *pChannel, portable_samplepair_t *pOutput, int *volume, short *pData, int inputOffset, fixedint rateScaleFix, int outCount ) +{ + switch ( pChannel->wavtype ) + { + case CHAR_DOPPLER: + SW_Mix16StereoDopplerLeft( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + SW_Mix16StereoDopplerRight( pOutput, &volume[IFRONT_LEFTD], pData, inputOffset, rateScaleFix, outCount ); + break; + + case CHAR_DIRECTIONAL: + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix16StereoDirectional_Interp( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + else + SW_Mix16StereoDirectional( pChannel->dspface, pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + break; + + case CHAR_DISTVARIANT: + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix16StereoDistVar_Interp( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount); + else + SW_Mix16StereoDistVar( pChannel->distmix, pOutput, volume, pData, inputOffset, rateScaleFix, outCount); + break; + + case CHAR_OMNI: + // non directional stereo - all channel volumes are same + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix16Stereo_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + else + SW_Mix16Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + break; + + default: + case CHAR_SPATIALSTEREO: + if ( FUseHighQualityPitch( pChannel ) ) + SW_Mix16Stereo_Interp( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + else + SW_Mix16Stereo( pOutput, volume, pData, inputOffset, rateScaleFix, outCount ); + break; + } +} + + +//=============================================================================== +// Client entity mouth movement code. Set entity mouthopen variable, based +// on the sound envelope of the voice channel playing. +// KellyB 10/22/97 +//=============================================================================== + + +extern IBaseClientDLL *g_ClientDLL; + +// called when voice channel is first opened on this entity +static CMouthInfo *GetMouthInfoForChannel( channel_t *pChannel ) +{ +#ifndef DEDICATED + // If it's a sound inside the client UI, ask the client for the mouthinfo + if ( pChannel->soundsource == SOUND_FROM_UI_PANEL ) + return g_ClientDLL ? g_ClientDLL->GetClientUIMouthInfo() : NULL; +#endif + + int mouthentity = pChannel->speakerentity == -1 ? pChannel->soundsource : pChannel->speakerentity; + + IClientEntity *pClientEntity = entitylist->GetClientEntity( mouthentity ); + + if( !pClientEntity ) + return NULL; + + return pClientEntity->GetMouth(); +} + +void SND_InitMouth( channel_t *pChannel ) +{ + if ( SND_IsMouth( pChannel ) ) + { + CMouthInfo *pMouth = GetMouthInfoForChannel(pChannel); + // init mouth movement vars + if ( pMouth ) + { + pMouth->mouthopen = 0; + pMouth->sndavg = 0; + pMouth->sndcount = 0; + if ( pChannel->sfx->pSource && pChannel->sfx->pSource->GetSentence() ) + { + pMouth->AddSource( pChannel->sfx->pSource, pChannel->flags.m_bIgnorePhonemes ); + } + } + } +} + +// called when channel stops + +void SND_CloseMouth(channel_t *pChannel) +{ + if ( SND_IsMouth( pChannel ) ) + { + CMouthInfo *pMouth = GetMouthInfoForChannel(pChannel); + if ( pMouth ) + { + // shut mouth + int idx = pMouth->GetIndexForSource( pChannel->sfx->pSource ); + + if ( idx != UNKNOWN_VOICE_SOURCE ) + { + pMouth->RemoveSourceByIndex(idx); + } + else + { + pMouth->ClearVoiceSources(); + } + pMouth->mouthopen = 0; + } + } +} + +#define CAVGSAMPLES 10 +// need this to make the debug code below work. +//#include "snd_wave_source.h" +void SND_MoveMouth8( channel_t *ch, CAudioSource *pSource, int count ) +{ + int data; + char *pdata = NULL; + int i; + int savg; + int scount; + + CMouthInfo *pMouth = GetMouthInfoForChannel( ch ); + + if ( !pMouth ) + return; + + if ( pSource->GetSentence() ) + { + int idx = pMouth->GetIndexForSource( pSource ); + + if ( idx == UNKNOWN_VOICE_SOURCE ) + { + if ( pMouth->AddSource( pSource, ch->flags.m_bIgnorePhonemes ) == NULL ) + { + DevMsg( 1, "out of voice sources, won't lipsync %s\n", ch->sfx->getname() ); +#if 0 + for ( int i = 0; i < pMouth->GetNumVoiceSources(); i++ ) + { + CVoiceData *pVoice = pMouth->GetVoiceSource(i); + CAudioSourceWave *pWave = dynamic_cast<CAudioSourceWave *>(pVoice->GetSource()); + const char *pName = "unknown"; + if ( pWave && pWave->GetName() ) + pName = pWave->GetName(); + Msg("Playing %s...\n", pName ); + } +#endif + } + } + else + { + // Update elapsed time from mixer + CVoiceData *vd = pMouth->GetVoiceSource( idx ); + Assert( vd ); + if ( vd ) + { + Assert( pSource->SampleRate() > 0 ); + + float elapsed = ( float )ch->pMixer->GetSamplePosition() / ( float )pSource->SampleRate(); + + vd->SetElapsedTime( elapsed ); + } + } + } + + if ( IsX360() ) + { + // not supporting because data is assumed to be 8 bit and bypasses mixer (decoding) + return; + } + + if ( pMouth->NeedsEnvelope() ) + { + int availableSamples = pSource->GetOutputData((void**)&pdata, ch->pMixer->GetSamplePosition(), count, NULL ); + + if( pdata == NULL ) + return; + + i = 0; + scount = pMouth->sndcount; + savg = 0; + + while ( i < availableSamples && scount < CAVGSAMPLES ) + { + data = pdata[i]; + savg += abs(data); + + i += 80 + ((byte)data & 0x1F); + scount++; + } + + pMouth->sndavg += savg; + pMouth->sndcount = (byte) scount; + + if ( pMouth->sndcount >= CAVGSAMPLES ) + { + pMouth->mouthopen = pMouth->sndavg / CAVGSAMPLES; + pMouth->sndavg = 0; + pMouth->sndcount = 0; + } + } + else + { + pMouth->mouthopen = 0; + } +} + + +void SND_UpdateMouth( channel_t *pChannel ) +{ + CMouthInfo *m = GetMouthInfoForChannel( pChannel ); + if ( !m ) + return; + + if ( pChannel->sfx ) + { + m->AddSource( pChannel->sfx->pSource, pChannel->flags.m_bIgnorePhonemes ); + } +} + + +void SND_ClearMouth( channel_t *pChannel ) +{ + CMouthInfo *m = GetMouthInfoForChannel( pChannel ); + if ( !m ) + return; + + if ( pChannel->sfx ) + { + m->RemoveSource( pChannel->sfx->pSource ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pChannel - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool SND_IsMouth( channel_t *pChannel ) +{ +#ifndef DEDICATED + if ( pChannel->soundsource == SOUND_FROM_UI_PANEL ) + return true; +#endif + + if ( !entitylist ) + { + return false; + } + + if ( pChannel->entchannel == CHAN_VOICE || pChannel->entchannel == CHAN_VOICE2 ) + { + return true; + } + + if ( pChannel->sfx && + pChannel->sfx->pSource && + pChannel->sfx->pSource->GetSentence() ) + { + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pChannel - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool SND_ShouldPause( channel_t *pChannel ) +{ + return pChannel->flags.m_bShouldPause; +} + +//=============================================================================== +// Movie recording support +//=============================================================================== + +void SND_RecordInit() +{ + g_paintedtime = 0; + g_soundtime = 0; + + // TMP Wave file supports stereo only, so force stereo + if ( snd_surround.GetInt() != 2 ) + { + snd_surround.SetValue( 2 ); + } +} + +void SND_MovieStart( void ) +{ + if ( IsX360() ) + return; + + if ( !cl_movieinfo.IsRecording() ) + return; + + SND_RecordInit(); + + // 44k: engine playback rate is now 44100...changed from 22050 + if ( cl_movieinfo.DoWav() ) + { + WaveCreateTmpFile( cl_movieinfo.moviename, SOUND_DMA_SPEED, 16, 2 ); + } +} + +void SND_MovieEnd( void ) +{ + if ( IsX360() ) + return; + + if ( !cl_movieinfo.IsRecording() ) + { + return; + } + + if ( cl_movieinfo.DoWav() ) + { + WaveFixupTmpFile( cl_movieinfo.moviename ); + } +} + +bool SND_IsRecording() +{ + return ( ( IsReplayRendering() || cl_movieinfo.IsRecording() ) && !Con_IsVisible() ); +} + + + +extern IVideoRecorder *g_pVideoRecorder; +void SND_RecordBuffer( void ) +{ + if ( IsX360() ) + return; + + if ( !SND_IsRecording() ) + return; + + int i; + int val; + int bufferSize = snd_linear_count * sizeof(short); + short *tmp = (short *)_alloca( bufferSize ); + + for (i=0 ; i<snd_linear_count ; i+=2) + { + val = (snd_p[i]*snd_vol)>>8; + tmp[i] = CLIP(val); + + val = (snd_p[i+1]*snd_vol)>>8; + tmp[i+1] = CLIP(val); + } + + if ( IsReplayRendering() ) + { +#if defined( REPLAY_ENABLED ) + extern IClientReplayContext *g_pClientReplayContext; + IReplayMovieRenderer *pMovieRenderer = g_pClientReplayContext->GetMovieRenderer(); + if ( IsReplayRendering() && pMovieRenderer && pMovieRenderer->IsAudioSyncFrame() ) + { + pMovieRenderer->RenderAudio( (unsigned char *)tmp, bufferSize, snd_linear_count ); + } +#endif + } + else + { + if ( cl_movieinfo.DoWav() ) + { + WaveAppendTmpFile( cl_movieinfo.moviename, tmp, 16, snd_linear_count ); + } + + if ( cl_movieinfo.DoVideoSound() ) + { + g_pVideoRecorder->AppendAudioSamples( tmp, bufferSize ); + } + } +} |