From 1e9decf19320eafd01f7221a91f8e41a1756cef5 Mon Sep 17 00:00:00 2001 From: smallmodel <15067410+smallmodel@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:37:56 +0100 Subject: [PATCH] Add support for IMA-ADPCM sounds (4-bit) Sounds such as sea waves (mohaa m3l1a) and subpen (mohaas flughafen) will be played accordingly. This uses OpenAL extensions such as AL_EXT_IMA4 and SOFT features such as AL_SOFT_block_alignment --- code/client/qal.c | 2 + code/client/qal.h | 2 +- code/client/snd_codec.h | 3 +- code/client/snd_codec_mp3.c | 2 +- code/client/snd_codec_wav.c | 20 ++++-- code/client/snd_local_new.h | 1 + code/client/snd_mem_new.cpp | 124 +++++++++++++++++---------------- code/client/snd_openal_new.cpp | 59 ++++++++++++++-- 8 files changed, 135 insertions(+), 78 deletions(-) diff --git a/code/client/qal.c b/code/client/qal.c index 4e33a292..78e0b80e 100644 --- a/code/client/qal.c +++ b/code/client/qal.c @@ -81,6 +81,7 @@ LPALGENBUFFERS qalGenBuffers; LPALDELETEBUFFERS qalDeleteBuffers; LPALISBUFFER qalIsBuffer; LPALBUFFERDATA qalBufferData; +LPALBUFFERI qalBufferi; LPALGETBUFFERF qalGetBufferf; LPALGETBUFFERFV qalGetBufferfv; LPALGETBUFFERI qalGetBufferi; @@ -201,6 +202,7 @@ qboolean QAL_Init(const char *libname) qalDeleteBuffers = GPA("alDeleteBuffers"); qalIsBuffer = GPA("alIsBuffer"); qalBufferData = GPA("alBufferData"); + qalBufferi = GPA("alBufferi"); qalGetBufferf = GPA("alGetBufferf"); qalGetBufferfv = GPA("alGetBufferfv"); qalGetBufferi = GPA("alGetBufferi"); diff --git a/code/client/qal.h b/code/client/qal.h index c72285b3..3495cc85 100644 --- a/code/client/qal.h +++ b/code/client/qal.h @@ -120,7 +120,7 @@ extern LPALBUFFERDATA qalBufferData; extern LPALBUFFERF qalBufferf; extern LPALBUFFER3F qalBuffer3f; extern LPALBUFFERFV qalBufferfv; -extern LPALBUFFERF qalBufferi; +extern LPALBUFFERI qalBufferi; extern LPALBUFFER3F qalBuffer3i; extern LPALBUFFERFV qalBufferiv; extern LPALGETBUFFERF qalGetBufferf; diff --git a/code/client/snd_codec.h b/code/client/snd_codec.h index 1ec4b110..8551827d 100644 --- a/code/client/snd_codec.h +++ b/code/client/snd_codec.h @@ -34,11 +34,12 @@ extern "C" { typedef struct snd_info_s { int rate; - int width; + float width; int channels; int samples; int size; int dataofs; + int dataalign; } snd_info_t; typedef struct snd_codec_s snd_codec_t; diff --git a/code/client/snd_codec_mp3.c b/code/client/snd_codec_mp3.c index 47c1ac86..bcb60e60 100644 --- a/code/client/snd_codec_mp3.c +++ b/code/client/snd_codec_mp3.c @@ -622,7 +622,7 @@ int S_MP3_CodecReadStream(snd_stream_t* stream, int bytes, void* buffer) mp3info = stream->ptr; // Make sure we get complete frames all the way through. - bytes -= bytes % (stream->info.channels * stream->info.width); + bytes -= fmod(bytes, (stream->info.channels * stream->info.width)); if (mp3info->buflen) { diff --git a/code/client/snd_codec_wav.c b/code/client/snd_codec_wav.c index 7fcdea59..7fed9bf3 100644 --- a/code/client/snd_codec_wav.c +++ b/code/client/snd_codec_wav.c @@ -133,6 +133,7 @@ static qboolean S_ReadRIFFHeader(fileHandle_t file, snd_info_t *info) char dump[16]; int bits; int fmtlen = 0; + int bytealign; // skip the riff wav header FS_Read(dump, 12, file); @@ -149,17 +150,22 @@ static qboolean S_ReadRIFFHeader(fileHandle_t file, snd_info_t *info) info->channels = FGetLittleShort(file); info->rate = FGetLittleLong(file); FGetLittleLong(file); - FGetLittleShort(file); + bytealign = FGetLittleShort(file); bits = FGetLittleShort(file); - if( bits < 8 ) - { - Com_Printf( S_COLOR_RED "ERROR: Less than 8 bit sound is not supported\n"); - return qfalse; - } + //if( bits < 8 ) + //{ + // Com_Printf( S_COLOR_RED "ERROR: Less than 8 bit sound is not supported\n"); + // return qfalse; + //} - info->width = bits / 8; + info->width = bits / 8.0; info->dataofs = 0; + if (bits == 16) { + info->dataalign = 1; + } else { + info->dataalign = (bytealign / info->channels - 4) / 4 * 8 + 1; + } // Skip the rest of the format chunk if required if(fmtlen > 16) diff --git a/code/client/snd_local_new.h b/code/client/snd_local_new.h index d20efcfb..877df120 100644 --- a/code/client/snd_local_new.h +++ b/code/client/snd_local_new.h @@ -62,6 +62,7 @@ typedef struct { int dataofs; int datasize; + int dataalign; } wavinfo_t; typedef struct sfx_s { diff --git a/code/client/snd_mem_new.cpp b/code/client/snd_mem_new.cpp index 264e6b22..0bf2dd63 100644 --- a/code/client/snd_mem_new.cpp +++ b/code/client/snd_mem_new.cpp @@ -43,13 +43,13 @@ short int GetLittleShort() byte bytes[2]; } val; -# ifdef Q3_LITTLE_ENDIAN +#ifdef Q3_LITTLE_ENDIAN val.bytes[0] = data_p[0]; val.bytes[1] = data_p[1]; -# else +#else val.bytes[0] = data_p[1]; val.bytes[1] = data_p[0]; -# endif +#endif data_p += sizeof(short); return val.value; @@ -67,17 +67,17 @@ int GetLittleLong() byte bytes[4]; } val; -# ifdef Q3_LITTLE_ENDIAN +#ifdef Q3_LITTLE_ENDIAN val.bytes[0] = data_p[0]; val.bytes[1] = data_p[1]; val.bytes[2] = data_p[2]; val.bytes[3] = data_p[3]; -# else +#else val.bytes[0] = data_p[3]; val.bytes[1] = data_p[2]; val.bytes[2] = data_p[1]; val.bytes[3] = data_p[0]; -# endif +#endif data_p += sizeof(int); return val.value; @@ -97,13 +97,13 @@ void SetLittleShort(int i) val.value = i; -# ifdef Q3_LITTLE_ENDIAN +#ifdef Q3_LITTLE_ENDIAN data_p[0] = val.bytes[0]; data_p[1] = val.bytes[1]; -# else +#else data_p[0] = val.bytes[1]; data_p[1] = val.bytes[0]; -# endif +#endif data_p += sizeof(short); } @@ -122,17 +122,17 @@ void SetLittleLong(int i) val.value = i; -# ifdef Q3_LITTLE_ENDIAN +#ifdef Q3_LITTLE_ENDIAN data_p[0] = val.bytes[0]; data_p[1] = val.bytes[1]; data_p[2] = val.bytes[2]; data_p[3] = val.bytes[3]; -# else +#else data_p[0] = val.bytes[3]; data_p[1] = val.bytes[2]; data_p[2] = val.bytes[1]; data_p[3] = val.bytes[0]; -# endif +#endif data_p += sizeof(int); } @@ -218,6 +218,7 @@ wavinfo_t GetWavinfo(const char *name, byte *wav, int wavlength) { wavinfo_t info; int samples; + short bytealign; memset(&info, 0, sizeof(wavinfo_t)); @@ -247,7 +248,9 @@ wavinfo_t GetWavinfo(const char *name, byte *wav, int wavlength) if (info.format == 17) { info.channels = GetLittleShort(); info.rate = (float)GetLittleLong(); - data_p += 6; + data_p += 4; + + bytealign = GetLittleShort(); info.width = (float)GetLittleShort() / 8.f; data_p += 2; @@ -270,12 +273,16 @@ wavinfo_t GetWavinfo(const char *name, byte *wav, int wavlength) Com_Error(ERR_DROP, "Sound %s has a bad loop length", name); } - info.dataofs = 0; + info.dataofs = data_p - wav; + info.datasize = iff_chunk_len - bytealign + info.dataofs; + info.dataalign = (bytealign / info.channels - 4) / 4 * 8 + 1; } else if (info.format == 1) { info.channels = GetLittleShort(); info.rate = (float)GetLittleLong(); - data_p += 6; - info.width = (float)(GetLittleShort() / 8); + data_p += 4; + + bytealign = GetLittleShort(); + info.width = (float)GetLittleShort() / 8.f; FindChunk("data"); if (!data_p) { @@ -292,14 +299,16 @@ wavinfo_t GetWavinfo(const char *name, byte *wav, int wavlength) Com_Error(ERR_DROP, "Sound %s has a bad loop length", name); } - info.dataofs = data_p - wav; + info.dataofs = data_p - wav; + info.datasize = iff_chunk_len; + info.dataalign = (bytealign / info.channels - 4) / 4 * 8 + 1; + // dataalign should always be 1 + assert(info.dataalign == 1); } else { Com_Printf("Microsoft PCM format only\n"); return info; } - info.datasize = iff_chunk_len; - return info; } @@ -310,26 +319,24 @@ DownSampleWav */ qboolean DownSampleWav(wavinfo_t *info, byte *wav, int wavlength, int newkhz, byte **newdata) { - int newdatasize; - byte* datap; - int i; - int ii; - int error; - int width; - int oldsamples; - int oldrate; + int newdatasize; + byte *datap; + int i; + int ii; + int error; + int width; + int oldsamples; + int oldrate; newdatasize = 0; - datap = &wav[info->dataofs]; + datap = &wav[info->dataofs]; - if (info->channels > 1) - { + if (info->channels > 1) { Com_DPrintf("Could not downsample WAV file. Stereo WAVs not supported!\n"); return 0; } - if (info->format != 1 || !info->dataofs) - { + if (info->format != 1 || !info->dataofs) { Com_DPrintf("Could not downsample WAV file. Not PCM format!\n"); return 0; } @@ -348,29 +355,27 @@ qboolean DownSampleWav(wavinfo_t *info, byte *wav, int wavlength, int newkhz, by } } - oldsamples = info->samples; - oldrate = info->rate; + oldsamples = info->samples; + oldrate = info->rate; info->samples = newdatasize / width; - info->rate = (float)newkhz; + info->rate = (float)newkhz; newdatasize += info->dataofs; - *newdata = (byte*)Z_TagMalloc(newdatasize, TAG_SOUND); + *newdata = (byte *)Z_TagMalloc(newdatasize, TAG_SOUND); memcpy(*newdata, wav, info->dataofs); iff_data = *newdata; - iff_end = *newdata + newdatasize; + iff_end = *newdata + newdatasize; FindChunk("RIFF"); - if (!data_p || strncmp((const char*)data_p + 8, "WAVE", 4u)) - { + if (!data_p || strncmp((const char *)data_p + 8, "WAVE", 4u)) { Com_DPrintf("Missing RIFF/WAVE chunks\n"); return 0; } iff_data = data_p + 12; FindChunk("fmt "); - if (!data_p) - { + if (!data_p) { Com_DPrintf("Missing fmt chunk\n"); return 0; } @@ -380,8 +385,7 @@ qboolean DownSampleWav(wavinfo_t *info, byte *wav, int wavlength, int newkhz, by data_p += 8; FindChunk("data"); - if (!data_p) - { + if (!data_p) { Com_DPrintf("Missing data chunk\n"); return 0; } @@ -427,10 +431,10 @@ S_LoadSound */ qboolean S_LoadSound(const char *fileName, sfx_t *sfx, int streamed, qboolean force_load) { - int size; + int size; fileHandle_t file_handle; - char tempName[MAX_RES_NAME + 1]; - int realKhz; + char tempName[MAX_RES_NAME + 1]; + int realKhz; sfx->buffer = 0; @@ -440,10 +444,10 @@ qboolean S_LoadSound(const char *fileName, sfx_t *sfx, int streamed, qboolean fo if (streamed) { sfx->length = 5000; - sfx->width = 1; + sfx->width = 1; sfx->iFlags |= SFX_FLAG_STREAMED; sfx->time_length = 5000.0; - sfx->data = NULL; + sfx->data = NULL; return qtrue; } @@ -456,22 +460,20 @@ qboolean S_LoadSound(const char *fileName, sfx_t *sfx, int streamed, qboolean fo } size = FS_FOpenFileRead(fileName, &file_handle, qfalse, qtrue); - if (size <= 0) - { + if (size <= 0) { if (file_handle) { FS_FCloseFile(file_handle); } return qfalse; } - sfx->data = (byte*)Z_TagMalloc(size, TAG_SOUND); + sfx->data = (byte *)Z_TagMalloc(size, TAG_SOUND); FS_Read(sfx->data, size, file_handle); FS_FCloseFile(file_handle); sfx->info = GetWavinfo(fileName, sfx->data, size); - if (sfx->info.channels != 1 && !streamed) - { + if (sfx->info.channels != 1 && !streamed) { Com_Printf("%s is a stereo wav file\n", fileName); Z_Free(sfx->data); sfx->data = NULL; @@ -496,8 +498,8 @@ qboolean S_LoadSound(const char *fileName, sfx_t *sfx, int streamed, qboolean fo } if (!(sfx->iFlags & SFX_FLAG_STREAMED) && realKhz < sfx->info.rate) { - byte* newdata; - int newdatasize; + byte *newdata; + int newdatasize; newdata = NULL; if (sfx->iFlags & SFX_FLAG_NO_OFFSET) { @@ -508,13 +510,13 @@ qboolean S_LoadSound(const char *fileName, sfx_t *sfx, int streamed, qboolean fo if (newdatasize) { Z_Free(sfx->data); - sfx->data = newdata; + sfx->data = newdata; sfx->info.datasize = newdatasize; } } - sfx->length = sfx->info.samples; - sfx->width = sfx->info.width; + sfx->length = sfx->info.samples; + sfx->width = sfx->info.width; sfx->time_length = sfx->info.samples / sfx->info.rate * 1000.f; if (sfx->iFlags & SFX_FLAG_STREAMED) { @@ -540,7 +542,7 @@ S_LoadMP3 */ qboolean S_LoadMP3(const char *fileName, sfx_t *sfx) { - int length; + int length; fileHandle_t file_handle; length = FS_FOpenFileRead(fileName, &file_handle, 0, 1); @@ -552,9 +554,9 @@ qboolean S_LoadMP3(const char *fileName, sfx_t *sfx) } memset(&sfx->info, 0, sizeof(sfx->info)); - sfx->data = (byte*)Z_TagMalloc(length, TAG_SOUND); + sfx->data = (byte *)Z_TagMalloc(length, TAG_SOUND); sfx->length = length; - sfx->width = 1; + sfx->width = 1; FS_Read(sfx->data, length, file_handle); FS_FCloseFile(file_handle); diff --git a/code/client/snd_openal_new.cpp b/code/client/snd_openal_new.cpp index 9f185e32..fc242f76 100644 --- a/code/client/snd_openal_new.cpp +++ b/code/client/snd_openal_new.cpp @@ -100,6 +100,9 @@ int music_currentsong = 0; static qboolean enumeration_ext = qfalse; static qboolean enumeration_all_ext = qfalse; +static qboolean ima4_ext = qfalse; +static qboolean soft_block_align = qfalse; + song_t music_songs[MAX_MUSIC_SONGS]; openal_internal_t openal; static float s_fFadeStartTime; @@ -113,7 +116,7 @@ static int S_OPENAL_SpatializeStereoSound(const vec3_t listener_origin, const vec3_t listener_left, const vec3_t origin); static void S_OPENAL_reverb(int iChannel, int iReverbType, float fReverbLevel); static bool S_OPENAL_LoadMP3_Codec(const char *_path, sfx_t *pSfx); -static ALuint S_OPENAL_Format(int width, int channels); +static ALuint S_OPENAL_Format(float width, int channels); #define alDieIfError() __alDieIfError(__FILE__, __LINE__) @@ -449,7 +452,9 @@ S_OPENAL_InitExtensions */ static bool S_OPENAL_InitExtensions() { - Com_Printf("AL extensions ignored\n"); + ima4_ext = qalIsExtensionPresent("AL_EXT_IMA4"); + soft_block_align = qalIsExtensionPresent("AL_SOFT_block_alignment"); + return true; extensions_table_t extensions_table[4] = { @@ -612,7 +617,9 @@ qboolean S_OPENAL_Init() } for (i = 0; i < MAX_SOUNDSYSTEM_CHANNELS_2D_STREAM; i++) { - if (!S_OPENAL_InitChannel(i + MAX_SOUNDSYSTEM_CHANNELS_3D + MAX_SOUNDSYSTEM_CHANNELS_2D, &openal.chan_2D_stream[i])) { + if (!S_OPENAL_InitChannel( + i + MAX_SOUNDSYSTEM_CHANNELS_3D + MAX_SOUNDSYSTEM_CHANNELS_2D, &openal.chan_2D_stream[i] + )) { return false; } } @@ -827,7 +834,8 @@ void S_DumpInfo() S_DumpStatus( "Misc", i, - openal.channel[MAX_SOUNDSYSTEM_CHANNELS_3D + MAX_SOUNDSYSTEM_CHANNELS_2D + MAX_SOUNDSYSTEM_CHANNELS_2D_STREAM + i] + openal.channel + [MAX_SOUNDSYSTEM_CHANNELS_3D + MAX_SOUNDSYSTEM_CHANNELS_2D + MAX_SOUNDSYSTEM_CHANNELS_2D_STREAM + i] ); } } @@ -1818,7 +1826,9 @@ static int S_OPENAL_Start2DLoopSound( pChannel->set_gain(fVolumeToPlay); pChannel->start_sample(); if (s_show_sounds->integer > 0) { - Com_DPrintf("OpenAL: %d (#%i) - %s (vol %f)\n", cl.serverTime, pLoopSound->iChannel, pLoopSound->pSfx->name, fVolume); + Com_DPrintf( + "OpenAL: %d (#%i) - %s (vol %f)\n", cl.serverTime, pLoopSound->iChannel, pLoopSound->pSfx->name, fVolume + ); } return iChannel; @@ -2138,7 +2148,9 @@ void S_OPENAL_AddLoopSounds(const vec3_t vTempAxis) } if (s_show_sounds->integer > 0) { - Com_DPrintf("OpenAL: %d (#%i) - started loop - %s\n", cl.serverTime, pLoopSound->iChannel, pLoopSound->pSfx->name); + Com_DPrintf( + "OpenAL: %d (#%i) - started loop - %s\n", cl.serverTime, pLoopSound->iChannel, pLoopSound->pSfx->name + ); } if (pLoopSound->pSfx->iFlags & (SFX_FLAG_NO_OFFSET) @@ -2991,9 +3003,21 @@ bool openal_channel::set_sfx(sfx_t *pSfx) return false; } + if (pSfx->info.dataalign > 1 && !soft_block_align) { + Com_DPrintf( + "OpenAL: Alignment specified but AL doesn't support block alignment (%d).", pSfx->info.dataalign + ); + return false; + } + qalGenBuffers(1, &pSfx->buffer); alDieIfError(); + if (pSfx->info.dataalign > 1) { + qalBufferi(pSfx->buffer, AL_UNPACK_BLOCK_ALIGNMENT_SOFT, pSfx->info.dataalign); + alDieIfError(); + } + qalBufferData( pSfx->buffer, fmt, @@ -4216,7 +4240,7 @@ int S_CurrentMoviePosition() S_AL_Format ================= */ -static ALuint S_OPENAL_Format(int width, int channels) +static ALuint S_OPENAL_Format(float width, int channels) { ALuint format = AL_FORMAT_MONO16; @@ -4233,6 +4257,17 @@ static ALuint S_OPENAL_Format(int width, int channels) } else if (channels == 2) { format = AL_FORMAT_STEREO16; } + } else if (width == 0.5) { + if (ima4_ext && soft_block_align) { + if (channels == 1) { + format = AL_FORMAT_MONO_IMA4; + } else if (channels == 2) { + format = AL_FORMAT_STEREO_IMA4; + } + } else { + // unsupported + format = 0; + } } return format; @@ -4360,6 +4395,11 @@ bool openal_channel_two_d_stream::set_sfx(sfx_t *pSfx) return true; } + if (stream->info.dataalign > 1 && soft_block_align) { + qalBufferi(buffers[currentBuf], AL_UNPACK_BLOCK_ALIGNMENT_SOFT, stream->info.dataalign); + alDieIfError(); + } + qalBufferData(buffers[currentBuf], pSfx->info.format, rawData, bytesRead, stream->info.rate); alDieIfError(); @@ -4499,6 +4539,11 @@ void openal_channel_two_d_stream::update() } } + if (stream->info.dataalign > 1 && soft_block_align) { + qalBufferi(buffers[currentBuf], AL_UNPACK_BLOCK_ALIGNMENT_SOFT, stream->info.dataalign); + alDieIfError(); + } + qalBufferData(buffers[currentBuf], format, rawData, bytesRead, stream->info.rate); alDieIfError();