mirror of
https://github.com/w23/xash3d-fwgs
synced 2024-12-17 14:59:57 +01:00
5e0a0765ce
The `.editorconfig` file in this repo is configured to trim all trailing whitespace regardless of whether the line is modified. Trims all trailing whitespace in the repository to make the codebase easier to work with in editors that respect `.editorconfig`. `git blame` becomes less useful on these lines but it already isn't very useful. Commands: ``` find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+ ```
1056 lines
30 KiB
C
1056 lines
30 KiB
C
/*
|
|
s_mix.c - portable code to mix sounds
|
|
Copyright (C) 2009 Uncle Mike
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "sound.h"
|
|
#include "client.h"
|
|
|
|
#define IPAINTBUFFER 0
|
|
#define IROOMBUFFER 1
|
|
#define ISTREAMBUFFER 2
|
|
|
|
#define FILTERTYPE_NONE 0
|
|
#define FILTERTYPE_LINEAR 1
|
|
#define FILTERTYPE_CUBIC 2
|
|
|
|
#define CCHANVOLUMES 2
|
|
|
|
#define SND_SCALE_BITS 7
|
|
#define SND_SCALE_SHIFT (8 - SND_SCALE_BITS)
|
|
#define SND_SCALE_LEVELS (1 << SND_SCALE_BITS)
|
|
|
|
portable_samplepair_t *g_curpaintbuffer;
|
|
portable_samplepair_t streambuffer[(PAINTBUFFER_SIZE+1)];
|
|
portable_samplepair_t paintbuffer[(PAINTBUFFER_SIZE+1)];
|
|
portable_samplepair_t roombuffer[(PAINTBUFFER_SIZE+1)];
|
|
portable_samplepair_t facingbuffer[(PAINTBUFFER_SIZE+1)];
|
|
portable_samplepair_t temppaintbuffer[(PAINTBUFFER_SIZE+1)];
|
|
paintbuffer_t paintbuffers[CPAINTBUFFERS];
|
|
|
|
int snd_scaletable[SND_SCALE_LEVELS][256];
|
|
|
|
void S_InitScaletable( void )
|
|
{
|
|
int i, j;
|
|
|
|
for( i = 0; i < SND_SCALE_LEVELS; i++ )
|
|
{
|
|
for( j = 0; j < 256; j++ )
|
|
snd_scaletable[i][j] = ((signed char)j) * i * (1<<SND_SCALE_SHIFT);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
S_TransferPaintBuffer
|
|
|
|
===================
|
|
*/
|
|
void S_TransferPaintBuffer( int endtime )
|
|
{
|
|
int *snd_p, snd_linear_count;
|
|
int lpos, lpaintedtime;
|
|
int i, val, sampleMask;
|
|
short *snd_out;
|
|
dword *pbuf;
|
|
|
|
pbuf = (dword *)dma.buffer;
|
|
snd_p = (int *)PAINTBUFFER;
|
|
lpaintedtime = paintedtime;
|
|
sampleMask = ((dma.samples >> 1) - 1);
|
|
|
|
while( lpaintedtime < endtime )
|
|
{
|
|
// handle recirculating buffer issues
|
|
lpos = lpaintedtime & sampleMask;
|
|
|
|
snd_out = (short *)pbuf + (lpos << 1);
|
|
|
|
snd_linear_count = (dma.samples>>1) - lpos;
|
|
if( lpaintedtime + snd_linear_count > endtime )
|
|
snd_linear_count = endtime - lpaintedtime;
|
|
|
|
snd_linear_count <<= 1;
|
|
|
|
// write a linear blast of samples
|
|
for( i = 0; i < snd_linear_count; i += 2 )
|
|
{
|
|
val = (snd_p[i+0] * 256) >> 8;
|
|
|
|
if( val > 0x7fff ) snd_out[i+0] = 0x7fff;
|
|
else if( val < (short)0x8000 )
|
|
snd_out[i+0] = (short)0x8000;
|
|
else snd_out[i+0] = val;
|
|
|
|
val = (snd_p[i+1] * 256) >> 8;
|
|
if( val > 0x7fff ) snd_out[i+1] = 0x7fff;
|
|
else if( val < (short)0x8000 )
|
|
snd_out[i+1] = (short)0x8000;
|
|
else snd_out[i+1] = val;
|
|
}
|
|
|
|
snd_p += snd_linear_count;
|
|
lpaintedtime += (snd_linear_count >> 1);
|
|
}
|
|
}
|
|
|
|
//===============================================================================
|
|
// Mix buffer (paintbuffer) management routines
|
|
//===============================================================================
|
|
// Activate a paintbuffer. All active paintbuffers are mixed in parallel within
|
|
// MIX_MixChannelsToPaintbuffer, according to flags
|
|
_inline void MIX_ActivatePaintbuffer( int ipaintbuffer )
|
|
{
|
|
Assert( ipaintbuffer < CPAINTBUFFERS );
|
|
paintbuffers[ipaintbuffer].factive = true;
|
|
}
|
|
|
|
// don't mix into this paintbuffer
|
|
_inline void MIX_DeactivatePaintbuffer( int ipaintbuffer )
|
|
{
|
|
Assert( ipaintbuffer < CPAINTBUFFERS );
|
|
paintbuffers[ipaintbuffer].factive = false;
|
|
}
|
|
|
|
_inline void MIX_SetCurrentPaintbuffer( int ipaintbuffer )
|
|
{
|
|
Assert( ipaintbuffer < CPAINTBUFFERS );
|
|
g_curpaintbuffer = paintbuffers[ipaintbuffer].pbuf;
|
|
Assert( g_curpaintbuffer != NULL );
|
|
}
|
|
|
|
_inline int MIX_GetCurrentPaintbufferIndex( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < CPAINTBUFFERS; i++ )
|
|
{
|
|
if( g_curpaintbuffer == paintbuffers[i].pbuf )
|
|
return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
_inline paintbuffer_t *MIX_GetCurrentPaintbufferPtr( void )
|
|
{
|
|
int ipaint = MIX_GetCurrentPaintbufferIndex();
|
|
|
|
Assert( ipaint < CPAINTBUFFERS );
|
|
return &paintbuffers[ipaint];
|
|
}
|
|
|
|
// Don't mix into any paintbuffers
|
|
_inline void MIX_DeactivateAllPaintbuffers( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < CPAINTBUFFERS; i++ )
|
|
paintbuffers[i].factive = false;
|
|
}
|
|
|
|
// set upsampling filter indexes back to 0
|
|
_inline void MIX_ResetPaintbufferFilterCounters( void )
|
|
{
|
|
int i;
|
|
|
|
for( i = 0; i < CPAINTBUFFERS; i++ )
|
|
paintbuffers[i].ifilter = FILTERTYPE_NONE;
|
|
}
|
|
|
|
_inline void MIX_ResetPaintbufferFilterCounter( int ipaintbuffer )
|
|
{
|
|
Assert( ipaintbuffer < CPAINTBUFFERS );
|
|
paintbuffers[ipaintbuffer].ifilter = 0;
|
|
}
|
|
|
|
// return pointer to front paintbuffer pbuf, given index
|
|
_inline portable_samplepair_t *MIX_GetPFrontFromIPaint( int ipaintbuffer )
|
|
{
|
|
Assert( ipaintbuffer < CPAINTBUFFERS );
|
|
return paintbuffers[ipaintbuffer].pbuf;
|
|
}
|
|
|
|
_inline paintbuffer_t *MIX_GetPPaintFromIPaint( int ipaint )
|
|
{
|
|
Assert( ipaint < CPAINTBUFFERS );
|
|
return &paintbuffers[ipaint];
|
|
}
|
|
|
|
void MIX_FreeAllPaintbuffers( void )
|
|
{
|
|
// clear paintbuffer structs
|
|
memset( paintbuffers, 0, CPAINTBUFFERS * sizeof( paintbuffer_t ));
|
|
}
|
|
|
|
// Initialize paintbuffers array, set current paint buffer to main output buffer IPAINTBUFFER
|
|
void MIX_InitAllPaintbuffers( void )
|
|
{
|
|
// clear paintbuffer structs
|
|
memset( paintbuffers, 0, CPAINTBUFFERS * sizeof( paintbuffer_t ));
|
|
|
|
paintbuffers[IPAINTBUFFER].pbuf = paintbuffer;
|
|
paintbuffers[IROOMBUFFER].pbuf = roombuffer;
|
|
paintbuffers[ISTREAMBUFFER].pbuf = streambuffer;
|
|
|
|
MIX_SetCurrentPaintbuffer( IPAINTBUFFER );
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
CHANNEL MIXING
|
|
|
|
===============================================================================
|
|
*/
|
|
void S_PaintMonoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount )
|
|
{
|
|
int *lscale, *rscale;
|
|
int i, data;
|
|
|
|
lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
|
|
rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
|
|
|
|
for( i = 0; i < outCount; i++ )
|
|
{
|
|
data = pData[i];
|
|
pbuf[i].left += lscale[data];
|
|
pbuf[i].right += rscale[data];
|
|
}
|
|
}
|
|
|
|
void S_PaintStereoFrom8( portable_samplepair_t *pbuf, int *volume, byte *pData, int outCount )
|
|
{
|
|
int *lscale, *rscale;
|
|
uint left, right;
|
|
word *data;
|
|
int i;
|
|
|
|
lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
|
|
rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
|
|
data = (word *)pData;
|
|
|
|
for( i = 0; i < outCount; i++, data++ )
|
|
{
|
|
left = (byte)((*data & 0x00FF));
|
|
right = (byte)((*data & 0xFF00) >> 8);
|
|
pbuf[i].left += lscale[left];
|
|
pbuf[i].right += rscale[right];
|
|
}
|
|
}
|
|
|
|
void S_PaintMonoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount )
|
|
{
|
|
int left, right;
|
|
int i, data;
|
|
|
|
for( i = 0; i < outCount; i++ )
|
|
{
|
|
data = pData[i];
|
|
left = ( data * volume[0]) >> 8;
|
|
right = (data * volume[1]) >> 8;
|
|
pbuf[i].left += left;
|
|
pbuf[i].right += right;
|
|
}
|
|
}
|
|
|
|
void S_PaintStereoFrom16( portable_samplepair_t *pbuf, int *volume, short *pData, int outCount )
|
|
{
|
|
uint *data;
|
|
int left, right;
|
|
int i;
|
|
|
|
data = (uint *)pData;
|
|
|
|
for( i = 0; i < outCount; i++, data++ )
|
|
{
|
|
left = (signed short)((*data & 0x0000FFFF));
|
|
right = (signed short)((*data & 0xFFFF0000) >> 16);
|
|
|
|
left = (left * volume[0]) >> 8;
|
|
right = (right * volume[1]) >> 8;
|
|
|
|
pbuf[i].left += left;
|
|
pbuf[i].right += right;
|
|
}
|
|
}
|
|
|
|
void S_Mix8MonoTimeCompress( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress )
|
|
{
|
|
}
|
|
|
|
void S_Mix8Mono( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount, int timecompress )
|
|
{
|
|
int i, sampleIndex = 0;
|
|
uint sampleFrac = inputOffset;
|
|
int *lscale, *rscale;
|
|
|
|
if( timecompress != 0 )
|
|
{
|
|
S_Mix8MonoTimeCompress( pbuf, volume, pData, inputOffset, rateScale, outCount, timecompress );
|
|
// return;
|
|
}
|
|
|
|
// Not using pitch shift?
|
|
if( rateScale == FIX( 1 ))
|
|
{
|
|
S_PaintMonoFrom8( pbuf, volume, pData, outCount );
|
|
return;
|
|
}
|
|
|
|
lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
|
|
rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
|
|
|
|
for( i = 0; i < outCount; i++ )
|
|
{
|
|
pbuf[i].left += lscale[pData[sampleIndex]];
|
|
pbuf[i].right += rscale[pData[sampleIndex]];
|
|
sampleFrac += rateScale;
|
|
sampleIndex += FIX_INTPART( sampleFrac );
|
|
sampleFrac = FIX_FRACPART( sampleFrac );
|
|
}
|
|
}
|
|
|
|
void S_Mix8Stereo( portable_samplepair_t *pbuf, int *volume, byte *pData, int inputOffset, uint rateScale, int outCount )
|
|
{
|
|
int i, sampleIndex = 0;
|
|
uint sampleFrac = inputOffset;
|
|
int *lscale, *rscale;
|
|
|
|
// Not using pitch shift?
|
|
if( rateScale == FIX( 1 ))
|
|
{
|
|
S_PaintStereoFrom8( pbuf, volume, pData, outCount );
|
|
return;
|
|
}
|
|
|
|
lscale = snd_scaletable[volume[0] >> SND_SCALE_SHIFT];
|
|
rscale = snd_scaletable[volume[1] >> SND_SCALE_SHIFT];
|
|
|
|
for( i = 0; i < outCount; i++ )
|
|
{
|
|
pbuf[i].left += lscale[pData[sampleIndex+0]];
|
|
pbuf[i].right += rscale[pData[sampleIndex+1]];
|
|
sampleFrac += rateScale;
|
|
sampleIndex += FIX_INTPART( sampleFrac )<<1;
|
|
sampleFrac = FIX_FRACPART( sampleFrac );
|
|
}
|
|
}
|
|
|
|
void S_Mix16Mono( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount )
|
|
{
|
|
int i, sampleIndex = 0;
|
|
uint sampleFrac = inputOffset;
|
|
|
|
// Not using pitch shift?
|
|
if( rateScale == FIX( 1 ))
|
|
{
|
|
S_PaintMonoFrom16( pbuf, volume, pData, outCount );
|
|
return;
|
|
}
|
|
|
|
for( i = 0; i < outCount; i++ )
|
|
{
|
|
pbuf[i].left += (volume[0] * (int)( pData[sampleIndex] ))>>8;
|
|
pbuf[i].right += (volume[1] * (int)( pData[sampleIndex] ))>>8;
|
|
sampleFrac += rateScale;
|
|
sampleIndex += FIX_INTPART( sampleFrac );
|
|
sampleFrac = FIX_FRACPART( sampleFrac );
|
|
}
|
|
}
|
|
|
|
void S_Mix16Stereo( portable_samplepair_t *pbuf, int *volume, short *pData, int inputOffset, uint rateScale, int outCount )
|
|
{
|
|
int i, sampleIndex = 0;
|
|
uint sampleFrac = inputOffset;
|
|
|
|
// Not using pitch shift?
|
|
if( rateScale == FIX( 1 ))
|
|
{
|
|
S_PaintStereoFrom16( pbuf, volume, pData, outCount );
|
|
return;
|
|
}
|
|
|
|
for( i = 0; i < outCount; i++ )
|
|
{
|
|
pbuf[i].left += (volume[0] * (int)( pData[sampleIndex+0] ))>>8;
|
|
pbuf[i].right += (volume[1] * (int)( pData[sampleIndex+1] ))>>8;
|
|
sampleFrac += rateScale;
|
|
sampleIndex += FIX_INTPART(sampleFrac)<<1;
|
|
sampleFrac = FIX_FRACPART(sampleFrac);
|
|
}
|
|
}
|
|
|
|
void S_MixChannel( channel_t *pChannel, void *pData, int outputOffset, int inputOffset, uint fracRate, int outCount, int timecompress )
|
|
{
|
|
int pvol[CCHANVOLUMES];
|
|
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
|
|
wavdata_t *pSource = pChannel->sfx->cache;
|
|
portable_samplepair_t *pbuf;
|
|
|
|
Assert( pSource != NULL );
|
|
|
|
pvol[0] = bound( 0, pChannel->leftvol, 255 );
|
|
pvol[1] = bound( 0, pChannel->rightvol, 255 );
|
|
pbuf = ppaint->pbuf + outputOffset;
|
|
|
|
if( pSource->channels == 1 )
|
|
{
|
|
if( pSource->width == 1 )
|
|
S_Mix8Mono( pbuf, pvol, pData, inputOffset, fracRate, outCount, timecompress );
|
|
else S_Mix16Mono( pbuf, pvol, (short *)pData, inputOffset, fracRate, outCount );
|
|
}
|
|
else
|
|
{
|
|
if( pSource->width == 1 )
|
|
S_Mix8Stereo( pbuf, pvol, pData, inputOffset, fracRate, outCount );
|
|
else S_Mix16Stereo( pbuf, pvol, (short *)pData, inputOffset, fracRate, outCount );
|
|
}
|
|
}
|
|
|
|
int S_MixDataToDevice( channel_t *pChannel, int sampleCount, int outRate, int outOffset, int timeCompress )
|
|
{
|
|
// save this to compute total output
|
|
int startingOffset = outOffset;
|
|
float inputRate = ( pChannel->pitch * pChannel->sfx->cache->rate );
|
|
float rate = inputRate / outRate;
|
|
|
|
// shouldn't be playing this if finished, but return if we are
|
|
if( pChannel->pMixer.finished )
|
|
return 0;
|
|
|
|
// If we are terminating this wave prematurely, then make sure we detect the limit
|
|
if( pChannel->pMixer.forcedEndSample )
|
|
{
|
|
// how many total input samples will we need?
|
|
int samplesRequired = (int)(sampleCount * rate);
|
|
|
|
// will this hit the end?
|
|
if( pChannel->pMixer.sample + samplesRequired >= pChannel->pMixer.forcedEndSample )
|
|
{
|
|
// yes, mark finished and truncate the sample request
|
|
pChannel->pMixer.finished = true;
|
|
sampleCount = (int)((pChannel->pMixer.forcedEndSample - pChannel->pMixer.sample) / rate );
|
|
}
|
|
}
|
|
|
|
while( sampleCount > 0 )
|
|
{
|
|
int availableSamples, outSampleCount;
|
|
wavdata_t *pSource = pChannel->sfx->cache;
|
|
qboolean use_loop = pChannel->use_loop;
|
|
void *pData = NULL;
|
|
double sampleFrac;
|
|
int i, j;
|
|
|
|
// compute number of input samples required
|
|
double end = pChannel->pMixer.sample + rate * sampleCount;
|
|
int inputSampleCount = (int)(ceil( end ) - floor( pChannel->pMixer.sample ));
|
|
|
|
availableSamples = S_GetOutputData( pSource, &pData, pChannel->pMixer.sample, inputSampleCount, use_loop );
|
|
|
|
// none available, bail out
|
|
if( !availableSamples ) break;
|
|
|
|
sampleFrac = pChannel->pMixer.sample - floor( pChannel->pMixer.sample );
|
|
|
|
if( availableSamples < inputSampleCount )
|
|
{
|
|
// how many samples are there given the number of input samples and the rate.
|
|
outSampleCount = (int)ceil(( availableSamples - sampleFrac ) / rate );
|
|
}
|
|
else
|
|
{
|
|
outSampleCount = sampleCount;
|
|
}
|
|
|
|
// Verify that we won't get a buffer overrun.
|
|
Assert( floor( sampleFrac + rate * ( outSampleCount - 1 )) <= availableSamples );
|
|
|
|
// save current paintbuffer
|
|
j = MIX_GetCurrentPaintbufferIndex();
|
|
|
|
for( i = 0; i < CPAINTBUFFERS; i++ )
|
|
{
|
|
if( !paintbuffers[i].factive )
|
|
continue;
|
|
|
|
// mix chan into all active paintbuffers
|
|
MIX_SetCurrentPaintbuffer( i );
|
|
|
|
S_MixChannel( pChannel, pData, outOffset, FIX_FLOAT( sampleFrac ), FIX_FLOAT( rate ), outSampleCount, timeCompress );
|
|
}
|
|
|
|
MIX_SetCurrentPaintbuffer( j );
|
|
|
|
pChannel->pMixer.sample += outSampleCount * rate;
|
|
outOffset += outSampleCount;
|
|
sampleCount -= outSampleCount;
|
|
}
|
|
|
|
// Did we run out of samples? if so, mark finished
|
|
if( sampleCount > 0 )
|
|
{
|
|
pChannel->pMixer.finished = true;
|
|
}
|
|
|
|
// total number of samples mixed !!! at the output clock rate !!!
|
|
return outOffset - startingOffset;
|
|
}
|
|
|
|
qboolean S_ShouldContinueMixing( channel_t *ch )
|
|
{
|
|
if( ch->isSentence )
|
|
{
|
|
if( ch->currentWord )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
return !ch->pMixer.finished;
|
|
}
|
|
|
|
// 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( int endtime, int rate, int outputRate )
|
|
{
|
|
channel_t *ch;
|
|
wavdata_t *pSource;
|
|
int i, sampleCount;
|
|
qboolean bZeroVolume;
|
|
|
|
// mix each channel into paintbuffer
|
|
ch = channels;
|
|
|
|
// validate parameters
|
|
Assert( outputRate <= SOUND_DMA_SPEED );
|
|
|
|
// make sure we're not discarding data
|
|
Assert( !(( endtime - paintedtime ) & 0x3 ) || ( outputRate == SOUND_DMA_SPEED ));
|
|
|
|
// 44k: try to mix this many samples at outputRate
|
|
sampleCount = ( endtime - paintedtime ) / ( SOUND_DMA_SPEED / outputRate );
|
|
|
|
if( sampleCount <= 0 ) return;
|
|
|
|
for( i = 0; i < total_channels; i++, ch++ )
|
|
{
|
|
if( !ch->sfx ) continue;
|
|
|
|
// NOTE: background map is allow both type sounds: menu and game
|
|
if( !cl.background )
|
|
{
|
|
if( cls.key_dest == key_console && ch->localsound )
|
|
{
|
|
// play, playvol
|
|
}
|
|
else if(( s_listener.inmenu || s_listener.paused ) && !ch->localsound )
|
|
{
|
|
// play only local sounds, keep pause for other
|
|
continue;
|
|
}
|
|
else if( !s_listener.inmenu && !s_listener.active && !ch->staticsound )
|
|
{
|
|
// play only ambient sounds, keep pause for other
|
|
continue;
|
|
}
|
|
}
|
|
else if( cls.key_dest == key_console )
|
|
continue; // silent mode in console
|
|
|
|
pSource = S_LoadSound( ch->sfx );
|
|
|
|
// 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.
|
|
bZeroVolume = !ch->leftvol && !ch->rightvol;
|
|
|
|
if( !bZeroVolume )
|
|
{
|
|
// this values matched with GoldSrc
|
|
if( ch->leftvol < 8 && ch->rightvol < 8 )
|
|
bZeroVolume = true;
|
|
}
|
|
|
|
if( !pSource || ( bZeroVolume && pSource->loopStart == -1 ))
|
|
{
|
|
if( !pSource )
|
|
{
|
|
S_FreeChannel( ch );
|
|
continue;
|
|
}
|
|
}
|
|
else if( bZeroVolume )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// multipass mixing - only mix samples of specified sample rate
|
|
switch( rate )
|
|
{
|
|
case SOUND_11k:
|
|
case SOUND_22k:
|
|
case SOUND_44k:
|
|
if( rate != pSource->rate )
|
|
continue;
|
|
break;
|
|
default: break;
|
|
}
|
|
|
|
// get playback pitch
|
|
if( ch->isSentence )
|
|
ch->pitch = VOX_ModifyPitch( ch, ch->basePitch * 0.01f );
|
|
else ch->pitch = ch->basePitch * 0.01f;
|
|
|
|
if( CL_GetEntityByIndex( ch->entnum ) && ( ch->entchannel == CHAN_VOICE ))
|
|
{
|
|
if( pSource->width == 1 )
|
|
SND_MoveMouth8( ch, pSource, sampleCount );
|
|
else SND_MoveMouth16( ch, pSource, sampleCount );
|
|
}
|
|
|
|
// mix channel to all active paintbuffers.
|
|
// NOTE: must be called once per channel only - consecutive calls retrieve additional data.
|
|
if( ch->isSentence )
|
|
VOX_MixDataToDevice( ch, sampleCount, outputRate, 0 );
|
|
else S_MixDataToDevice( ch, sampleCount, outputRate, 0, 0 );
|
|
|
|
if( !S_ShouldContinueMixing( ch ))
|
|
{
|
|
S_FreeChannel( ch );
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
// 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, same size as 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+0, pbuffer, pfiltermem );
|
|
psamp2 = S_GetNextpFilter( i+1, pbuffer, pfiltermem );
|
|
psamp3 = S_GetNextpFilter( i+2, pbuffer, pfiltermem );
|
|
|
|
// write out original sample to interpolation buffer
|
|
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
|
|
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
|
|
temppaintbuffer[outpos++].right = a/8 + b/4 + c/2 + x0;
|
|
|
|
Assert( outpos <= ( sizeof( temppaintbuffer ) / sizeof( temppaintbuffer[0] )));
|
|
}
|
|
|
|
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] = 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];
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
int upCount = count<<1;
|
|
int i, j;
|
|
|
|
// 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 )
|
|
{
|
|
case FILTERTYPE_LINEAR:
|
|
S_Interpolate2xLinear( pbuffer, pfiltermem, cfltmem, count );
|
|
break;
|
|
case FILTERTYPE_CUBIC:
|
|
S_Interpolate2xCubic( pbuffer, pfiltermem, cfltmem, count );
|
|
break;
|
|
default: // no filter
|
|
break;
|
|
}
|
|
}
|
|
|
|
// zero out all paintbuffers
|
|
void MIX_ClearAllPaintBuffers( int SampleCount, qboolean clearFilters )
|
|
{
|
|
int count = min( SampleCount, PAINTBUFFER_SIZE );
|
|
int i;
|
|
|
|
// zero out all paintbuffer data (ignore sampleCount)
|
|
for( i = 0; i < CPAINTBUFFERS; i++ )
|
|
{
|
|
if( paintbuffers[i].pbuf != NULL )
|
|
memset( paintbuffers[i].pbuf, 0, (count+1) * sizeof( portable_samplepair_t ));
|
|
|
|
if( clearFilters )
|
|
{
|
|
memset( paintbuffers[i].fltmem, 0, sizeof( paintbuffers[i].fltmem ));
|
|
}
|
|
}
|
|
|
|
if( clearFilters )
|
|
{
|
|
MIX_ResetPaintbufferFilterCounters();
|
|
}
|
|
}
|
|
|
|
// mixes pbuf1 + pbuf2 into pbuf3, count samples
|
|
// fgain is output gain 0-1.0
|
|
// NOTE: pbuf3 may equal pbuf1 or pbuf2!
|
|
void MIX_MixPaintbuffers( int ibuf1, int ibuf2, int ibuf3, int count, float fgain )
|
|
{
|
|
portable_samplepair_t *pbuf1, *pbuf2, *pbuf3;
|
|
int i, gain;
|
|
|
|
gain = 256 * fgain;
|
|
|
|
Assert( count <= PAINTBUFFER_SIZE );
|
|
Assert( ibuf1 < CPAINTBUFFERS );
|
|
Assert( ibuf2 < CPAINTBUFFERS );
|
|
Assert( ibuf3 < CPAINTBUFFERS );
|
|
|
|
pbuf1 = paintbuffers[ibuf1].pbuf;
|
|
pbuf2 = paintbuffers[ibuf2].pbuf;
|
|
pbuf3 = paintbuffers[ibuf3].pbuf;
|
|
|
|
// destination buffer stereo - average n chans down to stereo
|
|
|
|
// destination 2ch:
|
|
// pb1 2ch + pb2 2ch -> pb3 2ch
|
|
// pb1 2ch + pb2 (4ch->2ch) -> pb3 2ch
|
|
// pb1 (4ch->2ch) + pb2 (4ch->2ch) -> pb3 2ch
|
|
|
|
// mix front channels
|
|
for( i = 0; i < count; i++ )
|
|
{
|
|
pbuf3[i].left = pbuf1[i].left;
|
|
pbuf3[i].right = pbuf1[i].right;
|
|
pbuf3[i].left += (pbuf2[i].left * gain) >> 8;
|
|
pbuf3[i].right += (pbuf2[i].right * gain) >> 8;
|
|
}
|
|
}
|
|
|
|
void MIX_CompressPaintbuffer( int ipaint, int count )
|
|
{
|
|
portable_samplepair_t *pbuf;
|
|
paintbuffer_t *ppaint;
|
|
int i;
|
|
|
|
ppaint = MIX_GetPPaintFromIPaint( ipaint );
|
|
pbuf = ppaint->pbuf;
|
|
|
|
for( i = 0; i < count; i++, pbuf++ )
|
|
{
|
|
pbuf->left = CLIP( pbuf->left );
|
|
pbuf->right = CLIP( pbuf->right );
|
|
}
|
|
}
|
|
|
|
void S_MixUpsample( int sampleCount, int filtertype )
|
|
{
|
|
paintbuffer_t *ppaint = MIX_GetCurrentPaintbufferPtr();
|
|
int ifilter = ppaint->ifilter;
|
|
|
|
Assert( ifilter < CPAINTFILTERS );
|
|
|
|
S_MixBufferUpsample2x( sampleCount, ppaint->pbuf, &(ppaint->fltmem[ifilter][0]), CPAINTFILTERMEM, filtertype );
|
|
|
|
// make sure on next upsample pass for this paintbuffer, new filter memory is used
|
|
ppaint->ifilter++;
|
|
}
|
|
|
|
void MIX_MixStreamBuffer( int end )
|
|
{
|
|
portable_samplepair_t *pbuf;
|
|
rawchan_t *ch;
|
|
|
|
pbuf = MIX_GetPFrontFromIPaint( ISTREAMBUFFER );
|
|
ch = S_FindRawChannel( S_RAW_SOUND_BACKGROUNDTRACK, false );
|
|
|
|
// clear the paint buffer
|
|
if( s_listener.paused || !ch || ch->s_rawend < paintedtime )
|
|
{
|
|
memset( pbuf, 0, (end - paintedtime) * sizeof( portable_samplepair_t ));
|
|
}
|
|
else
|
|
{
|
|
int i, stop;
|
|
|
|
// copy from the streaming sound source
|
|
stop = (end < ch->s_rawend) ? end : ch->s_rawend;
|
|
|
|
for( i = paintedtime; i < stop; i++ )
|
|
{
|
|
pbuf[i-paintedtime].left = ( ch->rawsamples[i & ( ch->max_samples - 1 )].left * ch->leftvol ) >> 8;
|
|
pbuf[i-paintedtime].right = ( ch->rawsamples[i & ( ch->max_samples - 1 )].right * ch->rightvol ) >> 8;
|
|
}
|
|
|
|
for( ; i < end; i++ )
|
|
pbuf[i-paintedtime].left = pbuf[i-paintedtime].right = 0;
|
|
}
|
|
}
|
|
|
|
void MIX_MixRawSamplesBuffer( int end )
|
|
{
|
|
portable_samplepair_t *pbuf;
|
|
uint i, j, stop;
|
|
|
|
pbuf = MIX_GetCurrentPaintbufferPtr()->pbuf;
|
|
|
|
if( s_listener.paused ) return;
|
|
|
|
// paint in the raw channels
|
|
for( i = 0; i < MAX_RAW_CHANNELS; i++ )
|
|
{
|
|
// copy from the streaming sound source
|
|
rawchan_t *ch = raw_channels[i];
|
|
|
|
// background track should be mixing into another buffer
|
|
if( !ch || ch->entnum == S_RAW_SOUND_BACKGROUNDTRACK )
|
|
continue;
|
|
|
|
// not audible
|
|
if( !ch->leftvol && !ch->rightvol )
|
|
continue;
|
|
|
|
stop = (end < ch->s_rawend) ? end : ch->s_rawend;
|
|
|
|
for( j = paintedtime; j < stop; j++ )
|
|
{
|
|
pbuf[j-paintedtime].left += ( ch->rawsamples[j & ( ch->max_samples - 1 )].left * ch->leftvol ) >> 8;
|
|
pbuf[j-paintedtime].right += ( ch->rawsamples[j & ( ch->max_samples - 1 )].right * ch->rightvol ) >> 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
// upsample and mix sounds into final 44khz versions of:
|
|
// IROOMBUFFER, IFACINGBUFFER, IFACINGAWAY
|
|
// dsp fx are then applied to these buffers by the caller.
|
|
// caller also remixes all into final IPAINTBUFFER output.
|
|
void MIX_UpsampleAllPaintbuffers( int end, int count )
|
|
{
|
|
// process stream buffer
|
|
MIX_MixStreamBuffer( end );
|
|
|
|
// 11khz sounds are mixed into 3 buffers based on distance from listener, and facing direction
|
|
// These buffers are facing, facingaway, room
|
|
// 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();
|
|
|
|
// only mix to roombuffer if dsp fx are on KDB: perf
|
|
MIX_ActivatePaintbuffer( IROOMBUFFER ); // operates on MIX_MixChannelsToPaintbuffer
|
|
|
|
// mix 11khz sounds:
|
|
MIX_MixChannelsToPaintbuffer( end, SOUND_11k, SOUND_11k );
|
|
|
|
// upsample all 11khz buffers by 2x
|
|
// only upsample roombuffer if dsp fx are on KDB: perf
|
|
MIX_SetCurrentPaintbuffer( IROOMBUFFER ); // operates on MixUpSample
|
|
S_MixUpsample( count / ( SOUND_DMA_SPEED / SOUND_11k ), s_lerping->value );
|
|
|
|
// mix 22khz sounds:
|
|
MIX_MixChannelsToPaintbuffer( end, SOUND_22k, SOUND_22k );
|
|
|
|
// upsample all 22khz buffers by 2x
|
|
// only upsample roombuffer if dsp fx are on KDB: perf
|
|
MIX_SetCurrentPaintbuffer( IROOMBUFFER );
|
|
S_MixUpsample( count / ( SOUND_DMA_SPEED / SOUND_22k ), s_lerping->value );
|
|
|
|
// mix all 44khz sounds to all active paintbuffers
|
|
MIX_MixChannelsToPaintbuffer( end, SOUND_44k, SOUND_DMA_SPEED );
|
|
|
|
// mix raw samples from the video streams
|
|
MIX_SetCurrentPaintbuffer( IROOMBUFFER );
|
|
MIX_MixRawSamplesBuffer( end );
|
|
|
|
MIX_DeactivateAllPaintbuffers();
|
|
MIX_SetCurrentPaintbuffer( IPAINTBUFFER );
|
|
}
|
|
|
|
void MIX_PaintChannels( int endtime )
|
|
{
|
|
int end, count;
|
|
|
|
CheckNewDspPresets();
|
|
|
|
while( paintedtime < endtime )
|
|
{
|
|
// if paintbuffer is smaller than DMA buffer
|
|
end = endtime;
|
|
if( endtime - paintedtime > PAINTBUFFER_SIZE )
|
|
end = paintedtime + PAINTBUFFER_SIZE;
|
|
|
|
// number of 44khz samples to mix into paintbuffer, up to paintbuffer size
|
|
count = end - paintedtime;
|
|
|
|
// clear the all mix buffers
|
|
MIX_ClearAllPaintBuffers( count, false );
|
|
|
|
MIX_UpsampleAllPaintbuffers( end, count );
|
|
|
|
// process all sounds with DSP
|
|
DSP_Process( idsp_room, MIX_GetPFrontFromIPaint( IROOMBUFFER ), count );
|
|
|
|
// add music or soundtrack from movie (no dsp)
|
|
MIX_MixPaintbuffers( IPAINTBUFFER, IROOMBUFFER, IPAINTBUFFER, count, S_GetMasterVolume() );
|
|
|
|
// add music or soundtrack from movie (no dsp)
|
|
MIX_MixPaintbuffers( IPAINTBUFFER, ISTREAMBUFFER, IPAINTBUFFER, count, S_GetMusicVolume() );
|
|
|
|
// clip all values > 16 bit down to 16 bit
|
|
MIX_CompressPaintbuffer( IPAINTBUFFER, count );
|
|
|
|
// transfer IPAINTBUFFER paintbuffer out to DMA buffer
|
|
MIX_SetCurrentPaintbuffer( IPAINTBUFFER );
|
|
|
|
// transfer out according to DMA format
|
|
S_TransferPaintBuffer( end );
|
|
paintedtime = end;
|
|
}
|
|
}
|