2018-04-13 19:23:45 +03:00
/*
snd_utils . c - sound common tools
Copyright ( C ) 2010 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 "soundlib.h"
2024-05-06 06:12:02 +03:00
# if XASH_SDL
# include <SDL_audio.h>
# endif // XASH_SDL
2018-04-13 19:23:45 +03:00
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
XASH3D LOAD SOUND FORMATS
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
// stub
static const loadwavfmt_t load_null [ ] =
{
{ NULL , NULL , NULL }
} ;
static const loadwavfmt_t load_game [ ] =
{
{ DEFAULT_SOUNDPATH " %s%s.%s " , " wav " , Sound_LoadWAV } ,
{ " %s%s.%s " , " wav " , Sound_LoadWAV } ,
{ DEFAULT_SOUNDPATH " %s%s.%s " , " mp3 " , Sound_LoadMPG } ,
{ " %s%s.%s " , " mp3 " , Sound_LoadMPG } ,
2024-11-30 16:06:16 +04:00
{ DEFAULT_SOUNDPATH " %s%s.%s " , " ogg " , Sound_LoadOggVorbis } ,
{ " %s%s.%s " , " ogg " , Sound_LoadOggVorbis } ,
2024-11-30 21:11:37 +04:00
{ DEFAULT_SOUNDPATH " %s%s.%s " , " opus " , Sound_LoadOggOpus } ,
{ " %s%s.%s " , " opus " , Sound_LoadOggOpus } ,
2018-04-13 19:23:45 +03:00
{ NULL , NULL , NULL }
} ;
/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
XASH3D PROCESS STREAM FORMATS
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
// stub
static const streamfmt_t stream_null [ ] =
{
{ NULL , NULL , NULL , NULL , NULL , NULL , NULL }
} ;
static const streamfmt_t stream_game [ ] =
{
{ " %s%s.%s " , " mp3 " , Stream_OpenMPG , Stream_ReadMPG , Stream_SetPosMPG , Stream_GetPosMPG , Stream_FreeMPG } ,
{ " %s%s.%s " , " wav " , Stream_OpenWAV , Stream_ReadWAV , Stream_SetPosWAV , Stream_GetPosWAV , Stream_FreeWAV } ,
2024-11-30 22:52:34 +04:00
{ " %s%s.%s " , " ogg " , Stream_OpenOggVorbis , Stream_ReadOggVorbis , Stream_SetPosOggVorbis , Stream_GetPosOggVorbis , Stream_FreeOggVorbis } ,
2024-12-01 18:46:45 +04:00
{ " %s%s.%s " , " opus " , Stream_OpenOggOpus , Stream_ReadOggOpus , Stream_SetPosOggOpus , Stream_GetPosOggOpus , Stream_FreeOggOpus } ,
2018-04-13 19:23:45 +03:00
{ NULL , NULL , NULL , NULL , NULL , NULL , NULL }
} ;
void Sound_Init ( void )
{
// init pools
host . soundpool = Mem_AllocPool ( " SoundLib Pool " ) ;
// install image formats (can be re-install later by Sound_Setup)
switch ( host . type )
{
case HOST_NORMAL :
sound . loadformats = load_game ;
2021-01-03 01:28:45 +00:00
sound . streamformat = stream_game ;
2018-04-13 19:23:45 +03:00
break ;
default : // all other instances not using soundlib or will be reinstalling later
sound . loadformats = load_null ;
sound . streamformat = stream_null ;
break ;
}
sound . tempbuffer = NULL ;
}
void Sound_Shutdown ( void )
{
Mem_Check ( ) ; // check for leaks
Mem_FreePool ( & host . soundpool ) ;
}
2024-01-27 19:48:17 +03:00
static byte * Sound_Copy ( size_t size )
2018-04-13 19:23:45 +03:00
{
byte * out ;
2024-07-16 05:11:06 +03:00
out = Mem_Realloc ( host . soundpool , sound . tempbuffer , size ) ;
sound . tempbuffer = NULL ;
2018-04-13 19:23:45 +03:00
2021-01-03 01:28:45 +00:00
return out ;
2018-04-13 19:23:45 +03:00
}
2018-04-19 21:51:17 +00:00
uint GAME_EXPORT Sound_GetApproxWavePlayLen ( const char * filepath )
{
2023-08-18 11:46:48 +03:00
string name ;
2021-03-07 02:49:40 +03:00
file_t * f ;
wavehdr_t wav ;
size_t filesize ;
uint msecs ;
2018-04-19 21:51:17 +00:00
2023-08-18 11:50:58 +03:00
Q_strncpy ( name , filepath , sizeof ( name ) ) ;
2023-08-18 11:46:48 +03:00
COM_FixSlashes ( name ) ;
f = FS_Open ( name , " rb " , false ) ;
2021-03-07 02:49:40 +03:00
if ( ! f )
return 0 ;
2018-04-19 21:51:17 +00:00
if ( FS_Read ( f , & wav , sizeof ( wav ) ) ! = sizeof ( wav ) )
{
FS_Close ( f ) ;
return 0 ;
}
filesize = FS_FileLength ( f ) ;
2021-03-07 02:49:40 +03:00
filesize - = 128 ; // magic number from GoldSrc, seems to be header size
2018-04-19 21:51:17 +00:00
FS_Close ( f ) ;
// is real wav file ?
if ( wav . riff_id ! = RIFFHEADER | | wav . wave_id ! = WAVEHEADER | | wav . fmt_id ! = FORMHEADER )
return 0 ;
2021-03-07 02:49:40 +03:00
if ( wav . nAvgBytesPerSec > = 1000 )
msecs = ( uint ) ( ( float ) filesize / ( ( float ) wav . nAvgBytesPerSec / 1000.0f ) ) ;
else msecs = ( uint ) ( ( ( float ) filesize / ( float ) wav . nAvgBytesPerSec ) * 1000.0f ) ;
2018-04-19 21:51:17 +00:00
2021-03-07 02:49:40 +03:00
return msecs ;
2018-04-19 21:51:17 +00:00
}
2024-07-15 12:44:50 +03:00
# define SOUND_FORMATCONVERT_BOILERPLATE( resamplemacro ) \
if ( inwidth = = 1 ) \
{ \
const int8_t * data = ( const int8_t * ) sc - > buffer ; \
if ( outwidth = = 1 ) \
{ \
int8_t * outdata = ( int8_t * ) sound . tempbuffer ; \
resamplemacro ( 1 ) \
} \
else if ( outwidth = = 2 ) \
{ \
int16_t * outdata = ( int16_t * ) sound . tempbuffer ; \
resamplemacro ( 256 ) \
} \
else \
return false ; \
} \
else if ( inwidth = = 2 ) \
{ \
const int16_t * data = ( const int16_t * ) sc - > buffer ; \
if ( outwidth = = 1 ) \
{ \
int8_t * outdata = ( int8_t * ) sound . tempbuffer ; \
resamplemacro ( 1 / 256.0 ) \
} \
else if ( outwidth = = 2 ) \
{ \
int16_t * outdata = ( int16_t * ) sound . tempbuffer ; \
resamplemacro ( 1 ) \
} \
else \
return false ; \
} \
else \
return false ; \
# define SOUND_CONVERTNORESAMPLE_BOILERPLATE( multiplier ) \
if ( inchannels = = 1 ) \
{ \
if ( outchannels = = 1 ) \
{ \
for ( i = 0 ; i < outcount * outchannels ; i + + ) \
outdata [ i ] = data [ i ] * ( multiplier ) ; \
} \
else if ( outchannels = = 2 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
{ \
outdata [ i * 2 + 0 ] = data [ i ] * ( multiplier ) ; \
outdata [ i * 2 + 1 ] = data [ i ] * ( multiplier ) ; \
} \
} \
else \
return false ; \
} \
else if ( inchannels = = 2 ) \
{ \
if ( outchannels = = 1 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
outdata [ i ] = ( data [ i * 2 + 0 ] + data [ i * 2 + 1 ] ) * ( multiplier ) / 2 ; \
} \
else if ( outchannels = = 2 ) \
{ \
for ( i = 0 ; i < outcount * outchannels ; i + + ) \
outdata [ i ] = data [ i ] * ( multiplier ) ; \
} \
else \
return false ; \
} \
else \
return false ; \
# define SOUND_CONVERTDOWNSAMPLE_BOILERPLATE( multiplier ) \
if ( inchannels = = 1 ) \
{ \
if ( outchannels = = 1 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
{ \
double j = stepscale * i ; \
outdata [ i ] = data [ ( int ) j ] * ( multiplier ) ; \
} \
} \
else if ( outchannels = = 2 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
{ \
double j = stepscale * i ; \
outdata [ i * 2 + 0 ] = data [ ( int ) j ] * ( multiplier ) ; \
outdata [ i * 2 + 1 ] = data [ ( int ) j ] * ( multiplier ) ; \
} \
} \
else \
return false ; \
} \
else if ( inchannels = = 2 ) \
{ \
if ( outchannels = = 1 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
{ \
double j = stepscale * i ; \
outdata [ i ] = ( data [ ( ( int ) j ) * 2 + 0 ] + data [ ( ( int ) j ) * 2 + 1 ] ) * ( multiplier ) / 2 ; \
} \
} \
else if ( outchannels = = 2 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
{ \
double j = stepscale * i ; \
outdata [ i * 2 + 0 ] = data [ ( ( int ) j ) * 2 + 0 ] * ( multiplier ) ; \
outdata [ i * 2 + 1 ] = data [ ( ( int ) j ) * 2 + 1 ] * ( multiplier ) ; \
} \
} \
else \
return false ; \
} \
else \
return false ; \
# define SOUND_CONVERTUPSAMPLE_BOILERPLATE( multiplier ) \
if ( inchannels = = 1 ) \
{ \
if ( outchannels = = 1 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
{ \
double j = stepscale * i ; \
outdata [ i ] = data [ ( int ) j ] * ( multiplier ) ; \
if ( j ! = ( int ) j & & j < incount ) \
{ \
double frac = ( j - ( int ) j ) * ( multiplier ) ; \
outdata [ i ] + = ( data [ ( int ) j + 1 ] - data [ ( int ) j ] ) * frac ; \
} \
} \
} \
else if ( outchannels = = 2 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
{ \
double j = stepscale * i ; \
outdata [ i * 2 + 0 ] = data [ ( int ) j ] * ( multiplier ) ; \
if ( j ! = ( int ) j & & j < incount ) \
{ \
double frac = ( j - ( int ) j ) * ( multiplier ) ; \
outdata [ i * 2 + 0 ] + = ( data [ ( int ) j + 1 ] - data [ ( int ) j ] ) * frac ; \
} \
outdata [ i * 2 + 1 ] = outdata [ i * 2 + 0 ] ; \
} \
} \
else \
return false ; \
} \
else if ( inchannels = = 2 ) \
{ \
if ( outchannels = = 1 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
{ \
double j = stepscale * i ; \
outdata [ i ] = ( data [ ( ( int ) j ) * 2 + 0 ] + data [ ( ( int ) j ) * 2 + 1 ] ) * ( multiplier ) / 2 ; \
if ( j ! = ( int ) j & & j < incount ) \
{ \
double frac = ( j - ( int ) j ) * ( multiplier ) / 2 ; \
outdata [ i ] + = ( data [ ( ( int ) j + 1 ) * 2 + 0 ] - data [ ( ( int ) j ) * 2 + 0 ] ) * frac ; \
outdata [ i ] + = ( data [ ( ( int ) j + 1 ) * 2 + 1 ] - data [ ( ( int ) j ) * 2 + 1 ] ) * frac ; \
} \
} \
} \
else if ( outchannels = = 2 ) \
{ \
for ( i = 0 ; i < outcount ; i + + ) \
{ \
double j = stepscale * i ; \
outdata [ i * 2 + 0 ] = data [ ( ( int ) j ) * 2 + 0 ] * ( multiplier ) ; \
outdata [ i * 2 + 1 ] = data [ ( ( int ) j ) * 2 + 1 ] * ( multiplier ) ; \
if ( j ! = ( int ) j & & j < incount ) \
{ \
double frac = ( j - ( int ) j ) * ( multiplier ) ; \
outdata [ i * 2 + 0 ] + = ( data [ ( ( int ) j + 1 ) * 2 + 0 ] - data [ ( ( int ) j ) * 2 + 0 ] ) * frac ; \
outdata [ i * 2 + 1 ] + = ( data [ ( ( int ) j + 1 ) * 2 + 1 ] - data [ ( ( int ) j ) * 2 + 1 ] ) * frac ; \
} \
} \
} \
else \
return false ; \
} \
else \
return false ; \
static qboolean Sound_ConvertNoResample ( wavdata_t * sc , int inwidth , int inchannels , int outwidth , int outchannels , int outcount )
2024-05-06 06:12:02 +03:00
{
size_t i ;
2018-04-13 19:23:45 +03:00
2024-07-15 12:44:50 +03:00
// I could write a generic case here but I also want to make it easier for compiler
// to unroll these for-loops
2024-05-06 06:12:02 +03:00
2024-07-15 12:44:50 +03:00
SOUND_FORMATCONVERT_BOILERPLATE ( SOUND_CONVERTNORESAMPLE_BOILERPLATE )
2024-05-06 06:12:02 +03:00
2024-07-15 12:44:50 +03:00
return true ;
2024-05-06 06:12:02 +03:00
}
2024-07-15 12:44:50 +03:00
static qboolean Sound_ConvertDownsample ( wavdata_t * sc , int inwidth , int inchannels , int outwidth , int outchannels , int outcount , double stepscale )
2018-04-13 19:23:45 +03:00
{
2024-05-06 06:12:02 +03:00
size_t i ;
2024-07-15 12:44:50 +03:00
SOUND_FORMATCONVERT_BOILERPLATE ( SOUND_CONVERTDOWNSAMPLE_BOILERPLATE )
2024-05-06 06:12:02 +03:00
2024-07-15 12:44:50 +03:00
return true ;
2024-05-06 06:12:02 +03:00
}
2024-07-15 12:44:50 +03:00
static qboolean Sound_ConvertUpsample ( wavdata_t * sc , int inwidth , int inchannels , int incount , int outwidth , int outchannels , int outcount , double stepscale )
2024-05-06 06:12:02 +03:00
{
size_t i ;
2024-05-06 15:45:49 +03:00
incount - - ; // to not go past last sample while interpolating
2024-07-15 12:44:50 +03:00
SOUND_FORMATCONVERT_BOILERPLATE ( SOUND_CONVERTUPSAMPLE_BOILERPLATE )
2024-05-06 06:12:02 +03:00
2024-07-15 12:44:50 +03:00
return true ;
2024-05-06 06:12:02 +03:00
}
2024-07-15 12:44:50 +03:00
# undef SOUND_FORMATCONVERT_BOILERPLATE
# undef SOUND_CONVERTNORESAMPLE_BOILERPLATE
# undef SOUND_CONVERTDOWNSAMPLE_BOILERPLATE
# undef SOUND_CONVERTUPSAMPLE_BOILERPLATE
2024-05-06 06:12:02 +03:00
/*
= = = = = = = = = = = = = = = =
Sound_ResampleInternal
= = = = = = = = = = = = = = = =
*/
2024-07-15 12:44:50 +03:00
static qboolean Sound_ResampleInternal ( wavdata_t * sc , int outrate , int outwidth , int outchannels )
2024-05-06 06:12:02 +03:00
{
2024-07-15 12:44:50 +03:00
const int inrate = sc - > rate ;
const int inwidth = sc - > width ;
const int inchannels = sc - > channels ;
const int incount = sc - > samples ;
2024-05-06 06:12:02 +03:00
qboolean handled = false ;
double stepscale ;
double t1 , t2 ;
2024-07-15 12:44:50 +03:00
int outcount ;
2024-05-06 06:12:02 +03:00
2024-07-15 12:44:50 +03:00
if ( inrate = = outrate & & inwidth = = outwidth & & inchannels = = outchannels )
2024-05-06 06:12:02 +03:00
return false ;
t1 = Sys_DoubleTime ( ) ;
stepscale = ( double ) inrate / outrate ; // this is usually 0.5, 1, or 2
outcount = sc - > samples / stepscale ;
2024-07-15 12:44:50 +03:00
sc - > size = outcount * outwidth * outchannels ;
sc - > channels = outchannels ;
2024-05-06 06:12:02 +03:00
sc - > samples = outcount ;
if ( FBitSet ( sc - > flags , SOUND_LOOPED ) )
sc - > loopStart = sc - > loopStart / stepscale ;
2024-07-15 12:44:50 +03:00
#if 0 && XASH_SDL // slow but somewhat accurate (wasn't updated to channel manipulation!!!)
2024-05-06 06:12:02 +03:00
{
const SDL_AudioFormat infmt = inwidth = = 1 ? AUDIO_S8 : AUDIO_S16 ;
const SDL_AudioFormat outfmt = outwidth = = 1 ? AUDIO_S8 : AUDIO_S16 ;
SDL_AudioCVT cvt ;
// SDL_AudioCVT does conversion in place, original buffer is used for it
2024-07-15 12:44:50 +03:00
if ( SDL_BuildAudioCVT ( & cvt , infmt , inchannels , inrate , outfmt , outchannels , outrate ) > 0 & & cvt . needed )
2024-05-06 06:12:02 +03:00
{
sc - > buffer = ( byte * ) Mem_Realloc ( host . soundpool , sc - > buffer , oldsize * cvt . len_mult ) ;
cvt . len = oldsize ;
cvt . buf = sc - > buffer ;
if ( ! SDL_ConvertAudio ( & cvt ) )
{
t2 = Sys_DoubleTime ( ) ;
Con_Reportf ( " Sound_Resample: from [%d bit %d Hz] to [%d bit %d Hz] (took %.3fs through SDL) \n " , inwidth * 8 , inrate , outwidth * 8 , outrate , t2 - t1 ) ;
sc - > rate = outrate ;
sc - > width = outwidth ;
return false ; // HACKHACK: return false so Sound_Process won't reallocate buffer
}
}
}
# endif
sound . tempbuffer = ( byte * ) Mem_Realloc ( host . soundpool , sound . tempbuffer , sc - > size ) ;
if ( inrate = = outrate ) // no resampling, just copy data
2024-07-15 12:44:50 +03:00
handled = Sound_ConvertNoResample ( sc , inwidth , inchannels , outwidth , outchannels , outcount ) ;
2024-05-06 06:12:02 +03:00
else if ( inrate > outrate ) // fast case, usually downsample but is also ok for upsampling
2024-07-15 12:44:50 +03:00
handled = Sound_ConvertDownsample ( sc , inwidth , inchannels , outwidth , outchannels , outcount , stepscale ) ;
2024-05-06 06:12:02 +03:00
else // upsample case, w/ interpolation
2024-07-15 12:44:50 +03:00
handled = Sound_ConvertUpsample ( sc , inwidth , inchannels , incount , outwidth , outchannels , outcount , stepscale ) ;
2024-05-06 06:12:02 +03:00
t2 = Sys_DoubleTime ( ) ;
2022-08-22 10:14:01 +03:00
if ( handled )
2024-05-06 06:12:02 +03:00
{
if ( t2 - t1 > 0.01f ) // critical, report to mod developer
2024-07-15 12:44:50 +03:00
Con_Printf ( S_WARN " %s: from [%d bit %d Hz %dch] to [%d bit %d Hz %dch] (took %.3fs) \n " , __func__ , inwidth * 8 , inrate , inchannels , outwidth * 8 , outrate , outchannels , t2 - t1 ) ;
2024-05-06 06:12:02 +03:00
else
2024-07-15 12:44:50 +03:00
Con_Reportf ( " %s: from [%d bit %d Hz %dch] to [%d bit %d Hz %dch] (took %.3fs) \n " , __func__ , inwidth * 8 , inrate , inchannels , outwidth * 8 , outrate , outchannels , t2 - t1 ) ;
2024-05-06 06:12:02 +03:00
}
2022-08-22 10:14:01 +03:00
else
2024-07-15 12:44:50 +03:00
Con_Printf ( S_ERROR " %s: unsupported from [%d bit %d Hz %dch] to [%d bit %d Hz %dch] \n " , __func__ , inwidth * 8 , inrate , inchannels , outwidth * 8 , outrate , outchannels ) ;
2022-08-22 10:14:01 +03:00
2018-04-13 19:23:45 +03:00
sc - > rate = outrate ;
sc - > width = outwidth ;
2022-08-22 10:14:01 +03:00
return handled ;
2018-04-13 19:23:45 +03:00
}
2024-07-15 12:44:50 +03:00
qboolean Sound_Process ( wavdata_t * * wav , int rate , int width , int channels , uint flags )
2018-04-13 19:23:45 +03:00
{
wavdata_t * snd = * wav ;
qboolean result = true ;
2021-01-03 01:28:45 +00:00
2018-04-13 19:23:45 +03:00
// check for buffers
2024-07-15 12:44:50 +03:00
if ( unlikely ( ! snd | | ! snd - > buffer ) )
2018-04-13 19:23:45 +03:00
return false ;
2024-07-15 12:44:50 +03:00
if ( likely ( FBitSet ( flags , SOUND_RESAMPLE ) & & ( width > 0 | | rate > 0 | | channels > 0 ) ) )
2018-04-13 19:23:45 +03:00
{
2024-07-15 12:44:50 +03:00
result = Sound_ResampleInternal ( snd , rate , width , channels ) ;
2024-05-06 06:12:02 +03:00
if ( result )
2018-04-13 19:23:45 +03:00
{
Mem_Free ( snd - > buffer ) ; // free original image buffer
2024-07-15 12:44:50 +03:00
snd - > buffer = Sound_Copy ( snd - > size ) ; // unzone buffer
2018-04-13 19:23:45 +03:00
}
}
* wav = snd ;
2024-05-06 06:12:02 +03:00
return result ;
2018-04-19 21:51:17 +00:00
}
2023-03-18 23:31:58 +04:00
qboolean Sound_SupportedFileFormat ( const char * fileext )
{
const loadwavfmt_t * format ;
if ( COM_CheckStringEmpty ( fileext ) )
{
for ( format = sound . loadformats ; format & & format - > formatstring ; format + + )
{
if ( ! Q_stricmp ( format - > ext , fileext ) )
return true ;
}
}
return false ;
}