/* 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" /* ============================================================================= 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 }, { 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 }, { 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; sound.streamformat = stream_game; 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 ); } byte *Sound_Copy( size_t size ) { byte *out; out = Mem_Malloc( host.soundpool, size ); memcpy( out, sound.tempbuffer, size ); return out; } uint GAME_EXPORT Sound_GetApproxWavePlayLen( const char *filepath ) { file_t *f; wavehdr_t wav; size_t filesize; float seconds; uint samples; f = FS_Open( filepath, "rb", false ); if( !f ) return 0; if( FS_Read( f, &wav, sizeof( wav )) != sizeof( wav )) { FS_Close( f ); return 0; } filesize = FS_FileLength( f ); filesize -= ( sizeof( wavehdr_t ) + sizeof( chunkhdr_t )); FS_Close( f ); // is real wav file ? if( wav.riff_id != RIFFHEADER || wav.wave_id != WAVEHEADER || wav.fmt_id != FORMHEADER ) return 0; if( wav.wFormatTag != 1 ) return 0; if( wav.nChannels != 1 && wav.nChannels != 2 ) return 0; if( wav.nBitsPerSample != 8 && wav.nBitsPerSample != 16 ) return 0; // calc samplecount seconds = (float)filesize / wav.nAvgBytesPerSec / wav.nChannels; samples = (uint)(( wav.nSamplesPerSec * wav.nChannels ) * seconds ); // g-cont. this function returns samplecount or time in milliseconds ??? return (uint)(seconds * 1000); } /* ================ Sound_ConvertToSigned Convert unsigned data to signed ================ */ void Sound_ConvertToSigned( const byte *data, int channels, int samples ) { int i; if( channels == 2 ) { for( i = 0; i < samples; i++ ) { ((signed char *)sound.tempbuffer)[i*2+0] = (int)((byte)(data[i*2+0]) - 128); ((signed char *)sound.tempbuffer)[i*2+1] = (int)((byte)(data[i*2+1]) - 128); } } else { for( i = 0; i < samples; i++ ) ((signed char *)sound.tempbuffer)[i] = (int)((unsigned char)(data[i]) - 128); } } /* ================ Sound_ResampleInternal We need convert sound to signed even if nothing to resample ================ */ qboolean Sound_ResampleInternal( wavdata_t *sc, int inrate, int inwidth, int outrate, int outwidth ) { float stepscale; int outcount, srcsample; int i, sample, sample2, samplefrac, fracstep; byte *data; data = sc->buffer; stepscale = (float)inrate / outrate; // this is usually 0.5, 1, or 2 outcount = sc->samples / stepscale; sc->size = outcount * outwidth * sc->channels; sound.tempbuffer = (byte *)Mem_Realloc( host.soundpool, sound.tempbuffer, sc->size ); sc->samples = outcount; if( sc->loopStart != -1 ) sc->loopStart = sc->loopStart / stepscale; // resample / decimate to the current source rate if( stepscale == 1.0f && inwidth == 1 && outwidth == 1 ) { Sound_ConvertToSigned( data, sc->channels, outcount ); } else { // general case samplefrac = 0; fracstep = stepscale * 256; if( sc->channels == 2 ) { for( i = 0; i < outcount; i++ ) { srcsample = samplefrac >> 8; samplefrac += fracstep; if( inwidth == 2 ) { sample = ((short *)data)[srcsample*2+0]; sample2 = ((short *)data)[srcsample*2+1]; } else { sample = (int)((char)(data[srcsample*2+0])) << 8; sample2 = (int)((char)(data[srcsample*2+1])) << 8; } if( outwidth == 2 ) { ((short *)sound.tempbuffer)[i*2+0] = sample; ((short *)sound.tempbuffer)[i*2+1] = sample2; } else { ((signed char *)sound.tempbuffer)[i*2+0] = sample >> 8; ((signed char *)sound.tempbuffer)[i*2+1] = sample2 >> 8; } } } else { for( i = 0; i < outcount; i++ ) { srcsample = samplefrac >> 8; samplefrac += fracstep; if( inwidth == 2 ) sample = ((short *)data)[srcsample]; else sample = (int)( (char)(data[srcsample])) << 8; if( outwidth == 2 ) ((short *)sound.tempbuffer)[i] = sample; else ((signed char *)sound.tempbuffer)[i] = sample >> 8; } } MsgDev( D_NOTE, "Sound_Resample: from[%d bit %d kHz] to [%d bit %d kHz]\n", inwidth * 8, inrate, outwidth * 8, outrate ); } sc->rate = outrate; sc->width = outwidth; return true; } qboolean Sound_Process( wavdata_t **wav, int rate, int width, uint flags ) { wavdata_t *snd = *wav; qboolean result = true; // check for buffers if( !snd || !snd->buffer ) { MsgDev( D_WARN, "Sound_Process: NULL sound\n" ); return false; } if(( flags & SOUND_RESAMPLE ) && ( width > 0 || rate > 0 )) { if( Sound_ResampleInternal( snd, snd->rate, snd->width, rate, width )) { Mem_Free( snd->buffer ); // free original image buffer snd->buffer = Sound_Copy( snd->size ); // unzone buffer (don't touch image.tempbuffer) } else { // not resampled result = false; } } *wav = snd; return false; }