This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/snd_al/s_load.c

706 lines
15 KiB
C

//=======================================================================
// Copyright XashXT Group 2007 ©
// s_load.c - sound managment
//=======================================================================
#include "sound.h"
#include "s_stream.h"
#define MAX_SFX 4096
static sfx_t s_knownSfx[MAX_SFX];
static int s_numSfx = 0;
int s_registration_sequence = 0;
bool s_registering = false;
typedef struct loadformat_s
{
char *formatstring;
char *ext;
bool (*loadfunc)( const char *name, byte **wav, wavinfo_t *info );
} loadformat_t;
/*
=================
S_SoundList_f
=================
*/
void S_SoundList_f( void )
{
sfx_t *sfx;
int i, samples = 0;
int totalSfx = 0;
Msg("\n");
Msg(" -samples -hz-- -format- -name--------\n");
for( i = 0; i < s_numSfx; i++ )
{
sfx = &s_knownSfx[i];
if( sfx->loaded )
{
samples += sfx->samples;
Msg( "%8i ", sfx->samples );
Msg( "%5i ", sfx->rate );
switch( sfx->format )
{
case AL_FORMAT_STEREO16: Msg( "STEREO16 " ); break;
case AL_FORMAT_STEREO8: Msg( "STEREO8 " ); break;
case AL_FORMAT_MONO16: Msg( "MONO16 " ); break;
case AL_FORMAT_MONO8: Msg( "MONO8 " ); break;
default: Msg( "???????? " ); break;
}
if( sfx->name[0] == '#' ) Msg( "%s", &sfx->name[1] );
else Msg( "sound/%s", sfx->name );
Msg( "\n" );
totalSfx++;
}
}
Msg("-------------------------------------------\n");
Msg("%i total samples\n", samples );
Msg("%i total sounds\n", totalSfx );
Msg("\n");
}
/*
=======================================================================
WAV LOADING
=======================================================================
*/
static byte *iff_data;
static byte *iff_dataPtr;
static byte *iff_end;
static byte *iff_lastChunk;
static int iff_chunkLen;
/*
=================
S_GetLittleShort
=================
*/
static short S_GetLittleShort( void )
{
short val = 0;
val += (*(iff_dataPtr+0) << 0);
val += (*(iff_dataPtr+1) << 8);
iff_dataPtr += 2;
return val;
}
/*
=================
S_GetLittleLong
=================
*/
static int S_GetLittleLong( void )
{
int val = 0;
val += (*(iff_dataPtr+0) << 0);
val += (*(iff_dataPtr+1) << 8);
val += (*(iff_dataPtr+2) << 16);
val += (*(iff_dataPtr+3) << 24);
iff_dataPtr += 4;
return val;
}
/*
=================
S_FindNextChunk
=================
*/
static void S_FindNextChunk( const char *name )
{
while( 1 )
{
iff_dataPtr = iff_lastChunk;
if( iff_dataPtr >= iff_end )
{
// didn't find the chunk
iff_dataPtr = NULL;
return;
}
iff_dataPtr += 4;
iff_chunkLen = S_GetLittleLong();
if (iff_chunkLen < 0)
{
iff_dataPtr = NULL;
return;
}
iff_dataPtr -= 8;
iff_lastChunk = iff_dataPtr + 8 + ((iff_chunkLen + 1) & ~1);
if(!com.strncmp( iff_dataPtr, name, 4 ))
return;
}
}
/*
=================
S_FindChunk
=================
*/
static void S_FindChunk( const char *name )
{
iff_lastChunk = iff_data;
S_FindNextChunk( name );
}
/*
=================
S_LoadWAV
=================
*/
static bool S_LoadWAV( const char *name, byte **wav, wavinfo_t *info )
{
byte *buffer, *out;
int length, samples;
buffer = FS_LoadFile( name, &length );
if( !buffer ) return false;
iff_data = buffer;
iff_end = buffer + length;
// dind "RIFF" chunk
S_FindChunk( "RIFF" );
if( !( iff_dataPtr && !com.strncmp( iff_dataPtr + 8, "WAVE", 4 )))
{
MsgDev( D_WARN, "S_LoadWAV: missing 'RIFF/WAVE' chunks (%s)\n", name );
Mem_Free( buffer );
return false;
}
// get "fmt " chunk
iff_data = iff_dataPtr + 12;
S_FindChunk( "fmt " );
if( !iff_dataPtr )
{
MsgDev( D_WARN, "S_LoadWAV: missing 'fmt ' chunk (%s)\n", name );
Mem_Free( buffer );
return false;
}
iff_dataPtr += 8;
if( S_GetLittleShort() != 1 )
{
MsgDev( D_WARN, "S_LoadWAV: microsoft PCM format only (%s)\n", name );
Mem_Free( buffer );
return false;
}
info->channels = S_GetLittleShort();
if( info->channels != 1 )
{
MsgDev( D_WARN, "S_LoadWAV: only mono WAV files supported (%s)\n", name );
Mem_Free( buffer );
return false;
}
info->rate = S_GetLittleLong();
iff_dataPtr += 4+2;
info->width = S_GetLittleShort() / 8;
if( info->width != 1 && info->width != 2 )
{
MsgDev( D_WARN, "S_LoadWAV: only 8 and 16 bit WAV files supported (%s)\n", name );
Mem_Free( buffer );
return false;
}
// get cue chunk
S_FindChunk( "cue " );
if( iff_dataPtr )
{
iff_dataPtr += 32;
info->loopstart = S_GetLittleLong();
S_FindNextChunk( "LIST" ); // if the next chunk is a LIST chunk, look for a cue length marker
if( iff_dataPtr )
{
if( !com.strncmp((const char *)iff_dataPtr + 28, "mark", 4 ))
{
// this is not a proper parse, but it works with CoolEdit...
iff_dataPtr += 24;
info->samples = info->loopstart + S_GetLittleLong(); // samples in loop
}
}
}
else
{
info->loopstart = -1;
info->samples = 0;
}
// find data chunk
S_FindChunk( "data" );
if( !iff_dataPtr )
{
MsgDev( D_WARN, "S_LoadWAV: missing 'data' chunk (%s)\n", name );
Mem_Free( buffer );
return false;
}
iff_dataPtr += 4;
samples = S_GetLittleLong() / info->width;
if( info->samples )
{
if( samples < info->samples )
{
MsgDev( D_ERROR, "S_LoadWAV: %s has a bad loop length\n", name );
Mem_Free( buffer );
return false;
}
}
else info->samples = samples;
if( info->samples <= 0 )
{
MsgDev( D_WARN, "S_LoadWAV: file with %i samples (%s)\n", info->samples, name );
Mem_Free( buffer );
return false;
}
// Load the data
*wav = out = Z_Malloc( info->samples * info->width );
Mem_Copy( out, buffer + (iff_dataPtr - buffer), info->samples * info->width );
Mem_Free( buffer );
return true;
}
/*
=======================================================================
OGG LOADING
=======================================================================
*/
typedef struct
{
byte *buffer;
ogg_int64_t ind;
ogg_int64_t buffsize;
} ov_decode_t;
static size_t ovc_read( void *ptr, size_t size, size_t nb, void *datasource )
{
ov_decode_t *sound = (ov_decode_t *)datasource;
size_t remain, length;
remain = sound->buffsize - sound->ind;
length = size * nb;
if( remain < length ) length = remain - remain % size;
Mem_Copy( ptr, sound->buffer + sound->ind, length );
sound->ind += length;
return length / size;
}
static int ovc_seek ( void *datasource, ogg_int64_t offset, int whence )
{
ov_decode_t *sound = (ov_decode_t*)datasource;
switch( whence )
{
case SEEK_SET:
break;
case SEEK_CUR:
offset += sound->ind;
break;
case SEEK_END:
offset += sound->buffsize;
break;
default:
return -1;
}
if( offset < 0 || offset > sound->buffsize )
return -1;
sound->ind = offset;
return 0;
}
static int ovc_close( void *datasource )
{
return 0;
}
static long ovc_tell (void *datasource)
{
return ((ov_decode_t*)datasource)->ind;
}
/*
=================
S_LoadOGG
=================
*/
static bool S_LoadOGG( const char *name, byte **wav, wavinfo_t *info )
{
vorbisfile_t vf;
vorbis_info_t *vi;
vorbis_comment_t *vc;
fs_offset_t filesize;
ov_decode_t ov_decode;
ogg_int64_t length, done = 0;
ov_callbacks_t ov_callbacks = { ovc_read, ovc_seek, ovc_close, ovc_tell };
byte *data, *buffer;
const char *comm;
int dummy;
long ret;
// load the file
data = FS_LoadFile( name, &filesize );
if( !data ) return false;
// Open it with the VorbisFile API
ov_decode.buffer = data;
ov_decode.ind = 0;
ov_decode.buffsize = filesize;
if( ov_open_callbacks( &ov_decode, &vf, NULL, 0, ov_callbacks ) < 0 )
{
MsgDev( D_ERROR, "S_LoadOGG: couldn't open ogg stream %s\n", name );
Mem_Free( data );
return false;
}
// get the stream information
vi = ov_info( &vf, -1 );
if( vi->channels != 1 )
{
MsgDev( D_ERROR, "S_LoadOGG: only mono OGG files supported (%s)\n", name );
ov_clear( &vf );
Mem_Free( data );
return false;
}
info->channels = vi->channels;
info->rate = vi->rate;
info->width = 2; // always 16-bit PCM
info->loopstart = -1;
length = ov_pcm_total( &vf, -1 ) * vi->channels * 2; // 16 bits => "* 2"
if( !length )
{
// bad ogg file
MsgDev( D_ERROR, "S_LoadOGG: (%s) is probably corrupted\n", name );
ov_clear( &vf );
Mem_Free( data );
return false;
}
buffer = (byte *)Z_Malloc( length );
// decompress ogg into pcm wav format
while((ret = ov_read( &vf, &buffer[done], (int)(length - done), big_endian, 2, 1, &dummy )) > 0)
done += ret;
info->samples = done / ( vi->channels * 2 );
vc = ov_comment( &vf, -1 );
if( vc )
{
comm = vorbis_comment_query( vc, "LOOP_START", 0 );
if( comm )
{
//FXIME: implement
Msg("ogg 'cue' %d\n", com.atoi(comm) );
//info->loopstart = bound( 0, com.atoi(comm), info->samples );
}
}
// close file
ov_clear( &vf );
Mem_Free( data );
*wav = buffer; // load the data
return true;
}
/*
=================
S_UploadSound
=================
*/
static void S_UploadSound( byte *data, int width, int channels, sfx_t *sfx )
{
int size;
// calculate buffer size
size = sfx->samples * width * channels;
// Set buffer format
if( width == 2 )
{
if( channels == 2 ) sfx->format = AL_FORMAT_STEREO16;
else sfx->format = AL_FORMAT_MONO16;
}
else
{
if( channels == 2 ) sfx->format = AL_FORMAT_STEREO8;
else sfx->format = AL_FORMAT_MONO8;
}
// upload the sound
palGenBuffers( 1, &sfx->bufferNum );
palBufferData( sfx->bufferNum, sfx->format, data, size, sfx->rate );
S_CheckForErrors();
}
/*
=================
S_CreateDefaultSound
=================
*/
static void S_CreateDefaultSound( byte **wav, wavinfo_t *info )
{
byte *out;
int i;
info->rate = 22050;
info->width = 2;
info->channels = 1;
info->samples = 11025;
*wav = out = Z_Malloc( info->samples * info->width );
if( s_check_errors->integer )
{
// create 1 kHz tone as default sound
for( i = 0; i < info->samples; i++ )
((short *)out)[i] = sin(i * 0.1f) * 20000;
}
else
{
// create silent sound
for( i = 0; i < info->samples; i++ )
((short *)out)[i] = i;
}
}
/*
=================
S_LoadSound
=================
*/
loadformat_t load_formats[] =
{
{ "sound/%s.%s", "ogg", S_LoadOGG },
{ "sound/%s.%s", "wav", S_LoadWAV },
{ "%s.%s", "ogg", S_LoadOGG },
{ "%s.%s", "wav", S_LoadWAV },
{ NULL, NULL }
};
bool S_LoadSound( sfx_t *sfx )
{
byte *data;
wavinfo_t info;
const char *ext;
string loadname, path;
loadformat_t *format;
bool anyformat;
if( !sfx ) return false;
if( sfx->name[0] == '*' ) return false;
if( sfx->loaded ) return true; // see if still in memory
// load it from disk
ext = FS_FileExtension( sfx->name );
anyformat = !com.stricmp( ext, "" ) ? true : false;
com.strncpy( loadname, sfx->name, sizeof( loadname ));
FS_StripExtension( loadname ); // remove extension if needed
Mem_Set( &info, 0, sizeof( info ));
// developer warning
if( !anyformat ) MsgDev( D_NOTE, "Note: %s will be loading only with ext .%s\n", loadname, ext );
// now try all the formats in the selected list
for( format = load_formats; format->formatstring; format++ )
{
if( anyformat || !com.stricmp( ext, format->ext ))
{
com.sprintf( path, format->formatstring, loadname, format->ext );
if( format->loadfunc( path, &data, &info ))
goto snd_loaded;
}
}
sfx->default_sound = true;
MsgDev( D_WARN, "FS_LoadSound: couldn't load %s\n", sfx->name );
S_CreateDefaultSound( &data, &info );
info.loopstart = -1;
snd_loaded:
// load it in
sfx->loopstart = info.loopstart;
sfx->samples = info.samples;
sfx->rate = info.rate;
S_UploadSound( data, info.width, info.channels, sfx );
sfx->loaded = true;
Mem_Free( data );
return true;
}
// =======================================================================
// Load a sound
// =======================================================================
/*
=================
S_FindSound
=================
*/
sfx_t *S_FindSound( const char *name )
{
sfx_t *sfx;
int i;
if( !name || !name[0] ) return NULL;
if( com.strlen(name) >= MAX_STRING )
{
MsgDev( D_ERROR, "S_FindSound: sound name too long: %s", name );
return NULL;
}
// see if already loaded
for( i = 0; i < s_numSfx; i++ )
{
sfx = &s_knownSfx[i];
if( !sfx->name[0] ) continue;
if( !com.strcmp( name, sfx->name ))
{
// prolonge registration
sfx->registration_sequence = s_registration_sequence;
return sfx;
}
}
// find a free sfx slot spot
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++)
{
if(!sfx->name[0]) break; // free spot
}
if( i == s_numSfx )
{
if( s_numSfx == MAX_SFX )
{
MsgDev( D_ERROR, "S_FindName: MAX_SFX limit exceeded\n" );
return NULL;
}
s_numSfx++;
}
sfx = &s_knownSfx[i];
Mem_Set( sfx, 0, sizeof(*sfx));
com.strncpy( sfx->name, name, MAX_STRING );
sfx->registration_sequence = s_registration_sequence;
return sfx;
}
/*
=====================
S_BeginRegistration
=====================
*/
void S_BeginRegistration( void )
{
s_registration_sequence++;
s_registering = true;
}
/*
=====================
S_EndRegistration
=====================
*/
void S_EndRegistration( void )
{
sfx_t *sfx;
int i;
// free any sounds not from this registration sequence
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
{
if( !sfx->name[0] ) continue;
if( sfx->registration_sequence != s_registration_sequence )
{
// don't need this sound
palDeleteBuffers( 1, &sfx->bufferNum );
Mem_Set( sfx, 0, sizeof( sfx_t )); // free spot
}
}
// load everything in
for( i = 0, sfx = s_knownSfx; i < s_numSfx; i++, sfx++ )
{
if( !sfx->name[0] ) continue;
S_LoadSound( sfx );
}
s_registering = false;
}
/*
=================
S_RegisterSound
=================
*/
sound_t S_RegisterSound( const char *name )
{
sfx_t *sfx;
if( !al_state.initialized )
return -1;
sfx = S_FindSound( name );
if( !sfx ) return -1;
sfx->registration_sequence = s_registration_sequence;
if( !s_registering ) S_LoadSound( sfx );
return sfx - s_knownSfx;
}
sfx_t *S_GetSfxByHandle( sound_t handle )
{
if( handle < 0 || handle >= s_numSfx )
{
MsgDev( D_ERROR, "S_GetSfxByHandle: handle %i out of range (%i)\n", handle, s_numSfx );
return NULL;
}
return &s_knownSfx[handle];
}
/*
=================
S_FreeSounds
=================
*/
void S_FreeSounds( void )
{
sfx_t *sfx;
int i;
// stop all sounds
S_StopAllSounds();
// free all sounds
for (i = 0; i < s_numSfx; i++)
{
sfx = &s_knownSfx[i];
if( !sfx->loaded ) continue;
palDeleteBuffers(1, &sfx->bufferNum);
}
Mem_Set( s_knownSfx, 0, sizeof(s_knownSfx));
s_numSfx = 0;
}