You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
4293 lines
124 KiB
4293 lines
124 KiB
//========= 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" |
|
uintp 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 = (uintp) 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 == (uintp)(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 ); |
|
} |
|
} |
|
}
|
|
|