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