shithub: zelda3

Download patch

ref: 312be71259a0ac8ee7eeb70f8b9b87589b7cdee5
parent: 5765732f64ed03e0e40335d4ac7ad912a2f9f7f5
author: Snesrev <[email protected]>
date: Fri Sep 16 14:25:52 EDT 2022

Add support for MSU audio tracks

 - Put the PCM files in the msu/ subfolder

--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,4 @@
 /*.exe
 /*.out
 /snes/*.o
+/msu/alttp_msu-*.pcm
\ No newline at end of file
--- a/config.c
+++ b/config.c
@@ -233,6 +233,9 @@
     } else if (StringEqualsNoCase(key, "AudioSamples")) {
       g_config.audio_samples = (uint16)strtol(value, (char**)NULL, 10);
       return true;
+    } else if (StringEqualsNoCase(key, "EnableMSU")) {
+      g_config.enable_msu = (uint16)strtol(value, (char **)NULL, 10);
+      return true;
     }
   } else if (section == 3) {
     if (StringEqualsNoCase(key, "Autosave")) {
@@ -245,6 +248,8 @@
           g_config.extended_aspect_ratio = (224 * 16 / 9 - 256) / 2;
         else if (strcmp(s, "16:10") == 0)
           g_config.extended_aspect_ratio = (224 * 16 / 10 - 256) / 2;
+        else if (strcmp(s, "4:3") == 0)
+          g_config.extended_aspect_ratio = 0;
         else if (strcmp(s, "unchanged_sprites") == 0)
           g_config.extended_aspect_ratio_nospr = true;
         else
--- a/config.h
+++ b/config.h
@@ -45,6 +45,7 @@
   uint8 extended_aspect_ratio;
   bool extended_aspect_ratio_nospr;
   bool display_perf_title;
+  bool enable_msu;
 } Config;
 
 extern Config g_config;
--- a/main.c
+++ b/main.c
@@ -184,7 +184,8 @@
   g_snes_width = 2 * (g_config.extended_aspect_ratio * 2 + 256);
   g_wanted_zelda_features = (g_zenv.ppu->extraLeftRight && !g_config.extended_aspect_ratio_nospr) ? kFeatures0_ExtendScreen64 : 0;
   g_ppu_render_flags = g_config.new_renderer * kPpuRenderFlags_NewRenderer | g_config.enhanced_mode7 * kPpuRenderFlags_4x4Mode7;
-  
+  msu_enabled = g_config.enable_msu;
+
   if (g_config.fullscreen == 1)
     g_win_flags ^= SDL_WINDOW_FULLSCREEN_DESKTOP;
   else if (g_config.fullscreen == 2)
@@ -402,6 +403,7 @@
   return 0;
 }
 
+
 static void PlayAudio(Snes *snes, SDL_AudioDeviceID device, int channels, int16 *audioBuffer) {
   // generate enough samples
   if (snes) {
@@ -411,6 +413,11 @@
   }
 
   dsp_getSamples(GetDspForRendering(), audioBuffer, g_samples_per_block, channels);
+
+  // Mixin the msu data?
+  if (channels == 2)
+    MixinMsuAudioData(audioBuffer, g_samples_per_block);
+
   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
--- a/nmi.c
+++ b/nmi.c
@@ -72,7 +72,7 @@
       zelda_apu_write(APUI00, 0);
   } else if (music_control != last_music_control) {
     last_music_control = music_control;
-    zelda_apu_write(APUI00, music_control);
+    ZeldaPlayMsuAudioTrack();
     if (music_control < 0xf2)
       music_unk1 = music_control;
     music_control = 0;
--- a/spc_player.c
+++ b/spc_player.c
@@ -725,8 +725,7 @@
     HIBYTE(p->master_volume) = p->byte_3E1;
     p->byte_3E1 = 0;
     goto handle_cmd_00;
-  } else if (a == 0xf0) {
-HandleCmd_0xf0_PauseMusic:
+  } else if (a == 0xf0) HandleCmd_0xf0_PauseMusic: {
     p->key_OFF = p->is_chan_on ^ 0xff;
     p->port_to_snes[0] = 0;
     p->cur_chan_bit = 0;
--- a/variables.h
+++ b/variables.h
@@ -1344,3 +1344,80 @@
 #define weathervane_var14 (*(uint8*)(g_ram+0x15879))
 #define weathervane_var2 (*(uint16*)(g_ram+0x158B6))
 #define weathervane_var1 (*(uint8*)(g_ram+0x158B8))
+
+
+#define scratch_0 (*(uint16*)(g_ram+0x72))
+#define scratch_1 (*(uint16*)(g_ram+0x74))
+#define srm_var1 (*(uint16*)(g_zenv.sram+0x1ffe))
+#define messaging_buf ((uint16*)(g_ram+0x10000))
+#define quake_arr1 ((uint8*)(g_ram+0x15800))
+#define quake_arr2 ((uint8*)(g_ram+0x15805))
+#define quake_var5 (*(uint8*)(g_ram+0x1580A))
+#define quake_var1 (*(uint16*)(g_ram+0x1580B))
+#define quake_var2 (*(uint16*)(g_ram+0x1580D))
+#define quake_var4 (*(uint8*)(g_ram+0x1580F))
+#define ether_y3 (*(uint16*)(g_ram+0x15810))
+#define ether_var1 (*(uint8*)(g_ram+0x15812))
+#define ether_y (*(uint16*)(g_ram+0x15813))
+#define ether_x (*(uint16*)(g_ram+0x15815))
+#define quake_var3 (*(uint16*)(g_ram+0x1581E))
+#define bombos_arr7 ((uint8*)(g_ram+0x15820))
+#define bombos_y_lo ((uint8*)(g_ram+0x15824))
+#define bombos_y_hi ((uint8*)(g_ram+0x15864))
+#define bombos_x_lo ((uint8*)(g_ram+0x158A4))
+#define bombos_x_hi ((uint8*)(g_ram+0x158E4))
+#define bombos_y_coord2 ((uint16*)(g_ram+0x15924))
+#define bombos_x_coord2 ((uint16*)(g_ram+0x1592C))
+#define bombos_var4 (*(uint8*)(g_ram+0x15934))
+#define bombos_arr3 ((uint8*)(g_ram+0x15935))
+#define bombos_arr4 ((uint8*)(g_ram+0x15945))
+#define bombos_y_coord ((uint16*)(g_ram+0x15955))
+#define bombos_x_coord ((uint16*)(g_ram+0x159D5))
+#define bombos_var3 (*(uint8*)(g_ram+0x15A55))
+#define bombos_var2 (*(uint8*)(g_ram+0x15A56))
+#define bombos_var1 (*(uint8*)(g_ram+0x15A57))
+
+#define happiness_pond_y_vel ((uint8*)(g_ram+0x15800))
+#define happiness_pond_x_vel ((uint8*)(g_ram+0x1580C))
+#define happiness_pond_z_vel ((uint8*)(g_ram+0x15818))
+#define happiness_pond_y_lo ((uint8*)(g_ram+0x15824))
+#define happiness_pond_y_hi ((uint8*)(g_ram+0x15830))
+#define happiness_pond_x_lo ((uint8*)(g_ram+0x1583C))
+#define happiness_pond_x_hi ((uint8*)(g_ram+0x15848))
+#define happiness_pond_z ((uint8*)(g_ram+0x15854))
+#define happiness_pond_timer ((uint8*)(g_ram+0x15860))
+#define happiness_pond_arr1 ((uint8*)(g_ram+0x1586C))
+#define happiness_pond_item_to_link ((uint8*)(g_ram+0x1587A))
+#define happiness_pond_y_subpixel ((uint8*)(g_ram+0x15886))
+#define happiness_pond_x_subpixel ((uint8*)(g_ram+0x15892))
+#define happiness_pond_z_subpixel ((uint8*)(g_ram+0x1589E))
+#define happiness_pond_step ((uint8*)(g_ram+0x158AA))
+
+
+#define turn_on_off_water_ctr (*(uint8*)(g_ram+0x424))
+#define mirror_vars (*(MirrorHdmaVars*)(g_ram+0x6A0))
+#define sprite_N_word ((uint16*)(g_ram+0xBC0))
+#define sprite_where_in_overworld ((uint8*)(g_ram+0x1DF80))
+#define alt_sprite_B ((uint8*)(g_ram+0x1FA5C))
+#define uvram_screen (*(UploadVram_32x32*)&g_ram[0x1000])
+#define vram_upload_offset (*(uint16*)(g_ram+0x1000))
+#define vram_upload_data ((uint16*)(g_ram+0x1002))
+#define vram_upload_tile_buf ((uint16*)(g_ram+0x1100))
+#define overworld_entrance_sequence_counter (*(uint8*)(g_ram+0xc8))
+
+#ifndef overworld_tileattr
+#define overworld_tileattr ((uint16*)(g_ram+0x2000))
+#endif
+#define dung_line_ptrs_row0 (*(uint16*)(g_ram+0xbf))
+#define star_shaped_switches_tile ((uint16*)(g_ram+0x6A0))
+#define dung_inter_starcases ((uint16*)(g_ram+0x6B0))
+#define dung_stairs_table_1 ((uint16*)(g_ram+0x6B8))
+#define selectfile_var8 (*(uint16*)(g_ram+0x630))
+
+#define R10 (*(uint16*)(g_ram+10))
+#define R12 (*(uint16*)(g_ram+12))
+#define R14 (*(uint16*)(g_ram+14))
+#define R16 (*(uint16*)(g_ram+0xc8))
+#define R18 (*(uint16*)(g_ram+0xca))
+#define R20 (*(uint16*)(g_ram+0xcc))
+
--- a/zelda3.ini
+++ b/zelda3.ini
@@ -3,10 +3,10 @@
 Autosave = 0
 DisplayPerfInTitle = 0
 
-# Extended aspect ratio, either 16:9 or 16:10.
+# Extended aspect ratio, either 16:9 or 16:10. 4:3 means normal aspect ratio.
 # Add ", unchanged_sprites" to avoid changing sprite spawn/die behavior. Without this
 # replays will be incompatible
-# ExtendedAspectRatio = 16:9
+ExtendedAspectRatio = 4:3
 
 [Graphics]
 # Fullscreen mode (0=windowed, 1=desktop fullscreen, 2=fullscreen w/mode change)
@@ -25,6 +25,9 @@
 AudioChannels = 2
 # Audio buffer size in samples (power of 2; e.g., 4096, 2048, 1024) [try 1024 if sound is crackly]
 AudioSamples = 2048
+
+# Enable MSU support for audio. Files need to be in a subfolder, msu/alttp_msu-*.pcm
+EnableMSU = 0
 
 
 [KeyMap]
--- a/zelda_cpu_infra.c
+++ b/zelda_cpu_infra.c
@@ -383,6 +383,8 @@
   memcpy(g_zenv.dma->channel, g_snes->dma->channel, sizeof(Dma) - offsetof(Dma, channel));
   
   g_zenv.player->timer_cycles = 0;
+
+  ZeldaOpenMsuFile();
 }
 
 void SaveSnesState(ByteArray *ctx) {
@@ -695,7 +697,7 @@
     turbo = false;
 
     // This is whether APUI00 is true or false, this is used by the ancilla code.
-    uint8 apui00 = g_zenv.player->port_to_snes[0] != 0;
+    uint8 apui00 = ZeldaIsMusicPlaying();
     if (apui00 != g_ram[kRam_APUI00]) {
       g_emulated_ram[kRam_APUI00] = g_ram[kRam_APUI00] = apui00;
       StateRecorder_RecordPatchByte(&state_recorder, 0x648, &apui00, 1);
--- a/zelda_rtl.c
+++ b/zelda_rtl.c
@@ -326,3 +326,122 @@
   SpcPlayer_Upload(g_zenv.player, p);
 }
 
+
+bool msu_enabled;
+static FILE *msu_file;
+static uint32 msu_loop_start;
+static uint32 msu_buffer_size, msu_buffer_pos;
+static uint8 msu_buffer[65536];
+
+static const uint8 kMsuTracksWithRepeat[48] = {
+  1,0,1,1,1,1,1,1,0,1,0,1,1,1,1,0,
+  1,1,1,0,1,1,1,1,1,1,1,1,1,0,1,1,
+  1,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,
+};
+
+#define msu_curr_sample (*(uint32*)(g_ram+0x650))
+#define msu_volume (*(uint8*)(g_ram+0x654))
+#define msu_track (*(uint8*)(g_ram+0x655))
+
+bool ZeldaIsMusicPlaying() {
+  if (msu_track) {
+    return msu_file != NULL;
+  } else {
+    return g_zenv.player->port_to_snes[0] != 0;
+  }
+}
+
+void ZeldaOpenMsuFile() {
+  if (msu_file) fclose(msu_file), msu_file = NULL;
+  if (msu_track == 0)
+    return;
+  char buf[40], hdr[8];
+  sprintf(buf, "msu/alttp_msu-%d.pcm", msu_track);
+  msu_file = fopen(buf, "rb");
+  if (msu_file == NULL || fread(hdr, 1, 8, msu_file) != 8 || *(uint32 *)(hdr + 0) != '1USM') {
+    if (msu_file != NULL) fclose(msu_file), msu_file = NULL;
+    zelda_apu_write(APUI00, msu_track);
+    msu_track = 0;
+    return;
+  }
+  if (msu_curr_sample != 0)
+    fseek(msu_file, msu_curr_sample * 4 + 8, SEEK_SET);
+  printf("Loading MSU PCM file: %s\n", buf);
+  msu_loop_start = *(uint32 *)(hdr + 4);
+  msu_buffer_size = msu_buffer_pos = 0;
+}
+
+void ZeldaPlayMsuAudioTrack() {
+  if (!msu_enabled) normal_playback: {
+    msu_track = 0;
+    zelda_apu_write(APUI00, music_control);
+    return;
+  }
+  if ((music_control & 0xf0) != 0xf0) {
+    msu_track = music_control;
+    msu_volume = 255;
+    msu_curr_sample = 0;
+    ZeldaOpenMsuFile();
+  } else if (msu_file == NULL) {
+    goto normal_playback;
+  }
+  zelda_apu_write(APUI00, 0xf1);  // pause spc player
+}
+
+void MixinMsuAudioData(int16 *audio_buffer, int audio_samples) {  
+  if (msu_file == NULL)
+    return;  // msu inactive
+  // handle volume fade
+  if (last_music_control >= 0xf1) {
+    if (last_music_control == 0xf1)
+      msu_volume = IntMax(msu_volume - 3, 0);
+    else if (last_music_control == 0xf2)
+      msu_volume = IntMax(msu_volume - 3, 0x40);
+    else if (last_music_control == 0xf3)
+      msu_volume = IntMin(msu_volume + 3, 0xff);
+  }
+  if (msu_volume == 0)
+    return;
+  int last_audio_samples = 0;
+  for (;;) {
+    if (msu_buffer_pos >= msu_buffer_size) {
+      msu_buffer_size = (int)fread(msu_buffer, 4, sizeof(msu_buffer) / 4, msu_file);
+      msu_buffer_pos = 0;
+    }
+    int nr = IntMin(audio_samples, msu_buffer_size - msu_buffer_pos);
+    uint8 *buf = msu_buffer + msu_buffer_pos * 4;
+    msu_buffer_pos += nr;
+    msu_curr_sample += nr;
+    int volume = msu_volume + 1;
+    if (volume == 256) {
+      for (int i = 0; i < nr; i++) {
+        audio_buffer[i * 2 + 0] += ((int16 *)buf)[i * 2 + 0];
+        audio_buffer[i * 2 + 1] += ((int16 *)buf)[i * 2 + 1];
+      }
+    } else {
+      for (int i = 0; i < nr; i++) {
+        audio_buffer[i * 2 + 0] += ((int16 *)buf)[i * 2 + 0] * volume >> 8;
+        audio_buffer[i * 2 + 1] += ((int16 *)buf)[i * 2 + 1] * volume >> 8;
+      }
+    }
+    audio_samples -= nr, audio_buffer += nr * 2;
+    if (audio_samples == 0)
+      break;
+    if (nr != 0)
+      continue;
+
+    if (last_audio_samples == audio_samples) {  // error?
+      zelda_apu_write(APUI00, msu_track);
+      fclose(msu_file), msu_file = NULL;
+      return;
+    }
+    last_audio_samples = audio_samples;
+
+    if (!kMsuTracksWithRepeat[msu_track]) {
+      fclose(msu_file), msu_file = NULL;
+      return;
+    }
+    fseek(msu_file, msu_loop_start * 4 + 8, SEEK_SET);
+    msu_curr_sample = msu_loop_start;
+  }
+}
--- a/zelda_rtl.h
+++ b/zelda_rtl.h
@@ -103,6 +103,10 @@
   kRam_CrystalRotateCounter = 0x649,
   kRam_BugsFixed = 0x64a,
   kRam_Features0 = 0x64c,
+
+  // 4 bytes holding the current msu playback sample, then 2 more more msu misc
+  kRam_MsuCurrSample = 0x650,
+
 };
 
 enum {
@@ -214,5 +218,12 @@
 void LoadSongBank(const uint8 *p);
 void ZeldaWriteSram();
 void ZeldaReadSram(struct Snes *snes);
+
+void ZeldaPlayMsuAudioTrack();
+void MixinMsuAudioData(int16 *audio_buffer, int audio_samples);
+void ZeldaOpenMsuFile();
+bool ZeldaIsMusicPlaying();
+
+extern bool msu_enabled;
 
 #endif  // ZELDA_RTL_H