dethrace-labs / dethrace

Reverse engineering the 1997 game "Carmageddon"
https://twitter.com/dethrace_labs
GNU General Public License v3.0
669 stars 38 forks source link

The horn has a noticeable clicking sound #324

Closed dethrace-labs closed 1 year ago

dethrace-labs commented 1 year ago

When holding down the horn ('H' key in race), there is a continuous click/pop sound as the horn sound loops.

This does not exist in OG, and is due to miniaudio not loading the wav file correctly. SDL_mixer also has the same issue.

madebr commented 1 year ago

For a wip patch, I implemented loading sound from memory instead of from file. When applying it on current master, the clicking goes away.

--- a/src/S3/audio.c
+++ b/src/S3/audio.c
@@ -215,6 +215,11 @@ int S3DisposeDescriptor(tS3_sound_id id) {
         if (desc->sound_data == NULL) {
             return 0;
         }
+
+        tS3_sample* sample = (tS3_sample*)desc->sound_data;
+        ma_audio_buffer_uninit(sample->audio_buffer);
+        S3MemFree(sample->dataptr);
+
         S3MemFree(desc->sound_data);
         desc->sound_data = NULL;
     }
--- a/src/S3/s3_defs.h
+++ b/src/S3/s3_defs.h
@@ -162,6 +162,7 @@ typedef struct tS3_sample {
     int channels;
     char* dataptr;
     void* freeptr;
+    void* audio_buffer;  // added by DethRace (FIXME: use dataptr for storage + freeptr for freeing)
 } tS3_sample;

 typedef struct tS3_hardware_info {
diff --git a/src/S3/s3sound.c b/src/S3/s3sound.c
index b4ab571f..121780fc 100644
--- a/src/S3/s3sound.c
+++ b/src/S3/s3sound.c
@@ -131,6 +131,8 @@ void* S3LoadWavFile(char* pFile_name, tS3_sample* pSample) {
     char* data_ptr;             // [esp+C8h] [ebp-Ch] BYREF
     // char* locked_buffer_data;   // [esp+CCh] [ebp-8h] BYREF
     size_t file_len; // [esp+D0h] [ebp-4h]
+    ma_format format;  // Added by DethRace
+    ma_uint64 nbFrames;  // Added by DethRace

     f = fopen(pFile_name, "r");
     if (f == NULL) {
@@ -154,12 +156,13 @@ void* S3LoadWavFile(char* pFile_name, tS3_sample* pSample) {
     wav_format = 0;
     data_ptr = 0;
     if (S3ReadWavHeader(buf, &wav_format, &data_ptr, &data_size) == 0) {
+        S3MemFree(buf);
         gS3_last_error = eS3_error_readfile;
         dr_dprintf("ERROR: .WAV file '%s' is crap", pFile_name);
         return 0;
     }
     pSample->freeptr = 0;
-    pSample->dataptr = 0;
+    pSample->dataptr = buf;
     pSample->size = data_size;
     pSample->rate = wav_format->nSamplesPerSec;
     pSample->resolution = wav_format->nAvgBytesPerSec;
@@ -170,12 +173,52 @@ void* S3LoadWavFile(char* pFile_name, tS3_sample* pSample) {
     pSample->channels = BrSwap32(pSample->channels);
 #endif

+    switch (wav_format->wBitsPerSample) {
+    case 8:
+        format = ma_format_u8;
+        nbFrames = data_size / wav_format->nChannels;
+        break;
+    case 16:
+        format = ma_format_s16;
+        nbFrames = data_size / 2 / wav_format->nChannels;
+        break;
+    case 24:
+        format = ma_format_s24;
+        nbFrames = data_size / 3 / wav_format->nChannels;
+        break;
+    case 32:
+        format = ma_format_s32;
+        nbFrames = data_size / 4 / wav_format->nChannels;
+        break;
+    default:
+        format = ma_format_unknown;
+        nbFrames = data_size * 8 / wav_format->wBitsPerSample / wav_format->nChannels;
+        break;
+    }
+    ma_audio_buffer_config bufferConfig = ma_audio_buffer_config_init(
+        format,
+        wav_format->nChannels,
+        nbFrames,
+        data_ptr,
+        NULL);
+    bufferConfig.sampleRate = wav_format->nSamplesPerSec;
+
+    pSample->audio_buffer = malloc(sizeof(ma_audio_buffer));
+    if (ma_audio_buffer_init(&bufferConfig, pSample->audio_buffer) != MA_SUCCESS) {
+        gS3_last_error = eS3_error_memory;
+        S3MemFree(buf);
+        return NULL;
+    }
+
     ma_sound* sound = malloc(sizeof(ma_sound));
-    // TOOD: load from memory - we've already read the file data
-    if (ma_sound_init_from_file(&miniaudio_engine, pFile_name, MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_NO_SPATIALIZATION, NULL, NULL, sound) != MA_SUCCESS) {
-        return NULL; // Failed to load sound.
+
+    if (ma_sound_init_from_data_source(&miniaudio_engine, pSample->audio_buffer, MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_NO_SPATIALIZATION, NULL, sound) != MA_SUCCESS) {
+        gS3_last_error = eS3_error_memory;
+        free(sound);
+        free(pSample->audio_buffer);
+        S3MemFree(buf);
+        return NULL; // Failed to load sound
     }
-    S3MemFree(buf);
     return sound;

     // S3MemFree(buf);
@@ -203,7 +246,7 @@ void* S3LoadWavFile(char* pFile_name, tS3_sample* pSample) {
     //     ds_buffer->lpVtbl->Unlock(ds_buffer, locked_buffer_data, locked_buffer_data_len, 0, 0);
     //     return ds_buffer;
     // }
-    return NULL;
+    // return NULL;
 }

 int S3StopSample(tS3_channel* chan) {

Clean up is missing. But that is not done anyway (#328)

dethrace-labs commented 1 year ago

Yes, I made similar changes to load from the data_ptr set by S3ReadWavHeader (I working on a couple of audio fixes on my local).

I didn't look further, but my guess is that there is some junk at the end of the wav file that is handled incorrectly by the miniaudio wav file loader, and also by the MIX_LoadWav function.

I originally also reproduced the same clicking sound by passing the last chunk of the file (buf[file_len - data_size]) to a miniaudio buffer