mirror of https://github.com/FWGS/xash3d-fwgs
307 lines
7.0 KiB
C
307 lines
7.0 KiB
C
/*
|
|
s_utils.c - common sound functions
|
|
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"
|
|
|
|
// hardcoded macros to test for zero crossing
|
|
#define ZERO_X_8( b ) (( b ) < 2 && ( b ) > -2 )
|
|
#define ZERO_X_16( b ) (( b ) < 512 && ( b ) > -512 )
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Search backward for a zero crossing starting at sample
|
|
// Input : sample - starting point
|
|
// Output : position of zero crossing
|
|
//-----------------------------------------------------------------------------
|
|
int S_ZeroCrossingBefore( wavdata_t *pWaveData, int sample )
|
|
{
|
|
if( pWaveData == NULL )
|
|
return sample;
|
|
|
|
if( pWaveData->type == WF_PCMDATA )
|
|
{
|
|
int sampleSize;
|
|
|
|
sampleSize = pWaveData->width * pWaveData->channels;
|
|
|
|
// this can never be zero -- other functions divide by this.
|
|
// This should never happen, but avoid crashing
|
|
if( sampleSize <= 0 ) sampleSize = 1;
|
|
|
|
if( pWaveData->width == 1 )
|
|
{
|
|
signed char *pData = (signed char *)(pWaveData->buffer + sample * sampleSize);
|
|
qboolean zero = false;
|
|
|
|
if( pWaveData->channels == 1 )
|
|
{
|
|
while( sample > 0 && !zero )
|
|
{
|
|
if( ZERO_X_8( *pData ))
|
|
{
|
|
zero = true;
|
|
}
|
|
else
|
|
{
|
|
sample--;
|
|
pData--;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while( sample > 0 && !zero )
|
|
{
|
|
if( ZERO_X_8( *pData ) && ZERO_X_8( pData[1] ))
|
|
{
|
|
zero = true;
|
|
}
|
|
else
|
|
{
|
|
sample--;
|
|
pData--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
short *pData = (short *)(pWaveData->buffer + sample * sampleSize);
|
|
qboolean zero = false;
|
|
|
|
if( pWaveData->channels == 1 )
|
|
{
|
|
while( sample > 0 && !zero )
|
|
{
|
|
if( ZERO_X_16(*pData ))
|
|
{
|
|
zero = true;
|
|
}
|
|
else
|
|
{
|
|
pData--;
|
|
sample--;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while( sample > 0 && !zero )
|
|
{
|
|
if( ZERO_X_16( *pData ) && ZERO_X_16( pData[1] ))
|
|
{
|
|
zero = true;
|
|
}
|
|
else
|
|
{
|
|
sample--;
|
|
pData--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return sample;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Search forward for a zero crossing
|
|
// Input : sample - starting point
|
|
// Output : position of found zero crossing
|
|
//-----------------------------------------------------------------------------
|
|
int S_ZeroCrossingAfter( wavdata_t *pWaveData, int sample )
|
|
{
|
|
if( pWaveData == NULL )
|
|
return sample;
|
|
|
|
if( pWaveData->type == WF_PCMDATA )
|
|
{
|
|
int sampleSize;
|
|
|
|
sampleSize = pWaveData->width * pWaveData->channels;
|
|
|
|
// this can never be zero -- other functions divide by this.
|
|
// This should never happen, but avoid crashing
|
|
if( sampleSize <= 0 ) sampleSize = 1;
|
|
|
|
if( pWaveData->width == 1 ) // 8-bit
|
|
{
|
|
signed char *pData = (signed char *)(pWaveData->buffer + sample * sampleSize);
|
|
qboolean zero = false;
|
|
|
|
if( pWaveData->channels == 1 )
|
|
{
|
|
while( sample < pWaveData->samples && !zero )
|
|
{
|
|
if( ZERO_X_8( *pData ))
|
|
{
|
|
zero = true;
|
|
}
|
|
else
|
|
{
|
|
sample++;
|
|
pData++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while( sample < pWaveData->samples && !zero )
|
|
{
|
|
if( ZERO_X_8( *pData ) && ZERO_X_8( pData[1] ))
|
|
{
|
|
zero = true;
|
|
}
|
|
else
|
|
{
|
|
sample++;
|
|
pData++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
short *pData = (short *)(pWaveData->buffer + sample * sampleSize);
|
|
qboolean zero = false;
|
|
|
|
if( pWaveData->channels == 1 )
|
|
{
|
|
while( sample > 0 && !zero )
|
|
{
|
|
if( ZERO_X_16( *pData ))
|
|
{
|
|
zero = true;
|
|
}
|
|
else
|
|
{
|
|
pData++;
|
|
sample++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while( sample > 0 && !zero )
|
|
{
|
|
if( ZERO_X_16( *pData ) && ZERO_X_16( pData[1] ))
|
|
{
|
|
zero = true;
|
|
}
|
|
else
|
|
{
|
|
sample++;
|
|
pData++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return sample;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: wrap the position wrt looping
|
|
// Input : samplePosition - absolute position
|
|
// Output : int - looped position
|
|
//-----------------------------------------------------------------------------
|
|
int S_ConvertLoopedPosition( wavdata_t *pSource, int samplePosition, qboolean use_loop )
|
|
{
|
|
// if the wave is looping and we're past the end of the sample
|
|
// convert to a position within the loop
|
|
// At the end of the loop, we return a short buffer, and subsequent call
|
|
// will loop back and get the rest of the buffer
|
|
if( pSource->loopStart >= 0 && samplePosition >= pSource->samples && use_loop )
|
|
{
|
|
// size of loop
|
|
int loopSize = pSource->samples - pSource->loopStart;
|
|
|
|
// subtract off starting bit of the wave
|
|
samplePosition -= pSource->loopStart;
|
|
|
|
if( loopSize )
|
|
{
|
|
// "real" position in memory (mod off extra loops)
|
|
samplePosition = pSource->loopStart + ( samplePosition % loopSize );
|
|
}
|
|
// ERROR? if no loopSize
|
|
}
|
|
|
|
return samplePosition;
|
|
}
|
|
|
|
int S_GetOutputData( wavdata_t *pSource, void **pData, int samplePosition, int sampleCount, qboolean use_loop )
|
|
{
|
|
int totalSampleCount;
|
|
int sampleSize;
|
|
|
|
// handle position looping
|
|
samplePosition = S_ConvertLoopedPosition( pSource, samplePosition, use_loop );
|
|
|
|
// how many samples are available (linearly not counting looping)
|
|
totalSampleCount = pSource->samples - samplePosition;
|
|
|
|
// may be asking for a sample out of range, clip at zero
|
|
if( totalSampleCount < 0 ) totalSampleCount = 0;
|
|
|
|
// clip max output samples to max available
|
|
if( sampleCount > totalSampleCount )
|
|
sampleCount = totalSampleCount;
|
|
|
|
sampleSize = pSource->width * pSource->channels;
|
|
|
|
// this can never be zero -- other functions divide by this.
|
|
// This should never happen, but avoid crashing
|
|
if( sampleSize <= 0 ) sampleSize = 1;
|
|
|
|
// byte offset in sample database
|
|
samplePosition *= sampleSize;
|
|
|
|
// if we are returning some samples, store the pointer
|
|
if( sampleCount )
|
|
{
|
|
*pData = pSource->buffer + samplePosition;
|
|
}
|
|
|
|
return sampleCount;
|
|
}
|
|
|
|
// move the current position to newPosition
|
|
void S_SetSampleStart( channel_t *pChan, wavdata_t *pSource, int newPosition )
|
|
{
|
|
if( pSource )
|
|
newPosition = S_ZeroCrossingAfter( pSource, newPosition );
|
|
|
|
pChan->pMixer.sample = newPosition;
|
|
}
|
|
|
|
// end playback at newEndPosition
|
|
void S_SetSampleEnd( channel_t *pChan, wavdata_t *pSource, int newEndPosition )
|
|
{
|
|
// forced end of zero means play the whole sample
|
|
if( !newEndPosition ) newEndPosition = 1;
|
|
|
|
if( pSource )
|
|
newEndPosition = S_ZeroCrossingBefore( pSource, newEndPosition );
|
|
|
|
// past current position? limit.
|
|
if( newEndPosition < pChan->pMixer.sample )
|
|
newEndPosition = pChan->pMixer.sample;
|
|
|
|
pChan->pMixer.forcedEndSample = newEndPosition;
|
|
}
|