//========= 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< g_paintBuffers; // pointer to current paintbuffer (front and reare), used by all mixing, upsampling and dsp routines portable_samplepair_t *g_curpaintbuffer = NULL; portable_samplepair_t *g_currearpaintbuffer = NULL; portable_samplepair_t *g_curcenterpaintbuffer = NULL; bool g_bdirectionalfx; bool g_bDspOff; float g_dsp_volume; // dsp performance timing unsigned g_snd_call_time_debug = 0; unsigned g_snd_time_debug = 0; unsigned g_snd_count_debug = 0; unsigned g_snd_samplecount = 0; unsigned g_snd_frametime = 0; unsigned g_snd_frametime_total = 0; int g_snd_profile_type = 0; // type 1 dsp, type 2 mixer, type 3 load sound, type 4 all sound #define FILTERTYPE_NONE 0 #define FILTERTYPE_LINEAR 1 #define FILTERTYPE_CUBIC 2 // filter memory for upsampling portable_samplepair_t cubicfilter1[3] = {{0,0},{0,0},{0,0}}; portable_samplepair_t cubicfilter2[3] = {{0,0},{0,0},{0,0}}; portable_samplepair_t linearfilter1[1] = {{0,0}}; portable_samplepair_t linearfilter2[1] = {{0,0}}; portable_samplepair_t linearfilter3[1] = {{0,0}}; portable_samplepair_t linearfilter4[1] = {{0,0}}; portable_samplepair_t linearfilter5[1] = {{0,0}}; portable_samplepair_t linearfilter6[1] = {{0,0}}; portable_samplepair_t linearfilter7[1] = {{0,0}}; portable_samplepair_t linearfilter8[1] = {{0,0}}; int snd_scaletable[SND_SCALE_LEVELS][256]; // 32k*4 = 128K int *snd_p, snd_linear_count, snd_vol; short *snd_out; extern int DSP_Alloc( int ipset, float xfade, int cchan ); bool DSP_CheckDspAutoEnabled( void ); int Get_idsp_room ( void ); int dsp_room_GetInt ( void ); void DSP_SetDspAuto( int dsp_preset ); bool DSP_CheckDspAutoEnabled( void ); void MIX_ScalePaintBuffer( int bufferIndex, int count, float fgain ); bool IsReplayRendering() { #if defined( REPLAY_ENABLED ) return g_pReplayMovieManager && g_pReplayMovieManager->IsRendering(); #else return false; #endif } //----------------------------------------------------------------------------- // Free allocated memory buffers //----------------------------------------------------------------------------- void MIX_FreeAllPaintbuffers(void) { if ( g_paintBuffers.Count() ) { if ( g_temppaintbuffer ) { _aligned_free( g_temppaintbuffer ); g_temppaintbuffer = NULL; } for ( int i = 0; i < g_paintBuffers.Count(); i++ ) { if ( g_paintBuffers[i].pbuf ) { _aligned_free( g_paintBuffers[i].pbuf ); } if ( g_paintBuffers[i].pbufrear ) { _aligned_free( g_paintBuffers[i].pbufrear ); } if ( g_paintBuffers[i].pbufcenter ) { _aligned_free( g_paintBuffers[i].pbufcenter ); } } g_paintBuffers.RemoveAll(); } } void MIX_InitializePaintbuffer( paintbuffer_t *pPaintBuffer, bool bSurround, bool bSurroundCenter ) { V_memset( pPaintBuffer, 0, sizeof( *pPaintBuffer ) ); pPaintBuffer->pbuf = (portable_samplepair_t *)_aligned_malloc( PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t), 16 ); V_memset( pPaintBuffer->pbuf, 0, PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t) ); if ( bSurround ) { pPaintBuffer->pbufrear = (portable_samplepair_t *)_aligned_malloc( PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t), 16 ); V_memset( pPaintBuffer->pbufrear, 0, PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t) ); } if ( bSurroundCenter ) { pPaintBuffer->pbufcenter = (portable_samplepair_t *)_aligned_malloc( PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t), 16 ); V_memset( pPaintBuffer->pbufcenter, 0, PAINTBUFFER_MEM_SIZE*sizeof(portable_samplepair_t) ); } } //----------------------------------------------------------------------------- // Allocate memory buffers // Initialize paintbuffers array, set current paint buffer to main output buffer SOUND_BUFFER_PAINT //----------------------------------------------------------------------------- bool MIX_InitAllPaintbuffers(void) { bool bSurround; bool bSurroundCenter; bSurroundCenter = g_AudioDevice->IsSurroundCenter(); bSurround = g_AudioDevice->IsSurround() || bSurroundCenter; g_temppaintbuffer = (portable_samplepair_t*)_aligned_malloc( TEMP_COPY_BUFFER_SIZE*sizeof(portable_samplepair_t), 16 ); V_memset( g_temppaintbuffer, 0, TEMP_COPY_BUFFER_SIZE*sizeof(portable_samplepair_t) ); while ( g_paintBuffers.Count() < SOUND_BUFFER_BASETOTAL ) { int nIndex = g_paintBuffers.AddToTail(); MIX_InitializePaintbuffer( &(g_paintBuffers[ nIndex ]), bSurround, bSurroundCenter ); } g_paintbuffer = g_paintBuffers[SOUND_BUFFER_PAINT].pbuf; // buffer flags g_paintBuffers[SOUND_BUFFER_ROOM].flags = SOUND_BUSS_ROOM; g_paintBuffers[SOUND_BUFFER_FACING].flags = SOUND_BUSS_FACING; g_paintBuffers[SOUND_BUFFER_FACINGAWAY].flags = SOUND_BUSS_FACINGAWAY; g_paintBuffers[SOUND_BUFFER_SPEAKER].flags = SOUND_BUSS_SPEAKER; g_paintBuffers[SOUND_BUFFER_DRY].flags = SOUND_BUSS_DRY; // buffer surround sound flag g_paintBuffers[SOUND_BUFFER_PAINT].fsurround = bSurround; g_paintBuffers[SOUND_BUFFER_FACING].fsurround = bSurround; g_paintBuffers[SOUND_BUFFER_FACINGAWAY].fsurround = bSurround; g_paintBuffers[SOUND_BUFFER_DRY].fsurround = bSurround; // buffer 5 channel surround sound flag g_paintBuffers[SOUND_BUFFER_PAINT].fsurround_center = bSurroundCenter; g_paintBuffers[SOUND_BUFFER_FACING].fsurround_center = bSurroundCenter; g_paintBuffers[SOUND_BUFFER_FACINGAWAY].fsurround_center = bSurroundCenter; g_paintBuffers[SOUND_BUFFER_DRY].fsurround_center = bSurroundCenter; // room buffer mixes down to mono or stereo, never to 4 or 5 ch g_paintBuffers[SOUND_BUFFER_ROOM].fsurround = false; g_paintBuffers[SOUND_BUFFER_ROOM].fsurround_center = false; // speaker buffer mixes to mono g_paintBuffers[SOUND_BUFFER_SPEAKER].fsurround = false; g_paintBuffers[SOUND_BUFFER_SPEAKER].fsurround_center = false; MIX_SetCurrentPaintbuffer( SOUND_BUFFER_PAINT ); return true; } // called before loading samples to mix - cap the mix rate (ie: pitch) so that // we never overflow the mix copy buffer. double MIX_GetMaxRate( double rate, int sampleCount ) { if (rate <= 2.0) return rate; // copybuf_bytes = rate_max * samples_max * samplesize_max // so: // rate_max = copybuf_bytes / (samples_max * samplesize_max ) double samplesize_max = 4.0; // stereo 16bit samples double copybuf_bytes = (double)(TEMP_COPY_BUFFER_SIZE * sizeof(portable_samplepair_t)); double samples_max = (double)(PAINTBUFFER_SIZE); double rate_max = copybuf_bytes / (samples_max * samplesize_max); // make sure sampleCount is never greater than paintbuffer samples // (this should have been set up in MIX_PaintChannels) Assert (sampleCount <= PAINTBUFFER_SIZE); return fpmin( rate, rate_max ); } // Transfer (endtime - lpaintedtime) stereo samples in pfront out to hardware // pfront - pointer to stereo paintbuffer - 32 bit samples, interleaved stereo // lpaintedtime - total number of 32 bit stereo samples previously output to hardware // endtime - total number of 32 bit stereo samples currently mixed in paintbuffer void S_TransferStereo16( void *pOutput, const portable_samplepair_t *pfront, int lpaintedtime, int endtime ) { int lpos; if ( IsX360() ) { // not the right path for 360 Assert( 0 ); return; } Assert( pOutput ); snd_vol = S_GetMasterVolume()*256; snd_p = (int *)pfront; // get size of output buffer in full samples (LR pairs) int samplePairCount = g_AudioDevice->DeviceSampleCount() >> 1; int sampleMask = samplePairCount - 1; bool bShouldPlaySound = !cl_movieinfo.IsRecording() && !IsReplayRendering(); while ( lpaintedtime < endtime ) { // pbuf can hold 16384, 16 bit L/R samplepairs. // lpaintedtime - where to start painting into dma buffer. // (modulo size of dma buffer for current position). // handle recirculating buffer issues // lpos - samplepair index into dma buffer. First samplepair from paintbuffer to be xfered here. lpos = lpaintedtime & sampleMask; // snd_out is L/R sample index into dma buffer. First L sample from paintbuffer goes here. snd_out = (short *)pOutput + (lpos<<1); // snd_linear_count is number of samplepairs between end of dma buffer and xfer start index. snd_linear_count = samplePairCount - lpos; // clamp snd_linear_count to be only as many samplepairs premixed if ( snd_linear_count > endtime - lpaintedtime ) { // endtime - lpaintedtime = number of premixed sample pairs ready for xfer. snd_linear_count = endtime - lpaintedtime; } // snd_linear_count is now number of mono 16 bit samples (L and R) to xfer. snd_linear_count <<= 1; // write a linear blast of samples SND_RecordBuffer(); if ( bShouldPlaySound ) { // transfer 16bit samples from snd_p into snd_out, multiplying each sample by volume. Snd_WriteLinearBlastStereo16(); } // advance paintbuffer pointer snd_p += snd_linear_count; // advance lpaintedtime by number of samplepairs just xfered. lpaintedtime += (snd_linear_count>>1); } } // Transfer contents of main paintbuffer pfront out to // device. Perform volume multiply on each sample. void S_TransferPaintBuffer(void *pOutput, const portable_samplepair_t *pfront, int lpaintedtime, int endtime) { int out_idx; // mono sample index int count; // number of mono samples to output int out_mask; int step; int val; int nSoundVol; const int *p; if ( IsX360() ) { // not the right path for 360 Assert( 0 ); return; } Assert( pOutput ); p = (const int *) pfront; count = ((endtime - lpaintedtime) * g_AudioDevice->DeviceChannels()); out_mask = g_AudioDevice->DeviceSampleCount() - 1; // 44k: remove old 22k sound support << HISPEED_DMA // out_idx = ((paintedtime << HISPEED_DMA) * g_AudioDevice->DeviceChannels()) & out_mask; out_idx = (lpaintedtime * g_AudioDevice->DeviceChannels()) & out_mask; step = 3 - g_AudioDevice->DeviceChannels(); // mono output buffer - step 2, stereo - step 1 nSoundVol = S_GetMasterVolume()*256; if (g_AudioDevice->DeviceSampleBits() == 16) { short *out = (short *) pOutput; while (count--) { val = (*p * nSoundVol) >> 8; p+= step; val = CLIP(val); out[out_idx] = val; out_idx = (out_idx + 1) & out_mask; } } else if (g_AudioDevice->DeviceSampleBits() == 8) { unsigned char *out = (unsigned char *) pOutput; while (count--) { val = (*p * nSoundVol) >> 8; p+= step; val = CLIP(val); out[out_idx] = (val>>8) + 128; out_idx = (out_idx + 1) & out_mask; } } } /* =============================================================================== CHANNEL MIXING =============================================================================== */ // free channel so that it may be allocated by the // next request to play a sound. If sound is a // word in a sentence, release the sentence. // Works for static, dynamic, sentence and stream sounds void S_FreeChannel(channel_t *ch) { // Don't reenter in here (can happen inside voice code). if ( ch->flags.m_bIsFreeingChannel ) return; ch->flags.m_bIsFreeingChannel = true; SND_CloseMouth(ch); g_pSoundServices->OnSoundStopped( ch->guid, ch->soundsource, ch->entchannel, ch->sfx->getname() ); ch->flags.isSentence = false; // Msg("End sound %s\n", ch->sfx->getname() ); delete ch->pMixer; ch->pMixer = NULL; ch->sfx = NULL; // zero all data in channel g_ActiveChannels.Remove( ch ); Q_memset(ch, 0, sizeof(channel_t)); } // Mix all channels into active paintbuffers until paintbuffer is full or 'endtime' is reached. // endtime: time in 44khz samples to mix // rate: ignore samples which are not natively at this rate (for multipass mixing/filtering) // if rate == SOUND_ALL_RATES then mix all samples this pass // flags: if SOUND_MIX_DRY, then mix only samples with channel flagged as 'dry' // outputRate: target mix rate for all samples. Note, if outputRate = SOUND_DMA_SPEED, then // this routine will fill the paintbuffer to endtime. Otherwise, fewer samples are mixed. // if (endtime - paintedtime) is not aligned on boundaries of 4, // we'll miss data if outputRate < SOUND_DMA_SPEED! void MIX_MixChannelsToPaintbuffer( CChannelList &list, int endtime, int flags, int rate, int outputRate ) { VPROF( "MixChannelsToPaintbuffer" ); int i; int sampleCount; tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s c:%d %d/%d", __FUNCTION__, list.Count(), rate, outputRate ); // mix each channel into paintbuffer // validate parameters Assert( outputRate <= SOUND_DMA_SPEED ); Assert( !((endtime - g_paintedtime) & 0x3) || (outputRate == SOUND_DMA_SPEED) ); // make sure we're not discarding data // 44k: try to mix this many samples at outputRate sampleCount = ( endtime - g_paintedtime ) / ( SOUND_DMA_SPEED / outputRate ); if ( sampleCount <= 0 ) return; // Apply a global pitch shift if we're playing back a time-scaled replay float flGlobalPitchScale = 1.0f; #if defined( REPLAY_ENABLED ) extern IDemoPlayer *g_pReplayDemoPlayer; if ( demoplayer->IsPlayingBack() && demoplayer == g_pReplayDemoPlayer ) { // adjust time scale if playing back demo flGlobalPitchScale = demoplayer->GetPlaybackTimeScale(); } #endif for ( i = list.Count(); --i >= 0; ) { channel_t *ch = list.GetChannel( i ); Assert( ch->sfx ); // must never have a 'dry' and 'speaker' set - causes double mixing & double data reading Assert ( !( ( ch->flags.bdry && ch->flags.bSpeaker ) || ( ch->flags.bdry && ch->special_dsp != 0 ) ) ); // if mixing with SOUND_MIX_DRY flag, ignore (don't even load) all channels not flagged as 'dry' if ( flags == SOUND_MIX_DRY ) { if ( !ch->flags.bdry ) continue; } // if mixing with SOUND_MIX_WET flag, ignore (don't even load) all channels flagged as 'dry' or 'speaker' if ( flags == SOUND_MIX_WET ) { if ( ch->flags.bdry || ch->flags.bSpeaker || ch->special_dsp != 0 ) continue; } // if mixing with SOUND_MIX_SPEAKER flag, ignore (don't even load) all channels not flagged as 'speaker' if ( flags == SOUND_MIX_SPEAKER ) { if ( !ch->flags.bSpeaker ) continue; } // if mixing with SOUND_MIX_SPEAKER flag, ignore (don't even load) all channels not flagged as 'speaker' if ( flags == SOUND_MIX_SPECIAL_DSP ) { if ( ch->special_dsp == 0 ) continue; } // multipass mixing - only mix samples of specified sample rate switch ( rate ) { case SOUND_11k: case SOUND_22k: case SOUND_44k: if ( rate != ch->sfx->pSource->SampleRate() ) continue; break; default: case SOUND_ALL_RATES: break; } // Tracker 20771, if breen is speaking through the monitor, the client doesn't have an entity // for the "soundsource" but we still need the lipsync to pause if the game is paused. Therefore // I changed SND_IsMouth to look for any .wav on any channels which has sentence data bool bIsMouth = SND_IsMouth(ch); bool bShouldPause = IsX360() ? !ch->sfx->m_bIsUISound : bIsMouth; // Tracker 14637: Pausing the game pauses voice sounds, but not other sounds... if ( bShouldPause && g_pSoundServices->IsGamePaused() ) { continue; } if ( bIsMouth ) { if ( ( ch->soundsource == SOUND_FROM_UI_PANEL ) || entitylist->GetClientEntity(ch->soundsource) || ( ch->flags.bSpeaker && entitylist->GetClientEntity( ch->speakerentity ) ) ) { // UNDONE: recode this as a member function of CAudioMixer SND_MoveMouth8(ch, ch->sfx->pSource, sampleCount); } } // mix channel to all active paintbuffers: // mix 'dry' sounds only to dry paintbuffer. // mix 'speaker' sounds only to speaker paintbuffer. // mix all other sounds between room, facing & facingaway paintbuffers // NOTE: must be called once per channel only - consecutive calls retrieve additional data. float flPitch = ch->pitch; ch->pitch *= flGlobalPitchScale; if (list.IsQuashed(i)) { // If the sound has been silenced as a performance heuristic, quash it. ch->pMixer->SkipSamples( ch, sampleCount, outputRate, 0 ); // DevMsg("Quashed channel %d (%s)\n", i, ch->sfx->GetFileName()); } else { tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "MixDataToDevice" ); ch->pMixer->MixDataToDevice( g_AudioDevice, ch, sampleCount, outputRate, 0 ); } // restore to original pitch settings ch->pitch = flPitch; if ( !ch->pMixer->ShouldContinueMixing() ) { S_FreeChannel( ch ); list.RemoveChannelFromList(i); } if ( (ch->nFreeChannelAtSampleTime > 0 && (int)ch->nFreeChannelAtSampleTime <= endtime) ) { S_FreeChannel( ch ); list.RemoveChannelFromList(i); } } } // pass in index -1...count+2, return pointer to source sample in either paintbuffer or delay buffer inline portable_samplepair_t * S_GetNextpFilter(int i, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem) { // The delay buffer is assumed to precede the paintbuffer by 6 duplicated samples if (i == -1) return (&(pfiltermem[0])); if (i == 0) return (&(pfiltermem[1])); if (i == 1) return (&(pfiltermem[2])); // return from paintbuffer, where samples are doubled. // even samples are to be replaced with interpolated value. return (&(pbuffer[(i-2)*2 + 1])); } // pass forward over passed in buffer and cubic interpolate all odd samples // pbuffer: buffer to filter (in place) // prevfilter: filter memory. NOTE: this must match the filtertype ie: filtercubic[] for FILTERTYPE_CUBIC // if NULL then perform no filtering. UNDONE: should have a filter memory array type // count: how many samples to upsample. will become count*2 samples in buffer, in place. void S_Interpolate2xCubic( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) { // implement cubic interpolation on 2x upsampled buffer. Effectively delays buffer contents by 2 samples. // pbuffer: contains samples at 0, 2, 4, 6... // temppaintbuffer is temp buffer, of same or larger size than a paintbuffer, used to store processed values // count: number of samples to process in buffer ie: how many samples at 0, 2, 4, 6... // finpos is the fractional, inpos the integer part. // finpos = 0.5 for upsampling by 2x // inpos is the position of the sample // xm1 = x [inpos - 1]; // x0 = x [inpos + 0]; // x1 = x [inpos + 1]; // x2 = x [inpos + 2]; // a = (3 * (x0-x1) - xm1 + x2) / 2; // b = 2*x1 + xm1 - (5*x0 + x2) / 2; // c = (x1 - xm1) / 2; // y [outpos] = (((a * finpos) + b) * finpos + c) * finpos + x0; int i, upCount = count << 1; int a, b, c; int xm1, x0, x1, x2; portable_samplepair_t *psamp0; portable_samplepair_t *psamp1; portable_samplepair_t *psamp2; portable_samplepair_t *psamp3; int outpos = 0; Assert (upCount <= PAINTBUFFER_SIZE); // pfiltermem holds 6 samples from previous buffer pass // process 'count' samples for ( i = 0; i < count; i++) { // get source sample pointer psamp0 = S_GetNextpFilter(i-1, pbuffer, pfiltermem); psamp1 = S_GetNextpFilter(i, pbuffer, pfiltermem); psamp2 = S_GetNextpFilter(i+1, pbuffer, pfiltermem); psamp3 = S_GetNextpFilter(i+2, pbuffer, pfiltermem); // write out original sample to interpolation buffer g_temppaintbuffer[outpos++] = *psamp1; // get all left samples for interpolation window xm1 = psamp0->left; x0 = psamp1->left; x1 = psamp2->left; x2 = psamp3->left; // interpolate a = (3 * (x0-x1) - xm1 + x2) / 2; b = 2*x1 + xm1 - (5*x0 + x2) / 2; c = (x1 - xm1) / 2; // write out interpolated sample g_temppaintbuffer[outpos].left = a/8 + b/4 + c/2 + x0; // get all right samples for window xm1 = psamp0->right; x0 = psamp1->right; x1 = psamp2->right; x2 = psamp3->right; // interpolate a = (3 * (x0-x1) - xm1 + x2) / 2; b = 2*x1 + xm1 - (5*x0 + x2) / 2; c = (x1 - xm1) / 2; // write out interpolated sample, increment output counter g_temppaintbuffer[outpos++].right = a/8 + b/4 + c/2 + x0; Assert( outpos <= TEMP_COPY_BUFFER_SIZE ); } Assert(cfltmem >= 3); // save last 3 samples from paintbuffer pfiltermem[0] = pbuffer[upCount - 5]; pfiltermem[1] = pbuffer[upCount - 3]; pfiltermem[2] = pbuffer[upCount - 1]; // copy temppaintbuffer back into paintbuffer for (i = 0; i < upCount; i++) pbuffer[i] = g_temppaintbuffer[i]; } // pass forward over passed in buffer and linearly interpolate all odd samples // pbuffer: buffer to filter (in place) // prevfilter: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR // if NULL then perform no filtering. // count: how many samples to upsample. will become count*2 samples in buffer, in place. void S_Interpolate2xLinear( portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int count ) { int i, upCount = count<<1; Assert (upCount <= PAINTBUFFER_SIZE); Assert (cfltmem >= 1); // use interpolation value from previous mix pbuffer[0].left = (pfiltermem->left + pbuffer[0].left) >> 1; pbuffer[0].right = (pfiltermem->right + pbuffer[0].right) >> 1; for ( i = 2; i < upCount; i+=2) { // use linear interpolation for upsampling pbuffer[i].left = (pbuffer[i].left + pbuffer[i-1].left) >> 1; pbuffer[i].right = (pbuffer[i].right + pbuffer[i-1].right) >> 1; } // save last value to be played out in buffer *pfiltermem = pbuffer[upCount - 1]; } // Optimized routine. 2.27X faster than the above routine void S_Interpolate2xLinear_2( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem ) { Assert (cfltmem >= 1); int sample = count-1; int end = (count*2)-1; portable_samplepair_t *pwrite = &pbuffer[end]; portable_samplepair_t *pread = &pbuffer[sample]; portable_samplepair_t last = pread[0]; pread--; // PERFORMANCE: Unroll the loop 8 times. This improves speed quite a bit for ( ;sample >= 8; sample -= 8 ) { pwrite[0] = last; pwrite[-1].left = (pread[0].left + last.left)>>1; pwrite[-1].right = (pread[0].right + last.right)>>1; last = pread[0]; pwrite[-2] = last; pwrite[-3].left = (pread[-1].left + last.left)>>1; pwrite[-3].right = (pread[-1].right + last.right)>>1; last = pread[-1]; pwrite[-4] = last; pwrite[-5].left = (pread[-2].left + last.left)>>1; pwrite[-5].right = (pread[-2].right + last.right)>>1; last = pread[-2]; pwrite[-6] = last; pwrite[-7].left = (pread[-3].left + last.left)>>1; pwrite[-7].right = (pread[-3].right + last.right)>>1; last = pread[-3]; pwrite[-8] = last; pwrite[-9].left = (pread[-4].left + last.left)>>1; pwrite[-9].right = (pread[-4].right + last.right)>>1; last = pread[-4]; pwrite[-10] = last; pwrite[-11].left = (pread[-5].left + last.left)>>1; pwrite[-11].right = (pread[-5].right + last.right)>>1; last = pread[-5]; pwrite[-12] = last; pwrite[-13].left = (pread[-6].left + last.left)>>1; pwrite[-13].right = (pread[-6].right + last.right)>>1; last = pread[-6]; pwrite[-14] = last; pwrite[-15].left = (pread[-7].left + last.left)>>1; pwrite[-15].right = (pread[-7].right + last.right)>>1; last = pread[-7]; pread -= 8; pwrite -= 16; } while ( pread >= pbuffer ) { pwrite[0] = last; pwrite[-1].left = (pread[0].left + last.left)>>1; pwrite[-1].right = (pread[0].right + last.right)>>1; last = pread[0]; pread--; pwrite-=2; } pbuffer[1] = last; pbuffer[0].left = (pfiltermem->left + last.left) >> 1; pbuffer[0].right = (pfiltermem->right + last.right) >> 1; *pfiltermem = pbuffer[end]; } // upsample by 2x, optionally using interpolation // count: how many samples to upsample. will become count*2 samples in buffer, in place. // pbuffer: buffer to upsample into (in place) // pfiltermem: filter memory. NOTE: this must match the filtertype ie: filterlinear[] for FILTERTYPE_LINEAR // if NULL then perform no filtering. // cfltmem: max number of sample pairs filter can use // filtertype: FILTERTYPE_NONE, _LINEAR, _CUBIC etc. Must match prevfilter. void S_MixBufferUpsample2x( int count, portable_samplepair_t *pbuffer, portable_samplepair_t *pfiltermem, int cfltmem, int filtertype ) { // JAY: Optimized this routine. Test then remove old routine. // NOTE: Has been proven equivalent by comparing output. if ( filtertype == FILTERTYPE_LINEAR ) { S_Interpolate2xLinear_2( count, pbuffer, pfiltermem, cfltmem ); return; } int i, j, upCount = count<<1; // reverse through buffer, duplicating contents for 'count' samples for (i = upCount - 1, j = count - 1; j >= 0; i-=2, j--) { pbuffer[i] = pbuffer[j]; pbuffer[i-1] = pbuffer[j]; } // pass forward through buffer, interpolate all even slots switch (filtertype) { default: break; case FILTERTYPE_LINEAR: S_Interpolate2xLinear(pbuffer, pfiltermem, cfltmem, count); break; case FILTERTYPE_CUBIC: S_Interpolate2xCubic(pbuffer, pfiltermem, cfltmem, count); break; } } //=============================================================================== // PAINTBUFFER ROUTINES //=============================================================================== // Set current paintbuffer to pbuf. // The set paintbuffer is used by all subsequent mixing, upsampling and dsp routines. // Also sets the rear paintbuffer if paintbuffer has fsurround true. // (otherwise, rearpaintbuffer is NULL) void MIX_SetCurrentPaintbuffer(int ipaintbuffer) { // set front and rear paintbuffer Assert(ipaintbuffer < g_paintBuffers.Count()); g_curpaintbuffer = g_paintBuffers[ipaintbuffer].pbuf; if ( g_paintBuffers[ipaintbuffer].fsurround ) { g_currearpaintbuffer = g_paintBuffers[ipaintbuffer].pbufrear; g_curcenterpaintbuffer = NULL; if ( g_paintBuffers[ipaintbuffer].fsurround_center ) g_curcenterpaintbuffer = g_paintBuffers[ipaintbuffer].pbufcenter; } else { g_currearpaintbuffer = NULL; g_curcenterpaintbuffer = NULL; } Assert(g_curpaintbuffer != NULL); } // return index to current paintbuffer int MIX_GetCurrentPaintbufferIndex( void ) { int i; for ( i = 0; i < g_paintBuffers.Count(); i++ ) { if (g_curpaintbuffer == g_paintBuffers[i].pbuf) return i; } return 0; } // return pointer to current paintbuffer struct paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void ) { int ipaint = MIX_GetCurrentPaintbufferIndex(); Assert( ipaint < g_paintBuffers.Count() ); return &g_paintBuffers[ipaint]; } // return pointer to front paintbuffer pbuf, given index inline portable_samplepair_t *MIX_GetPFrontFromIPaint(int ipaintbuffer) { return g_paintBuffers[ipaintbuffer].pbuf; } paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaintbuffer ) { Assert( ipaintbuffer < g_paintBuffers.Count() ); return &g_paintBuffers[ipaintbuffer]; } // return pointer to rear buffer, given index. // returns null if fsurround is false; inline portable_samplepair_t *MIX_GetPRearFromIPaint(int ipaintbuffer) { if ( g_paintBuffers[ipaintbuffer].fsurround ) return g_paintBuffers[ipaintbuffer].pbufrear; return NULL; } // return pointer to center buffer, given index. // returns null if fsurround_center is false; inline portable_samplepair_t *MIX_GetPCenterFromIPaint(int ipaintbuffer) { if ( g_paintBuffers[ipaintbuffer].fsurround_center ) return g_paintBuffers[ipaintbuffer].pbufcenter; return NULL; } // return index to paintbuffer, given buffer pointer inline int MIX_GetIPaintFromPFront( portable_samplepair_t *pbuf ) { int i; for ( i = 0; i < g_paintBuffers.Count(); i++ ) { if ( pbuf == g_paintBuffers[i].pbuf ) return i; } return 0; } // return pointer to paintbuffer struct, given ptr to buffer data inline paintbuffer_t *MIX_GetPPaintFromPFront( portable_samplepair_t *pbuf ) { int i; i = MIX_GetIPaintFromPFront( pbuf ); return &g_paintBuffers[i]; } // up convert mono buffer to full surround inline void MIX_ConvertBufferToSurround( int ipaintbuffer ) { paintbuffer_t *ppaint = &g_paintBuffers[ipaintbuffer]; // duplicate channel data as needed if ( g_AudioDevice->IsSurround() ) { // set buffer flags ppaint->fsurround = g_AudioDevice->IsSurround(); ppaint->fsurround_center = g_AudioDevice->IsSurroundCenter(); portable_samplepair_t *pfront = MIX_GetPFrontFromIPaint( ipaintbuffer ); portable_samplepair_t *prear = MIX_GetPRearFromIPaint( ipaintbuffer ); portable_samplepair_t *pcenter = MIX_GetPCenterFromIPaint( ipaintbuffer ); // copy front to rear Q_memcpy(prear, pfront, sizeof(portable_samplepair_t) * PAINTBUFFER_SIZE); // copy front to center if ( g_AudioDevice->IsSurroundCenter() ) Q_memcpy(pcenter, pfront, sizeof(portable_samplepair_t) * PAINTBUFFER_SIZE); } } // Activate a paintbuffer. All active paintbuffers are mixed in parallel within // MIX_MixChannelsToPaintbuffer, according to flags inline void MIX_ActivatePaintbuffer(int ipaintbuffer) { Assert( ipaintbuffer < g_paintBuffers.Count() ); g_paintBuffers[ipaintbuffer].factive = true; } // Don't mix into this paintbuffer inline void MIX_DeactivatePaintbuffer(int ipaintbuffer) { Assert( ipaintbuffer < g_paintBuffers.Count() ); g_paintBuffers[ipaintbuffer].factive = false; } // Don't mix into any paintbuffers inline void MIX_DeactivateAllPaintbuffers(void) { int i; for ( i = 0; i < g_paintBuffers.Count(); i++ ) g_paintBuffers[i].factive = false; } // set upsampling filter indexes back to 0 inline void MIX_ResetPaintbufferFilterCounters( void ) { int i; for ( i = 0; i < g_paintBuffers.Count(); i++ ) g_paintBuffers[i].ifilter = 0; } inline void MIX_ResetPaintbufferFilterCounter( int ipaintbuffer ) { Assert ( ipaintbuffer < g_paintBuffers.Count() ); g_paintBuffers[ipaintbuffer].ifilter = 0; } // Change paintbuffer's flags inline void MIX_SetPaintbufferFlags(int ipaintbuffer, int flags) { Assert( ipaintbuffer < g_paintBuffers.Count() ); g_paintBuffers[ipaintbuffer].flags = flags; } // zero out all paintbuffers void MIX_ClearAllPaintBuffers( int SampleCount, bool clearFilters ) { // g_paintBuffers can be NULL with -nosound if ( g_paintBuffers.Count() <= 0 ) { return; } int i; int count = min(SampleCount, PAINTBUFFER_SIZE); // zero out all paintbuffer data (ignore sampleCount) for ( i = 0; i < g_paintBuffers.Count(); i++ ) { if (g_paintBuffers[i].pbuf != NULL) Q_memset(g_paintBuffers[i].pbuf, 0, (count+1) * sizeof(portable_samplepair_t)); if (g_paintBuffers[i].pbufrear != NULL) Q_memset(g_paintBuffers[i].pbufrear, 0, (count+1) * sizeof(portable_samplepair_t)); if (g_paintBuffers[i].pbufcenter != NULL) Q_memset(g_paintBuffers[i].pbufcenter, 0, (count+1) * sizeof(portable_samplepair_t)); if ( clearFilters ) { Q_memset( g_paintBuffers[i].fltmem, 0, sizeof(g_paintBuffers[i].fltmem) ); Q_memset( g_paintBuffers[i].fltmemrear, 0, sizeof(g_paintBuffers[i].fltmemrear) ); Q_memset( g_paintBuffers[i].fltmemcenter, 0, sizeof(g_paintBuffers[i].fltmemcenter) ); } } if ( clearFilters ) { MIX_ResetPaintbufferFilterCounters(); } } #define SWAP(a,b,t) {(t) = (a); (a) = (b); (b) = (t);} #define AVG(a,b) (((a) + (b)) >> 1 ) #define AVG4(a,b,c,d) (((a) + (b) + (c) + (d)) >> 2 ) // Synthesize center channel from left/right values (average). // Currently just averages, but could actually remove // the center signal from the l/r channels... inline void MIX_CenterFromLeftRight( int *pl, int *pr, int *pc ) { int l = *pl; int r = *pr; int c = 0; c = (l + r) / 2; /* l = l - c/2; r = r - c/2; if (l < 0) { l = 0; r += (-l); c += (-l); } else if (r < 0) { r = 0; l += (-r); c += (-r); } */ *pc = c; // *pl = l; // *pr = r; } // mixes pbuf1 + pbuf2 into pbuf3, count samples // fgain is output gain 0-1.0 // NOTE: pbuf3 may equal pbuf1 or pbuf2! // mixing algorithms: // destination 2ch: // pb1 2ch + pb2 2ch -> pb3 2ch // pb1 (4ch->2ch) + pb2 2ch -> pb3 2ch // pb1 2ch + pb2 (4ch->2ch) -> pb3 2ch // pb1 (4ch->2ch) + pb2 (4ch->2ch) -> pb3 2ch // destination 4ch: // pb1 4ch + pb2 4ch -> pb3 4ch // pb1 (2ch->4ch) + pb2 4ch -> pb3 4ch // pb1 4ch + pb2 (2ch->4ch) -> pb3 4ch // pb1 (2ch->4ch) + pb2 (2ch->4ch) -> pb3 4ch // if all buffers are 4 or 5 ch surround, mix rear & center channels into ibuf3 as well. // NOTE: for performance, conversion and mixing are done in a single pass instead of // a two pass channel convert + mix scheme. void MIX_MixPaintbuffers(int ibuf1, int ibuf2, int ibuf3, int count, float fgain_out) { VPROF("Mixpaintbuffers"); int i; portable_samplepair_t *pbuf1, *pbuf2, *pbuf3, *pbuft; portable_samplepair_t *pbufrear1, *pbufrear2, *pbufrear3, *pbufreart; portable_samplepair_t *pbufcenter1, *pbufcenter2, *pbufcenter3, *pbufcentert; int cchan1, cchan2, cchan3, cchant; int xl,xr; int l,r,l2,r2,c, c2; int gain_out; gain_out = 256 * fgain_out; Assert (count <= PAINTBUFFER_SIZE); Assert (ibuf1 < g_paintBuffers.Count()); Assert (ibuf2 < g_paintBuffers.Count()); Assert (ibuf3 < g_paintBuffers.Count()); pbuf1 = g_paintBuffers[ibuf1].pbuf; pbuf2 = g_paintBuffers[ibuf2].pbuf; pbuf3 = g_paintBuffers[ibuf3].pbuf; pbufrear1 = g_paintBuffers[ibuf1].pbufrear; pbufrear2 = g_paintBuffers[ibuf2].pbufrear; pbufrear3 = g_paintBuffers[ibuf3].pbufrear; pbufcenter1 = g_paintBuffers[ibuf1].pbufcenter; pbufcenter2 = g_paintBuffers[ibuf2].pbufcenter; pbufcenter3 = g_paintBuffers[ibuf3].pbufcenter; cchan1 = 2 + (g_paintBuffers[ibuf1].fsurround ? 2 : 0) + (g_paintBuffers[ibuf1].fsurround_center ? 1 : 0); cchan2 = 2 + (g_paintBuffers[ibuf2].fsurround ? 2 : 0) + (g_paintBuffers[ibuf2].fsurround_center ? 1 : 0); cchan3 = 2 + (g_paintBuffers[ibuf3].fsurround ? 2 : 0) + (g_paintBuffers[ibuf3].fsurround_center ? 1 : 0); // make sure pbuf1 always has fewer or equal channels than pbuf2 // NOTE: pbuf3 may equal pbuf1 or pbuf2! if ( cchan2 < cchan1 ) { SWAP( cchan1, cchan2, cchant ); SWAP( pbuf1, pbuf2, pbuft ); SWAP( pbufrear1, pbufrear2, pbufreart ); SWAP( pbufcenter1, pbufcenter2, pbufcentert); } // UNDONE: implement fast mixing routines for each of the following sections // destination buffer stereo - average n chans down to stereo if ( cchan3 == 2 ) { // destination 2ch: // pb1 2ch + pb2 2ch -> pb3 2ch // pb1 2ch + pb2 (4ch->2ch) -> pb3 2ch // pb1 (4ch->2ch) + pb2 (4ch->2ch) -> pb3 2ch if ( cchan1 == 2 && cchan2 == 2 ) { // mix front channels for (i = 0; i < count; i++) { pbuf3[i].left = pbuf1[i].left + pbuf2[i].left; pbuf3[i].right = pbuf1[i].right + pbuf2[i].right; } goto gain2ch; } if ( cchan1 == 2 && cchan2 == 4 ) { // avg rear chan l/r for (i = 0; i < count; i++) { pbuf3[i].left = pbuf1[i].left + AVG( pbuf2[i].left, pbufrear2[i].left ); pbuf3[i].right = pbuf1[i].right + AVG( pbuf2[i].right, pbufrear2[i].right ); } goto gain2ch; } if ( cchan1 == 4 && cchan2 == 4 ) { // avg rear chan l/r for (i = 0; i < count; i++) { pbuf3[i].left = AVG( pbuf1[i].left, pbufrear1[i].left) + AVG( pbuf2[i].left, pbufrear2[i].left ); pbuf3[i].right = AVG( pbuf1[i].right, pbufrear1[i].right) + AVG( pbuf2[i].right, pbufrear2[i].right ); } goto gain2ch; } if ( cchan1 == 2 && cchan2 == 5 ) { // avg rear chan l/r + center split into left/right for (i = 0; i < count; i++) { l = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); r = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); pbuf3[i].left = pbuf1[i].left + AVG( l, pbufrear2[i].left ); pbuf3[i].right = pbuf1[i].right + AVG( r, pbufrear2[i].right ); } goto gain2ch; } if ( cchan1 == 4 && cchan2 == 5) { for (i = 0; i < count; i++) { l = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); r = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); pbuf3[i].left = AVG( pbuf1[i].left, pbufrear1[i].left) + AVG( l, pbufrear2[i].left ); pbuf3[i].right = AVG( pbuf1[i].right, pbufrear1[i].right) + AVG( r, pbufrear2[i].right ); } goto gain2ch; } if ( cchan1 == 5 && cchan2 == 5) { for (i = 0; i < count; i++) { l = pbuf1[i].left + ((pbufcenter1[i].left) >> 1); r = pbuf1[i].right + ((pbufcenter1[i].left) >> 1); l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); pbuf3[i].left = AVG( l, pbufrear1[i].left) + AVG( l2, pbufrear2[i].left ); pbuf3[i].right = AVG( r, pbufrear1[i].right) + AVG( r2, pbufrear2[i].right ); } goto gain2ch; } } // destination buffer quad - duplicate n chans up to quad if ( cchan3 == 4 ) { // pb1 4ch + pb2 4ch -> pb3 4ch // pb1 (2ch->4ch) + pb2 4ch -> pb3 4ch // pb1 (2ch->4ch) + pb2 (2ch->4ch) -> pb3 4ch if ( cchan1 == 4 && cchan2 == 4) { // mix front -> front, rear -> rear for (i = 0; i < count; i++) { pbuf3[i].left = pbuf1[i].left + pbuf2[i].left; pbuf3[i].right = pbuf1[i].right + pbuf2[i].right; pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; } goto gain4ch; } if ( cchan1 == 2 && cchan2 == 4) { for (i = 0; i < count; i++) { // split 2 ch left -> front left, rear left // split 2 ch right -> front right, rear right xl = pbuf1[i].left; xr = pbuf1[i].right; pbuf3[i].left = xl + pbuf2[i].left; pbuf3[i].right = xr + pbuf2[i].right; pbufrear3[i].left = xl + pbufrear2[i].left; pbufrear3[i].right = xr + pbufrear2[i].right; } goto gain4ch; } if ( cchan1 == 2 && cchan2 == 2) { // mix l,r, split into front l, front r for (i = 0; i < count; i++) { xl = pbuf1[i].left + pbuf2[i].left; xr = pbuf1[i].right + pbuf2[i].right; pbufrear3[i].left = pbuf3[i].left = xl; pbufrear3[i].right = pbuf3[i].right = xr; } goto gain4ch; } if ( cchan1 == 2 && cchan2 == 5 ) { for (i = 0; i < count; i++) { // split center of chan2 into left/right l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); xl = pbuf1[i].left; xr = pbuf1[i].right; pbuf3[i].left = xl + l2; pbuf3[i].right = xr + r2; pbufrear3[i].left = xl + pbufrear2[i].left; pbufrear3[i].right = xr + pbufrear2[i].right; } goto gain4ch; } if ( cchan1 == 4 && cchan2 == 5) { for (i = 0; i < count; i++) { l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); pbuf3[i].left = pbuf1[i].left + l2; pbuf3[i].right = pbuf1[i].right + r2; pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; } goto gain4ch; } if ( cchan1 == 5 && cchan2 == 5 ) { for (i = 0; i < count; i++) { l = pbuf1[i].left + ((pbufcenter1[i].left) >> 1); r = pbuf1[i].right + ((pbufcenter1[i].left) >> 1); l2 = pbuf2[i].left + ((pbufcenter2[i].left) >> 1); r2 = pbuf2[i].right + ((pbufcenter2[i].left) >> 1); pbuf3[i].left = l + l2; pbuf3[i].right = r + r2; pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; } goto gain4ch; } } // 5 channel destination if (cchan3 == 5) { // up convert from 2 or 4 ch buffer to 5 ch buffer: // center channel is synthesized from front left, front right if (cchan1 == 2 && cchan2 == 2) { for (i = 0; i < count; i++) { // split 2 ch left -> front left, center, rear left // split 2 ch right -> front right, center, rear right l = pbuf1[i].left; r = pbuf1[i].right; MIX_CenterFromLeftRight(&l, &r, &c); l2 = pbuf2[i].left; r2 = pbuf2[i].right; MIX_CenterFromLeftRight(&l2, &r2, &c2); pbuf3[i].left = l + l2; pbuf3[i].right = r + r2; pbufrear3[i].left = pbuf1[i].left + pbuf2[i].left; pbufrear3[i].right = pbuf1[i].right + pbuf2[i].right; pbufcenter3[i].left = c + c2; } goto gain5ch; } if (cchan1 == 2 && cchan2 == 4) { for (i = 0; i < count; i++) { l = pbuf1[i].left; r = pbuf1[i].right; MIX_CenterFromLeftRight(&l, &r, &c); l2 = pbuf2[i].left; r2 = pbuf2[i].right; MIX_CenterFromLeftRight(&l2, &r2, &c2); pbuf3[i].left = l + l2; pbuf3[i].right = r + r2; pbufrear3[i].left = pbuf1[i].left + pbufrear2[i].left; pbufrear3[i].right = pbuf1[i].right + pbufrear2[i].right; pbufcenter3[i].left = c + c2; } goto gain5ch; } if (cchan1 == 2 && cchan2 == 5) { for (i = 0; i < count; i++) { l = pbuf1[i].left; r = pbuf1[i].right; MIX_CenterFromLeftRight(&l, &r, &c); pbuf3[i].left = l + pbuf2[i].left; pbuf3[i].right = r + pbuf2[i].right; pbufrear3[i].left = pbuf1[i].left + pbufrear2[i].left; pbufrear3[i].right = pbuf1[i].right + pbufrear2[i].right; pbufcenter3[i].left = c + pbufcenter2[i].left; } goto gain5ch; } if (cchan1 == 4 && cchan2 == 4) { for (i = 0; i < count; i++) { l = pbuf1[i].left; r = pbuf1[i].right; MIX_CenterFromLeftRight(&l, &r, &c); l2 = pbuf2[i].left; r2 = pbuf2[i].right; MIX_CenterFromLeftRight(&l2, &r2, &c2); pbuf3[i].left = l + l2; pbuf3[i].right = r + r2; pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; pbufcenter3[i].left = c + c2; } goto gain5ch; } if (cchan1 == 4 && cchan2 == 5) { for (i = 0; i < count; i++) { l = pbuf1[i].left; r = pbuf1[i].right; MIX_CenterFromLeftRight(&l, &r, &c); pbuf3[i].left = l + pbuf2[i].left; pbuf3[i].right = r + pbuf2[i].right; pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; pbufcenter3[i].left = c + pbufcenter2[i].left; } goto gain5ch; } if ( cchan2 == 5 && cchan1 == 5 ) { for (i = 0; i < count; i++) { pbuf3[i].left = pbuf1[i].left + pbuf2[i].left; pbuf3[i].right = pbuf1[i].right + pbuf2[i].right; pbufrear3[i].left = pbufrear1[i].left + pbufrear2[i].left; pbufrear3[i].right = pbufrear1[i].right + pbufrear2[i].right; pbufcenter3[i].left = pbufcenter1[i].left + pbufcenter2[i].left; } goto gain5ch; } } gain2ch: if ( gain_out == 256) // KDB: perf return; for (i = 0; i < count; i++) { pbuf3[i].left = (pbuf3[i].left * gain_out) >> 8; pbuf3[i].right = (pbuf3[i].right * gain_out) >> 8; } return; gain4ch: if ( gain_out == 256) // KDB: perf return; for (i = 0; i < count; i++) { pbuf3[i].left = (pbuf3[i].left * gain_out) >> 8; pbuf3[i].right = (pbuf3[i].right * gain_out) >> 8; pbufrear3[i].left = (pbufrear3[i].left * gain_out) >> 8; pbufrear3[i].right = (pbufrear3[i].right * gain_out) >> 8; } return; gain5ch: if ( gain_out == 256) // KDB: perf return; for (i = 0; i < count; i++) { pbuf3[i].left = (pbuf3[i].left * gain_out) >> 8; pbuf3[i].right = (pbuf3[i].right * gain_out) >> 8; pbufrear3[i].left = (pbufrear3[i].left * gain_out) >> 8; pbufrear3[i].right = (pbufrear3[i].right * gain_out) >> 8; pbufcenter3[i].left = (pbufcenter3[i].left * gain_out) >> 8; } return; } // multiply all values in paintbuffer by fgain void MIX_ScalePaintBuffer( int bufferIndex, int count, float fgain ) { portable_samplepair_t *pbuf = g_paintBuffers[bufferIndex].pbuf; portable_samplepair_t *pbufrear = g_paintBuffers[bufferIndex].pbufrear; portable_samplepair_t *pbufcenter = g_paintBuffers[bufferIndex].pbufcenter; int gain = 256 * fgain; int i; if (gain == 256) return; if ( !g_paintBuffers[bufferIndex].fsurround ) { for (i = 0; i < count; i++) { pbuf[i].left = (pbuf[i].left * gain) >> 8; pbuf[i].right = (pbuf[i].right * gain) >> 8; } } else { for (i = 0; i < count; i++) { pbuf[i].left = (pbuf[i].left * gain) >> 8; pbuf[i].right = (pbuf[i].right * gain) >> 8; pbufrear[i].left = (pbufrear[i].left * gain) >> 8; pbufrear[i].right = (pbufrear[i].right * gain) >> 8; } if (g_paintBuffers[bufferIndex].fsurround_center) { for (i = 0; i < count; i++) { pbufcenter[i].left = (pbufcenter[i].left * gain) >> 8; // pbufcenter[i].right = (pbufcenter[i].right * gain) >> 8; mono center channel } } } } // DEBUG peak detection values #define _SDEBUG 1 #ifdef _SDEBUG float sdebug_avg_in = 0.0; float sdebug_in_count = 0.0; float sdebug_avg_out = 0.0; float sdebug_out_count = 0.0; #define SDEBUG_TOTAL_COUNT (3*44100) #endif // DEBUG // DEBUG code - get and show peak value of specified paintbuffer // DEBUG code - ibuf is buffer index, count is # samples to test, pppeakprev stores peak void SDEBUG_GetAvgValue( int ibuf, int count, float *pav ) { #ifdef _SDEBUG if (snd_showstart.GetInt() != 4 ) return; float av = 0.0; for (int i = 0; i < count; i++) av += (float)(abs(g_paintBuffers[ibuf].pbuf->left) + abs(g_paintBuffers[ibuf].pbuf->right))/2.0; *pav = av / count; #endif // DEBUG } void SDEBUG_GetAvgIn( int ibuf, int count) { float av = 0.0; SDEBUG_GetAvgValue( ibuf, count, &av ); sdebug_avg_in = ((av * count ) + (sdebug_avg_in * sdebug_in_count)) / (count + sdebug_in_count); sdebug_in_count += count; } void SDEBUG_GetAvgOut( int ibuf, int count) { float av = 0.0; SDEBUG_GetAvgValue( ibuf, count, &av ); sdebug_avg_out = ((av * count ) + (sdebug_avg_out * sdebug_out_count)) / (count + sdebug_out_count); sdebug_out_count += count; } void SDEBUG_ShowAvgValue() { #ifdef _SDEBUG if (sdebug_in_count > SDEBUG_TOTAL_COUNT) { if ((int)sdebug_avg_in > 20.0 && (int)sdebug_avg_out > 20.0) DevMsg("dsp avg gain:%1.2f in:%1.2f out:%1.2f 1/gain:%1.2f\n", sdebug_avg_out/sdebug_avg_in, sdebug_avg_in, sdebug_avg_out, sdebug_avg_in/sdebug_avg_out); sdebug_avg_in = 0.0; sdebug_avg_out = 0.0; sdebug_in_count = 0.0; sdebug_out_count = 0.0; } #endif // DEBUG } // clip all values in paintbuffer to 16bit. // if fsurround is set for paintbuffer, also process rear buffer samples void MIX_CompressPaintbuffer(int ipaint, int count) { VPROF("CompressPaintbuffer"); int i; paintbuffer_t *ppaint = MIX_GetPPaintFromIPaint(ipaint); portable_samplepair_t *pbf; portable_samplepair_t *pbr; portable_samplepair_t *pbc; pbf = ppaint->pbuf; pbr = ppaint->pbufrear; pbc = ppaint->pbufcenter; for (i = 0; i < count; i++) { pbf->left = CLIP(pbf->left); pbf->right = CLIP(pbf->right); pbf++; } if ( ppaint->fsurround ) { Assert (pbr); for (i = 0; i < count; i++) { pbr->left = CLIP(pbr->left); pbr->right = CLIP(pbr->right); pbr++; } } if ( ppaint->fsurround_center ) { Assert (pbc); for (i = 0; i < count; i++) { pbc->left = CLIP(pbc->left); //pbc->right = CLIP(pbc->right); mono center channel pbc++; } } } // mix and upsample channels to 44khz 'ipaintbuffer' // mix channels matching 'flags' (SOUND_MIX_DRY, SOUND_MIX_WET, SOUND_MIX_SPEAKER) into specified paintbuffer // upsamples 11khz, 22khz channels to 44khz. // NOTE: only call this on channels that will be mixed into only 1 paintbuffer // and that will not be mixed until the next mix pass! otherwise, MIX_MixChannelsToPaintbuffer // will advance any internal pointers on mixed channels; subsequent calls will be at // incorrect offset. void MIX_MixUpsampleBuffer( CChannelList &list, int ipaintbuffer, int end, int count, int flags ) { VPROF("MixUpsampleBuffer"); int ipaintcur = MIX_GetCurrentPaintbufferIndex(); // save current paintbuffer // reset paintbuffer upsampling filter index MIX_ResetPaintbufferFilterCounter( ipaintbuffer ); // prevent other paintbuffers from being mixed MIX_DeactivateAllPaintbuffers(); MIX_ActivatePaintbuffer( ipaintbuffer ); // operates on MIX_MixChannelsToPaintbuffer MIX_SetCurrentPaintbuffer( ipaintbuffer ); // operates on MixUpSample // mix 11khz channels to buffer if ( list.m_has11kChannels ) { MIX_MixChannelsToPaintbuffer( list, end, flags, SOUND_11k, SOUND_11k ); // upsample 11khz buffer by 2x g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); } if ( list.m_has22kChannels || list.m_has11kChannels ) { // mix 22khz channels to buffer MIX_MixChannelsToPaintbuffer( list, end, flags, SOUND_22k, SOUND_22k ); #if (SOUND_DMA_SPEED > SOUND_22k) // upsample 22khz buffer by 2x g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR ); #endif } // mix 44khz channels to buffer MIX_MixChannelsToPaintbuffer( list, end, flags, SOUND_44k, SOUND_DMA_SPEED); MIX_DeactivateAllPaintbuffers(); // restore previous paintbuffer MIX_SetCurrentPaintbuffer( ipaintcur ); } // upsample and mix sounds into final 44khz versions of the following paintbuffers: // SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, IFACINGAWAY, SOUND_BUFFER_DRY, SOUND_BUFFER_SPEAKER, SOUND_BUFFER_SPECIALs // dsp fx are then applied to these buffers by the caller. // caller also remixes all into final SOUND_BUFFER_PAINT output. void MIX_UpsampleAllPaintbuffers( CChannelList &list, int end, int count ) { VPROF( "MixUpsampleAll" ); // 'dry' and 'speaker' channel sounds mix 100% into their corresponding buffers // mix and upsample all 'dry' sounds (channels) to 44khz SOUND_BUFFER_DRY paintbuffer if ( list.m_hasDryChannels ) MIX_MixUpsampleBuffer( list, SOUND_BUFFER_DRY, end, count, SOUND_MIX_DRY ); // mix and upsample all 'speaker' sounds (channels) to 44khz SOUND_BUFFER_SPEAKER paintbuffer if ( list.m_hasSpeakerChannels ) MIX_MixUpsampleBuffer( list, SOUND_BUFFER_SPEAKER, end, count, SOUND_MIX_SPEAKER ); // mix and upsample all 'special dsp' sounds (channels) to 44khz SOUND_BUFFER_SPECIALs paintbuffer for ( int iDSP = 0; iDSP < list.m_nSpecialDSPs.Count(); ++iDSP ) { for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i ) { paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i ); if ( pSpecialBuffer->nSpecialDSP == list.m_nSpecialDSPs[ iDSP ] && pSpecialBuffer->idsp_specialdsp != -1 ) { MIX_MixUpsampleBuffer( list, i, end, count, SOUND_MIX_SPECIAL_DSP ); break; } } } // 'room', 'facing' 'facingaway' sounds are mixed into up to 3 buffers: // 11khz sounds are mixed into 3 buffers based on distance from listener, and facing direction // These buffers are room, facing, facingaway // These 3 mixed buffers are then each upsampled to 22khz. // 22khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction // These 3 mixed buffers are then each upsampled to 44khz. // 44khz sounds are mixed into the 3 buffers based on distance from listener, and facing direction MIX_DeactivateAllPaintbuffers(); // set paintbuffer upsample filter indices to 0 MIX_ResetPaintbufferFilterCounters(); if ( !g_bDspOff ) { // only mix to roombuffer if dsp fx are on KDB: perf MIX_ActivatePaintbuffer(SOUND_BUFFER_ROOM); // operates on MIX_MixChannelsToPaintbuffer } MIX_ActivatePaintbuffer(SOUND_BUFFER_FACING); if ( g_bdirectionalfx ) { // mix to facing away buffer only if directional presets are set MIX_ActivatePaintbuffer(SOUND_BUFFER_FACINGAWAY); } // mix 11khz sounds: // pan sounds between 3 busses: facing, facingaway and room buffers MIX_MixChannelsToPaintbuffer( list, end, SOUND_MIX_WET, SOUND_11k, SOUND_11k); // upsample all 11khz buffers by 2x if ( !g_bDspOff ) { // only upsample roombuffer if dsp fx are on KDB: perf MIX_SetCurrentPaintbuffer(SOUND_BUFFER_ROOM); // operates on MixUpSample g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); } MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACING); g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); if ( g_bdirectionalfx ) { MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACINGAWAY); g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_11k), FILTERTYPE_LINEAR ); } // mix 22khz sounds: // pan sounds between 3 busses: facing, facingaway and room buffers MIX_MixChannelsToPaintbuffer( list, end, SOUND_MIX_WET, SOUND_22k, SOUND_22k); // upsample all 22khz buffers by 2x #if ( SOUND_DMA_SPEED > SOUND_22k ) if ( !g_bDspOff ) { // only upsample roombuffer if dsp fx are on KDB: perf MIX_SetCurrentPaintbuffer(SOUND_BUFFER_ROOM); g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR ); } MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACING); g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR ); if ( g_bdirectionalfx ) { MIX_SetCurrentPaintbuffer(SOUND_BUFFER_FACINGAWAY); g_AudioDevice->MixUpsample( count / (SOUND_DMA_SPEED / SOUND_22k), FILTERTYPE_LINEAR ); } #endif // mix all 44khz sounds to all active paintbuffers MIX_MixChannelsToPaintbuffer( list, end, SOUND_MIX_WET, SOUND_44k, SOUND_DMA_SPEED); MIX_DeactivateAllPaintbuffers(); MIX_SetCurrentPaintbuffer(SOUND_BUFFER_PAINT); } ConVar snd_cull_duplicates("snd_cull_duplicates","0",FCVAR_ALLOWED_IN_COMPETITIVE,"If nonzero, aggressively cull duplicate sounds during mixing. The number specifies the number of duplicates allowed to be played."); // Helper class for determining whether a given channel number should be culled from // mixing, if snd_cull_duplicates is enabled (psychoacoustic quashing). class CChannelCullList { public: // default constructor CChannelCullList() : m_numChans(0) {}; // call if you plan on culling channels - and not otherwise, it's a little expensive // (that's why it's not in the constructor) void Initialize( CChannelList &list ); // returns true if a given channel number has been marked for culling inline bool ShouldCull( int channelNum ) { return (m_numChans > channelNum) ? m_bShouldCull[channelNum] : false; } // an array of sound names and their volumes // TODO: there may be a way to do this faster on 360 (eg, pad to 128bit, use SIMD) struct sChannelVolData { int m_channelNum; int m_vol; // max volume of sound. -1 means "do not cull, ever, do not even do the math" unsigned int m_nameHash; // a unique id for a sound file }; protected: sChannelVolData m_channelInfo[MAX_CHANNELS]; bool m_bShouldCull[MAX_CHANNELS]; // in ChannelList order, not sorted order int m_numChans; }; // comparator for qsort as used below (eg a lambda) // returns < 0 if a should come before b, > 0 if a should come after, 0 otherwise static int __cdecl ChannelVolComparator ( const void * a, const void * b ) { // greater numbers come first. return static_cast(b)->m_vol - static_cast(a)->m_vol; } void CChannelCullList::Initialize( CChannelList &list ) { VPROF("CChannelCullList::Initialize"); // First, build a sorted list of channels by decreasing volume, and by a hash of their wavname. m_numChans = list.Count(); for ( int i = m_numChans - 1 ; i >= 0 ; --i ) { channel_t *ch = list.GetChannel(i); m_channelInfo[i].m_channelNum = i; if ( ch && ch->pMixer->IsReadyToMix() ) { m_channelInfo[i].m_vol = ChannelLoudestCurVolume(ch); AssertMsg(m_channelInfo[i].m_vol >= 0, "Sound channel has a negative volume?"); m_channelInfo[i].m_nameHash = (unsigned int) ch->sfx; } else { m_channelInfo[i].m_vol = -1; m_channelInfo[i].m_nameHash = NULL; // doesn't matter } } // set the unused channels to invalid data for ( int i = m_numChans ; i < MAX_CHANNELS ; ++i ) { m_channelInfo[i].m_channelNum = -1; m_channelInfo[i].m_vol = -1; } // Sort the list. qsort( m_channelInfo, MAX_CHANNELS, sizeof(sChannelVolData), ChannelVolComparator ); // Then, determine if the given sound is less than the nth loudest of its hash. If so, mark its flag // for removal. // TODO: use an actual algorithm rather than this bogus quadratic technique. // (I'm using it for now because we don't have convenient/fast hash table // classes, which would be the linear-time way to deal with this). const int cutoff = snd_cull_duplicates.GetInt(); for ( int i = 0 ; i < m_numChans ; ++i ) // i is index in original channel list { channel_t *ch = list.GetChannel(i); // for each sound, determine where it ranks in loudness int howManyLouder = 0; for ( int j = 0 ; m_channelInfo[j].m_channelNum != i && m_channelInfo[j].m_vol >= 0 && j < MAX_CHANNELS ; ++j ) { // j steps through the sorted list until we find ourselves: if (m_channelInfo[j].m_nameHash == (unsigned int)(ch->sfx)) { // that's another channel playing this sound but louder than me ++howManyLouder; } } if (howManyLouder >= cutoff) { // this sound should be culled m_bShouldCull[i] = true; } else { // this sound should not be culled m_bShouldCull[i] = false; } } } ConVar snd_mute_losefocus("snd_mute_losefocus", "1", FCVAR_ARCHIVE); // build a list of channels that will actually do mixing in this update // remove all active channels that won't mix for some reason void MIX_BuildChannelList( CChannelList &list ) { VPROF("MIX_BuildChannelList"); g_ActiveChannels.GetActiveChannels( list ); list.m_nSpecialDSPs.RemoveAll(); list.m_hasDryChannels = false; list.m_hasSpeakerChannels = false; list.m_has11kChannels = false; list.m_has22kChannels = false; list.m_has44kChannels = false; bool delayStartServer = false; bool delayStartClient = false; bool bPaused = g_pSoundServices->IsGamePaused(); #ifdef POSIX bool bActive = g_pSoundServices->IsGameActive(); bool bStopOnFocusLoss = !bActive && snd_mute_losefocus.GetBool(); #endif CChannelCullList cullList; if (snd_cull_duplicates.GetInt() > 0) { cullList.Initialize(list); } // int numQuashed = 0; for ( int i = list.Count(); --i >= 0; ) { channel_t *ch = list.GetChannel(i); bool bRemove = false; // Certain async loaded sounds lazily load into memory in the background, use this to determine // if the sound is ready for mixing CAudioSource *pSource = NULL; if ( ch->pMixer->IsReadyToMix() ) { pSource = S_LoadSound( ch->sfx, ch ); // Don't mix sound data for sounds with 'zero' volume. If it's a non-looping sound, // just remove the sound when its volume goes to zero. If it's a 'dry' channel sound (ie: music) // then assume bZeroVolume is fade in - don't restart // To be 'zero' volume, all target volume and current volume values must all be less than 5 bool bZeroVolume = BChannelLowVolume( ch, 1 ); if ( !pSource || ( bZeroVolume && !pSource->IsLooped() && !ch->flags.bdry ) ) { // NOTE: Since we've loaded the sound, check to see if it's a sentence. Play them at zero anyway // to keep the character's lips moving and the captions happening. if ( !pSource || pSource->GetSentence() == NULL ) { S_FreeChannel( ch ); bRemove = true; } } else if ( bZeroVolume ) { bRemove = true; } // If the sound wants to stop when the game pauses, do so if ( bPaused && SND_ShouldPause(ch) ) { bRemove = true; } #ifdef POSIX // If we aren't the active app and the option for background audio isn't on, mute the audio // Windows has it's own system for background muting if ( !bRemove && bStopOnFocusLoss ) { bRemove = true; // Free up the sound channels otherwise they start filling up if ( pSource && ( !pSource->IsLooped() && !pSource->IsStreaming() ) ) { S_FreeChannel( ch ); } } #endif // On lowend, aggressively cull duplicate sounds. if ( !bRemove && snd_cull_duplicates.GetInt() > 0 ) { // We can't simply remove them, because then sounds will pile up waiting to finish later. // We need to flag them for not mixing. list.m_quashed[i] = cullList.ShouldCull(i); /* if (list.m_quashed[i]) { numQuashed++; // Msg("removed %i\n", i); } */ } else { list.m_quashed[i] = false; } } else { bRemove = true; } if ( bRemove ) { list.RemoveChannelFromList(i); continue; } if ( ch->flags.bSpeaker ) { list.m_hasSpeakerChannels = true; } if ( ch->special_dsp != 0 ) { if ( list.m_nSpecialDSPs.Find( ch->special_dsp ) == -1 ) { list.m_nSpecialDSPs.AddToTail( ch->special_dsp ); } } if ( ch->flags.bdry ) { list.m_hasDryChannels = true; } int rate = pSource->SampleRate(); if ( rate == SOUND_11k ) { list.m_has11kChannels = true; } else if ( rate == SOUND_22k ) { list.m_has22kChannels = true; } else if ( rate == SOUND_44k ) { list.m_has44kChannels = true; } if ( ch->flags.delayed_start && !SND_IsMouth(ch) ) { if ( ch->flags.fromserver ) { delayStartServer = true; } else { delayStartClient = true; } } // get playback pitch ch->pitch = ch->pMixer->ModifyPitch( ch->basePitch * 0.01f ); } // DevMsg( "%d channels quashed.\n", numQuashed ); // This code will resync the delay calculation clock really often // any time there are no scheduled waves or the game is paused // we go ahead and reset the clock // That way the clock is only used for short periods of time // and we need no solution for drift if ( bPaused || (host_frametime_unbounded > host_frametime) ) { delayStartClient = false; delayStartServer = false; } if (!delayStartServer) { S_SyncClockAdjust(CLOCK_SYNC_SERVER); } if (!delayStartClient) { S_SyncClockAdjust(CLOCK_SYNC_CLIENT); } } // main mixing rountine - mix up to 'endtime' samples. // All channels are mixed in a paintbuffer and then sent to // hardware. // A mix pass is performed, resulting in mixed sounds in SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, SOUND_BUFFER_FACINGAWAY, SOUND_BUFFER_DRY, SOUND_BUFFER_SPEAKER, SOUND_BUFFER_SPECIALs // directional sounds are panned and mixed between SOUND_BUFFER_FACING and SOUND_BUFFER_FACINGAWAY // omnidirectional sounds are panned 100% into SOUND_BUFFER_FACING // sound sources far from player (ie: near back of room ) are mixed in proportion to this distance // into SOUND_BUFFER_ROOM // sounds with ch->bSpeaker set are mixed in mono into SOUND_BUFFER_SPEAKER // sounds with ch->bSpecialDSP set are mixed in mono into SOUND_BUFFER_SPECIALs // dsp_facingaway fx (2 or 4ch filtering) are then applied to the SOUND_BUFFER_FACINGAWAY // dsp_speaker fx (1ch) are then applied to the SOUND_BUFFER_SPEAKER // dsp_specialdsp fx (1ch) are then applied to the SOUND_BUFFER_SPECIALs // dsp_room fx (1ch reverb) are then applied to the SOUND_BUFFER_ROOM // All buffers are recombined into the SOUND_BUFFER_PAINT // The dsp_water and dsp_player fx are applied in series to the SOUND_BUFFER_PAINT // Finally, the SOUND_BUFFER_DRY buffer is mixed into the SOUND_BUFFER_PAINT extern ConVar dsp_off; extern ConVar snd_profile; extern void DEBUG_StartSoundMeasure(int type, int samplecount ); extern void DEBUG_StopSoundMeasure(int type, int samplecount ); extern ConVar dsp_enhance_stereo; extern ConVar dsp_volume; extern ConVar dsp_vol_5ch; extern ConVar dsp_vol_4ch; extern ConVar dsp_vol_2ch; extern void MXR_SetCurrentSoundMixer( const char *szsoundmixer ); extern ConVar snd_soundmixer; void MIX_PaintChannels( int endtime, bool bIsUnderwater ) { VPROF("MIX_PaintChannels"); tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); int end; int count; bool b_spatial_delays = dsp_enhance_stereo.GetInt() != 0 ? true : false; bool room_fsurround_sav; bool room_fsurround_center_sav; paintbuffer_t *proom = MIX_GetPPaintFromIPaint(SOUND_BUFFER_ROOM); CheckNewDspPresets(); MXR_SetCurrentSoundMixer( snd_soundmixer.GetString() ); // dsp performance tuning g_snd_profile_type = snd_profile.GetInt(); // dsp_off is true if no dsp processing is to run // directional dsp processing is enabled if dsp_facingaway is non-zero g_bDspOff = dsp_off.GetInt() ? 1 : 0; CChannelList list; MIX_BuildChannelList(list); // get master dsp volume g_dsp_volume = dsp_volume.GetFloat(); // attenuate master dsp volume by 2,4 or 5 ch settings if ( g_AudioDevice->IsSurround() ) { g_dsp_volume *= ( g_AudioDevice->IsSurroundCenter() ? dsp_vol_5ch.GetFloat() : dsp_vol_4ch.GetFloat() ); } else { g_dsp_volume *= dsp_vol_2ch.GetFloat(); } if ( !g_bDspOff ) { g_bdirectionalfx = dsp_facingaway.GetInt() ? 1 : 0; } else { g_bdirectionalfx = 0; } // get dsp preset gain values, update gain crossfaders, used when mixing dsp processed buffers into paintbuffer SDEBUG_ShowAvgValue(); // the cache needs to hold the audio in memory during mixing, so tell it that mixing is starting wavedatacache->OnMixBegin(); while ( g_paintedtime < endtime ) { VPROF("MIX_PaintChannels inner loop"); // mix a full 'paintbuffer' of sound // clamp at paintbuffer size end = endtime; if (endtime - g_paintedtime > PAINTBUFFER_SIZE) { end = g_paintedtime + PAINTBUFFER_SIZE; } // number of 44khz samples to mix into paintbuffer, up to paintbuffer size count = end - g_paintedtime; // clear all mix buffers g_AudioDevice->MixBegin( count ); // upsample all mix buffers. // results in 44khz versions of: // SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, SOUND_BUFFER_FACINGAWAY, SOUND_BUFFER_DRY, SOUND_BUFFER_SPEAKER, SOUND_BUFFER_SPECIALs MIX_UpsampleAllPaintbuffers( list, end, count ); // apply appropriate dsp fx to each buffer, remix buffers into single quad output buffer // apply 2 or 4ch filtering to IFACINGAWAY buffer if ( g_bdirectionalfx ) { g_AudioDevice->ApplyDSPEffects( idsp_facingaway, MIX_GetPFrontFromIPaint(SOUND_BUFFER_FACINGAWAY), MIX_GetPRearFromIPaint(SOUND_BUFFER_FACINGAWAY), MIX_GetPCenterFromIPaint(SOUND_BUFFER_FACINGAWAY), count ); } if ( !g_bDspOff && list.m_hasSpeakerChannels ) { // apply 1ch filtering to SOUND_BUFFER_SPEAKER g_AudioDevice->ApplyDSPEffects( idsp_speaker, MIX_GetPFrontFromIPaint(SOUND_BUFFER_SPEAKER), MIX_GetPRearFromIPaint(SOUND_BUFFER_SPEAKER), MIX_GetPCenterFromIPaint(SOUND_BUFFER_SPEAKER), count ); // mix SOUND_BUFFER_SPEAKER with SOUND_BUFFER_ROOM and SOUND_BUFFER_FACING MIX_ScalePaintBuffer( SOUND_BUFFER_SPEAKER, count, 0.7 ); MIX_MixPaintbuffers( SOUND_BUFFER_SPEAKER, SOUND_BUFFER_FACING, SOUND_BUFFER_FACING, count, 1.0 ); // +70% dry speaker MIX_ScalePaintBuffer( SOUND_BUFFER_SPEAKER, count, 0.43 ); MIX_MixPaintbuffers( SOUND_BUFFER_SPEAKER, SOUND_BUFFER_ROOM, SOUND_BUFFER_ROOM, count, 1.0 ); // +30% wet speaker } if ( !g_bDspOff ) { // apply 1ch filtering to SOUND_BUFFER_SPECIALs for ( int iDSP = 0; iDSP < list.m_nSpecialDSPs.Count(); ++iDSP ) { bool bFoundMixer = false; for ( int i = SOUND_BUFFER_SPECIAL_START; i < g_paintBuffers.Count(); ++i ) { paintbuffer_t *pSpecialBuffer = MIX_GetPPaintFromIPaint( i ); if ( pSpecialBuffer->nSpecialDSP == list.m_nSpecialDSPs[ iDSP ] && pSpecialBuffer->idsp_specialdsp != -1 ) { g_AudioDevice->ApplyDSPEffects( pSpecialBuffer->idsp_specialdsp, MIX_GetPFrontFromIPaint( i ), MIX_GetPRearFromIPaint( i ), MIX_GetPCenterFromIPaint( i ), count ); // mix SOUND_BUFFER_SPECIALs with SOUND_BUFFER_ROOM and SOUND_BUFFER_FACING MIX_ScalePaintBuffer( i, count, 0.7 ); MIX_MixPaintbuffers( i, SOUND_BUFFER_FACING, SOUND_BUFFER_FACING, count, 1.0 ); // +70% dry speaker MIX_ScalePaintBuffer( i, count, 0.43 ); MIX_MixPaintbuffers( i, SOUND_BUFFER_ROOM, SOUND_BUFFER_ROOM, count, 1.0 ); // +30% wet speaker bFoundMixer = true; break; } } // Couldn't find a mixer with the correct DSP, so make a new one! if ( !bFoundMixer ) { bool bSurroundCenter = g_AudioDevice->IsSurroundCenter(); bool bSurround = g_AudioDevice->IsSurround() || bSurroundCenter; int nIndex = g_paintBuffers.AddToTail(); MIX_InitializePaintbuffer( &(g_paintBuffers[ nIndex ]), bSurround, bSurroundCenter ); g_paintBuffers[ nIndex ].flags = SOUND_BUSS_SPECIAL_DSP; // special dsp buffer mixes to mono g_paintBuffers[ nIndex ].fsurround = false; g_paintBuffers[ nIndex ].fsurround_center = false; g_paintBuffers[ nIndex ].idsp_specialdsp = -1; g_paintBuffers[ nIndex ].nSpecialDSP = list.m_nSpecialDSPs[ iDSP ]; g_paintBuffers[ nIndex ].nPrevSpecialDSP = g_paintBuffers[ nIndex ].nSpecialDSP; g_paintBuffers[ nIndex ].idsp_specialdsp = DSP_Alloc( g_paintBuffers[ nIndex ].nSpecialDSP, 300, 1 ); } } } // apply dsp_room effects to room buffer g_AudioDevice->ApplyDSPEffects( Get_idsp_room(), MIX_GetPFrontFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPRearFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPCenterFromIPaint(SOUND_BUFFER_ROOM), count ); // save room buffer surround status, in case we upconvert it room_fsurround_sav = proom->fsurround; room_fsurround_center_sav = proom->fsurround_center; // apply left/center/right/lrear/rrear spatial delays to room buffer if ( b_spatial_delays && !g_bDspOff && !DSP_RoomDSPIsOff() ) { // upgrade mono room buffer to surround status so we can apply spatial delays to all channels MIX_ConvertBufferToSurround( SOUND_BUFFER_ROOM ); g_AudioDevice->ApplyDSPEffects( idsp_spatial, MIX_GetPFrontFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPRearFromIPaint(SOUND_BUFFER_ROOM), MIX_GetPCenterFromIPaint(SOUND_BUFFER_ROOM), count ); } if ( g_bdirectionalfx ) // KDB: perf { // Recombine IFACING and IFACINGAWAY buffers into SOUND_BUFFER_PAINT MIX_MixPaintbuffers( SOUND_BUFFER_FACING, SOUND_BUFFER_FACINGAWAY, SOUND_BUFFER_PAINT, count, DSP_NOROOM_MIX ); // Add in dsp room fx to paintbuffer, mix at 75% MIX_MixPaintbuffers( SOUND_BUFFER_ROOM, SOUND_BUFFER_PAINT, SOUND_BUFFER_PAINT, count, DSP_ROOM_MIX ); } else { // Mix IFACING buffer with SOUND_BUFFER_ROOM // (SOUND_BUFFER_FACINGAWAY contains no data, IFACINGBBUFFER has full dry mix based on distance from listener) // if dsp disabled, mix 100% facingbuffer, otherwise, mix 75% facingbuffer + roombuffer float mix = g_bDspOff ? 1.0 : DSP_ROOM_MIX; MIX_MixPaintbuffers( SOUND_BUFFER_ROOM, SOUND_BUFFER_FACING, SOUND_BUFFER_PAINT, count, mix ); } // restore room buffer surround status, in case we upconverted it proom->fsurround = room_fsurround_sav; proom->fsurround_center = room_fsurround_center_sav; // Apply underwater fx dsp_water (serial in-line) if ( bIsUnderwater ) { // BUG: if out of water, previous delays will be heard. must clear dly buffers. g_AudioDevice->ApplyDSPEffects( idsp_water, MIX_GetPFrontFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPRearFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPCenterFromIPaint(SOUND_BUFFER_PAINT), count ); } // find dsp gain SDEBUG_GetAvgIn(SOUND_BUFFER_PAINT, count); // Apply player fx dsp_player (serial in-line) - does nothing if dsp fx are disabled g_AudioDevice->ApplyDSPEffects( idsp_player, MIX_GetPFrontFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPRearFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPCenterFromIPaint(SOUND_BUFFER_PAINT), count ); // display dsp gain SDEBUG_GetAvgOut(SOUND_BUFFER_PAINT, count); /* // apply left/center/right/lrear/rrear spatial delays to paint buffer if ( b_spatial_delays ) g_AudioDevice->ApplyDSPEffects( idsp_spatial, MIX_GetPFrontFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPRearFromIPaint(SOUND_BUFFER_PAINT), MIX_GetPCenterFromIPaint(SOUND_BUFFER_PAINT), count ); */ // Add dry buffer, set output gain to water * player dsp gain (both 1.0 if not active) MIX_MixPaintbuffers( SOUND_BUFFER_PAINT, SOUND_BUFFER_DRY, SOUND_BUFFER_PAINT, count, 1.0); // clip all values > 16 bit down to 16 bit // NOTE: This is required - the hardware buffer transfer routines no longer perform clipping. MIX_CompressPaintbuffer( SOUND_BUFFER_PAINT, count ); // transfer SOUND_BUFFER_PAINT paintbuffer out to DMA buffer MIX_SetCurrentPaintbuffer( SOUND_BUFFER_PAINT ); g_AudioDevice->TransferSamples( end ); g_paintedtime = end; } // the cache needs to hold the audio in memory during mixing, so tell it that mixing is complete wavedatacache->OnMixEnd(); } // Applies volume scaling (evenly) to all fl,fr,rl,rr volumes // used for voice ducking and panning between various mix busses // Ensures if mixing to speaker buffer, only speaker sounds pass through // Called just before mixing wav data to current paintbuffer. // a) if another player in a multiplayer game is speaking, scale all volumes down. // b) if mixing to SOUND_BUFFER_ROOM, scale all volumes by ch.dspmix and dsp_room gain // c) if mixing to SOUND_BUFFER_FACINGAWAY, scale all volumes by ch.dspface and dsp_facingaway gain // d) If SURROUND_ON, but buffer is not surround, recombined front/rear volumes // returns false if channel is to be entirely skipped. bool MIX_ScaleChannelVolume( paintbuffer_t *ppaint, channel_t *pChannel, int volume[CCHANVOLUMES], int mixchans ) { int i; int mixflag = ppaint->flags; float scale; char wavtype = pChannel->wavtype; float dspmix; // copy current channel volumes into output array ChannelCopyVolumes( pChannel, volume, 0, CCHANVOLUMES ); dspmix = pChannel->dspmix; // if dsp is off, or room dsp is off, mix 0% to mono room buffer, 100% to facing buffer if ( g_bDspOff || DSP_RoomDSPIsOff() ) dspmix = 0.0; // duck all sound volumes except speaker's voice #if !defined( NO_VOICE ) int duckScale = min((int)(g_DuckScale * 256), g_SND_VoiceOverdriveInt); #else int duckScale = (int)(g_DuckScale * 256); #endif if( duckScale < 256 ) { if( pChannel->pMixer ) { CAudioSource *pSource = pChannel->pMixer->GetSource(); if( !pSource->IsVoiceSource() ) { // Apply voice overdrive.. for (i = 0; i < CCHANVOLUMES; i++) volume[i] = (volume[i] * duckScale) >> 8; } } } // If mixing to the room buss, adjust volume based on channel's dspmix setting. // dspmix is DSP_MIX_MAX (~0.78) if sound is far from player, DSP_MIX_MIN (~0.24) if sound is near player if ( mixflag & SOUND_BUSS_ROOM ) { // set dsp mix volume, scaled by global dsp_volume float dspmixvol = fpmin(dspmix * g_dsp_volume, 1.0f); // if dspmix is 1.0, 100% of sound goes to SOUND_BUFFER_ROOM and 0% to SOUND_BUFFER_FACING for (i = 0; i < CCHANVOLUMES; i++) volume[i] = (int)((float)(volume[i]) * dspmixvol); } // If global dsp volume is less than 1, reduce dspmix (ie: increase dry volume) // If gloabl dsp volume is greater than 1, do not reduce dspmix if (g_dsp_volume < 1.0) dspmix *= g_dsp_volume; // If mixing to facing/facingaway buss, adjust volume based on sound entity's facing direction. // If sound directly faces player, ch->dspface = 1.0. If facing directly away, ch->dspface = -1.0. // mix to lowpass buffer if facing away, to allpass if facing // scale 1.0 - facing player, scale 0, facing away scale = (pChannel->dspface + 1.0) / 2.0; // UNDONE: get front cone % from channel to set this. // bias scale such that 1.0 to 'cone' is considered facing. Facing cone narrows as cone -> 1.0 // and 'cone' -> 0.0 becomes 1.0 -> 0.0 float cone = 0.6f; scale = scale * (1/cone); scale = clamp( scale, 0.0f, 1.0f ); // pan between facing and facing away buffers // if ( !g_bdirectionalfx || wavtype == CHAR_DOPPLER || wavtype == CHAR_OMNI || (wavtype == CHAR_DIRECTIONAL && mixchans == 2) ) if ( !g_bdirectionalfx || wavtype != CHAR_DIRECTIONAL ) { // if no directional fx mix 0% to facingaway buffer // if wavtype is DOPPLER, mix 0% to facingaway buffer - DOPPLER wavs have a custom mixer // if wavtype is OMNI, mix 0% to facingaway buffer - OMNI wavs have no directionality // if wavtype is DIRECTIONAL and stereo encoded, mix 0% to facingaway buffer - DIRECTIONAL STEREO wavs have a custom mixer scale = 1.0; } if ( mixflag & SOUND_BUSS_FACING ) { // facing player // if dspface is 1.0, 100% of sound goes to SOUND_BUFFER_FACING for (i = 0; i < CCHANVOLUMES; i++) volume[i] = (int)((float)(volume[i]) * scale * (1.0 - dspmix)); } else if ( mixflag & SOUND_BUSS_FACINGAWAY ) { // facing away from player // if dspface is 0.0, 100% of sound goes to SOUND_BUFFER_FACINGAWAY for (i = 0; i < CCHANVOLUMES; i++) volume[i] = (int)((float)(volume[i]) * (1.0 - scale) * (1.0 - dspmix)); } // NOTE: this must occur last in this routine: if ( g_AudioDevice->IsSurround() && !ppaint->fsurround ) { // if 4ch or 5ch spatialization on, but current mix buffer is 2ch, // recombine front + rear volumes (revert to 2ch spatialization) volume[IFRONT_RIGHT] += volume[IREAR_RIGHT]; volume[IFRONT_LEFT] += volume[IREAR_LEFT]; volume[IFRONT_RIGHTD] += volume[IREAR_RIGHTD]; volume[IFRONT_LEFTD] += volume[IREAR_LEFTD]; // if 5 ch, recombine center channel vol if ( g_AudioDevice->IsSurroundCenter() ) { volume[IFRONT_RIGHT] += volume[IFRONT_CENTER] / 2; volume[IFRONT_LEFT] += volume[IFRONT_CENTER] / 2; volume[IFRONT_RIGHTD] += volume[IFRONT_CENTERD] / 2; volume[IFRONT_LEFTD] += volume[IFRONT_CENTERD] / 2; } // clear rear & center volumes volume[IREAR_RIGHT] = 0; volume[IREAR_LEFT] = 0; volume[IFRONT_CENTER] = 0; volume[IREAR_RIGHTD] = 0; volume[IREAR_LEFTD] = 0; volume[IFRONT_CENTERD] = 0; } bool fzerovolume = true; for (i = 0; i < CCHANVOLUMES; i++) { volume[i] = clamp(volume[i], 0, 255); if (volume[i]) fzerovolume = false; } if ( fzerovolume ) { // DevMsg ("Skipping mix of 0 volume sound! \n"); return false; } return true; } //=============================================================================== // Low level mixing routines //=============================================================================== void Snd_WriteLinearBlastStereo16( void ) { #if !id386 int i; int val; for ( i=0; i>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_SHIFT]; rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT]; for (i=0 ; i> 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(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>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 ); } } }