shithub: zelda3

Download patch

ref: 6ccbc7fba30bab731577a0ce4512bec6ce420e8f
parent: e2cea92259ab384150ee7056695db359745f9987
author: Snesrev <snesrev@protonmail.com>
date: Fri Sep 30 00:44:33 EDT 2022

Switch to callback based audio

--- a/main.c
+++ b/main.c
@@ -49,7 +49,6 @@
 static SDL_Renderer *g_renderer;
 static uint8 g_paused, g_turbo, g_replay_turbo = true, g_cursor = true;
 static uint8 g_current_window_scale;
-static int g_samples_per_block;
 static uint8 g_gamepad_buttons;
 static int g_input1_state;
 static bool g_display_perf;
@@ -161,7 +160,34 @@
   }
 }
 
+static SDL_mutex *g_audio_mutex;
+static uint8 *g_audiobuffer, *g_audiobuffer_cur, *g_audiobuffer_end;
+static int g_frames_per_block;
+static uint8 g_audio_channels;
 
+static void SDLCALL AudioCallback(void *userdata, Uint8 *stream, int len) {
+  SDL_LockMutex(g_audio_mutex);
+  while (len != 0) {
+    if (g_audiobuffer_end - g_audiobuffer_cur == 0) {
+      ZeldaRenderAudio((int16*)g_audiobuffer, g_frames_per_block, g_audio_channels);
+      g_audiobuffer_cur = g_audiobuffer;
+      g_audiobuffer_end = g_audiobuffer + g_frames_per_block * g_audio_channels * sizeof(int16);
+    }
+    int n = IntMin(len, g_audiobuffer_end - g_audiobuffer_cur);
+    memcpy(stream, g_audiobuffer_cur, n);
+    g_audiobuffer_cur += n;
+    stream += n;
+    len -= n;
+  }
+
+  ZeldaDiscardUnusedAudioFrames();
+  SDL_UnlockMutex(g_audio_mutex);
+
+
+}
+
+
+
 #undef main
 int main(int argc, char** argv) {
   SwitchDirectory();
@@ -244,7 +270,9 @@
 
   SDL_AudioDeviceID device;
   SDL_AudioSpec want = { 0 }, have;
-  int16_t* audioBuffer = NULL;
+  SDL_mutex *audio_mutex = SDL_CreateMutex();
+  if (!audio_mutex) Die("No mutex");
+  SDL_LockMutex(audio_mutex);
 
   if (g_config.enable_audio) {
     want.freq = g_config.audio_freq;
@@ -251,13 +279,15 @@
     want.format = AUDIO_S16;
     want.channels = g_config.audio_channels;
     want.samples = g_config.audio_samples;
+    want.callback = &AudioCallback;
     device = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
     if (device == 0) {
       printf("Failed to open audio device: %s\n", SDL_GetError());
       return 1;
     }
-    g_samples_per_block = (534 * have.freq) / 32000;
-    audioBuffer = (int16_t*)malloc(g_samples_per_block * have.channels * sizeof(int16));
+    g_audio_channels = have.channels;
+    g_frames_per_block = (534 * have.freq) / 32000;
+    g_audiobuffer = malloc(g_frames_per_block * have.channels * sizeof(int16));
     SDL_PauseAudioDevice(device, 0);
   }
 
@@ -341,26 +371,12 @@
     if ((g_turbo ^ (is_replay & g_replay_turbo)) && (frameCtr++ & (g_turbo ? 0xf : 0x7f)) != 0)
       continue;
 
+    // Unlock mutex for the final rendering stage.
+    SDL_UnlockMutex(audio_mutex);
 
-    uint64 t1 = SDL_GetPerformanceCounter();
-    if (audioBuffer)
-      PlayAudio(device, have.channels, audioBuffer);
-    uint64 t2 = SDL_GetPerformanceCounter();
-
     RenderScreen(window, renderer, texture, (g_win_flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0);
-    uint64 t3 = SDL_GetPerformanceCounter();
     SDL_RenderPresent(renderer); // vsyncs to 60 FPS?
-    uint64 t4 = SDL_GetPerformanceCounter();
 
-    double f = 1e3 / (double)SDL_GetPerformanceFrequency();
-    if (0) printf("Perf %6.2f %6.2f %6.2f %6.2f\n", 
-      (t1 - t0) * f,
-      (t2 - t1) * f,
-      (t3 - t2) * f,
-      (t4 - t3) * f
-      );
-
-
     // if vsync isn't working, delay manually
     curTick = SDL_GetTicks();
 
@@ -379,15 +395,18 @@
       lastTick = curTick;
     }
 #endif
+    SDL_LockMutex(audio_mutex);
   }
   if (g_config.autosave)
     SaveLoadSlot(kSaveLoad_Save, 0);
   // clean sdl
   if (g_config.enable_audio) {
+    SDL_UnlockMutex(audio_mutex);
     SDL_PauseAudioDevice(device, 1);
     SDL_CloseAudioDevice(device);
   }
-  free(audioBuffer);
+  SDL_DestroyMutex(audio_mutex);
+  free(g_audiobuffer);
   SDL_DestroyTexture(texture);
   SDL_DestroyRenderer(renderer);
   SDL_DestroyWindow(window);
@@ -394,20 +413,6 @@
   SDL_Quit();
   //SaveConfigFile();
   return 0;
-}
-
-
-static void PlayAudio(SDL_AudioDeviceID device, int channels, int16 *audioBuffer) {
-  ZeldaRenderAudio(audioBuffer, g_samples_per_block, channels);
-
-  for (int i = 0; i < 10; i++) {
-    if (SDL_GetQueuedAudioSize(device) <= g_samples_per_block * channels * sizeof(int16) * 6) {
-      // don't queue audio if buffer is still filled
-      SDL_QueueAudio(device, audioBuffer, g_samples_per_block * channels * sizeof(int16));
-      return;
-    }
-    SDL_Delay(1);
-  }
 }
 
 static void RenderDigit(uint8 *dst, size_t pitch, int digit, uint32 color, bool big) {
--- a/nmi.c
+++ b/nmi.c
@@ -78,7 +78,7 @@
   }
 
   if (sound_effect_ambient == 0) {
-    if (zelda_apu_read(APUI01) == sound_effect_ambient)
+    if (zelda_apu_read(APUI01) == sound_effect_ambient_last)
       zelda_apu_write(APUI01, 0);
   } else {
     sound_effect_ambient_last = sound_effect_ambient;
--- a/snes/dsp.c
+++ b/snes/dsp.c
@@ -636,8 +636,8 @@
 
 void dsp_getSamples(Dsp* dsp, int16_t* sampleData, int samplesPerFrame, int numChannels) {
   // resample from 534 samples per frame to wanted value
-  float adder = 534.0 / samplesPerFrame;
-  float location = 0.5f;
+  float adder = 534.0f / samplesPerFrame;
+  float location = 0.0f;
 
   if (numChannels == 1) {
     for (int i = 0; i < samplesPerFrame; i++) {
--- a/zelda3.ini
+++ b/zelda3.ini
@@ -31,8 +31,8 @@
 AudioFreq = 44100
 # number of separate sound channels (1=mono, 2=stereo)
 AudioChannels = 2
-# Audio buffer size in samples (power of 2; e.g., 4096, 2048, 1024) [try 1024 if sound is crackly]
-AudioSamples = 2048
+# Audio buffer size in samples (power of 2; e.g., 4096, 2048, 1024) [try 1024 if sound is crackly]. The higher the more lag before you hear sounds.
+AudioSamples = 512
 
 # Enable MSU support for audio. Files need to be in a subfolder, msu/alttp_msu-*.pcm
 # Only works with 44100 hz and 2 channels
@@ -76,7 +76,7 @@
 # Enable various zelda bug fixes
 MiscBugFixes = 1
 
-# Allow bird travel to be cancelled
+# Allow bird travel to be cancelled by hitting the X key
 CancelBirdTravel = 1
 
 [KeyMap]
--- a/zelda_rtl.c
+++ b/zelda_rtl.c
@@ -57,11 +57,42 @@
 static const uint8 kMapModeHdma1[7] = {0xf0, AT_WORD(0xdee7), 0xf0, AT_WORD(0xdfc7), 0};
 static const uint8 kAttractIndirectHdmaTab[7] = {0xf0, AT_WORD(0x1b00), 0xf0, AT_WORD(0x1be0), 0};
 static const uint8 kHdmaTableForPrayingScene[7] = {0xf8, AT_WORD(0x1b00), 0xf8, AT_WORD(0x1bf0), 0};
+
+
+// Maintain a queue cause the snes and audio callback are not in sync.
+struct ApuWriteEnt {
+  uint8 ports[4];
+};
+static struct ApuWriteEnt g_apu_write_ents[16], g_apu_write;
+static uint8 g_apu_write_ent_pos, g_apu_write_count, g_apu_total_write;
 void zelda_apu_write(uint32_t adr, uint8_t val) {
-  assert(adr >= APUI00 && adr <= APUI03);
-  g_zenv.player->input_ports[adr & 0x3] = val;
+  g_apu_write.ports[adr & 0x3] = val;
 }
 
+void ZeldaPushApuState() {
+  g_apu_write_ents[g_apu_write_ent_pos++ & 0xf] = g_apu_write;
+  if (g_apu_write_count < 16)
+    g_apu_write_count++;
+  g_apu_total_write++;
+}
+
+void ZeldaPopApuState() {
+  if (g_apu_write_count != 0)
+    memcpy(g_zenv.player->input_ports, &g_apu_write_ents[(g_apu_write_ent_pos - g_apu_write_count--) & 0xf], 4);
+}
+
+void ZeldaDiscardUnusedAudioFrames() {
+  if (g_apu_write_count != 0 && memcmp(g_zenv.player->input_ports, &g_apu_write_ents[(g_apu_write_ent_pos - g_apu_write_count) & 0xf], 4) == 0) {
+    if (g_apu_total_write >= 16) {
+      g_apu_total_write = 14;
+      g_apu_write_count--;
+    }
+  } else {
+    g_apu_total_write = 0;
+  }
+}
+
+
 uint8_t zelda_read_apui00() {
   // This needs to be here because the ancilla code reads
   // from the apu and we don't want to make the core code
@@ -792,6 +823,9 @@
   } else {
     g_emu_runframe(inputs, run_what);
   }
+
+  ZeldaPushApuState();
+
   return is_replay;
 }
 
@@ -1015,6 +1049,7 @@
 
 
 void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels) {
+  ZeldaPopApuState();
   SpcPlayer_GenerateSamples(g_zenv.player);
   dsp_getSamples(g_zenv.player->dsp, audio_buffer, samples, channels);
   if (channels == 2)
--- a/zelda_rtl.h
+++ b/zelda_rtl.h
@@ -69,6 +69,7 @@
 void ZeldaReadSram();
 
 void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels);
+void ZeldaDiscardUnusedAudioFrames();
 
 typedef void ZeldaRunFrameFunc(uint16 input, int run_what);
 typedef void ZeldaSyncAllFunc();