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