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
This commit is contained in:
smallmodel 2024-11-27 20:37:56 +01:00 committed by GitHub
parent 459e13dc76
commit 1e9decf193
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 135 additions and 78 deletions

View file

@ -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");

View file

@ -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;

View file

@ -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;

View file

@ -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)
{

View file

@ -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)

View file

@ -62,6 +62,7 @@ typedef struct {
int dataofs;
int datasize;
int dataalign;
} wavinfo_t;
typedef struct sfx_s {

View file

@ -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);

View file

@ -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();