mirror of
https://github.com/snesrev/zelda3.git
synced 2025-12-19 18:05:55 -05:00
533 lines
19 KiB
C
533 lines
19 KiB
C
#include "audio.h"
|
|
#include "zelda_rtl.h"
|
|
#include "variables.h"
|
|
#include "features.h"
|
|
#include "snes/snes_regs.h"
|
|
#include "spc_player.h"
|
|
#include "third_party/opus-1.3.1-stripped/opus.h"
|
|
#include "config.h"
|
|
#include "assets.h"
|
|
|
|
// This needs to hold a lot more things than with just PCM
|
|
typedef struct MsuPlayerResumeInfo {
|
|
uint32 tag;
|
|
uint32 offset;
|
|
uint32 samples_until_repeat;
|
|
uint16 range_cur, range_repeat;
|
|
uint64 initial_packet_bytes; // To verify we seeked right
|
|
uint8 orig_track; // Using the old zelda track numbers
|
|
uint8 actual_track; // The MSU track index we're playing (Different if using msu deluxe)
|
|
} MsuPlayerResumeInfo;
|
|
|
|
enum {
|
|
kMsuState_Idle = 0,
|
|
kMsuState_FinishedPlaying = 1,
|
|
kMsuState_Resuming = 2,
|
|
kMsuState_Playing = 3,
|
|
};
|
|
|
|
typedef struct MsuPlayer {
|
|
FILE *f;
|
|
uint32 buffer_size, buffer_pos;
|
|
uint32 preskip, samples_until_repeat;
|
|
uint32 total_samples_in_file, repeat_position;
|
|
uint32 cur_file_offs;
|
|
MsuPlayerResumeInfo resume_info;
|
|
uint8 enabled;
|
|
uint8 state;
|
|
float volume, volume_step, volume_target;
|
|
uint16 range_cur, range_repeat;
|
|
OpusDecoder *opus;
|
|
int16 buffer[960 * 2];
|
|
} MsuPlayer;
|
|
|
|
static MsuPlayer g_msu_player;
|
|
|
|
static void MsuPlayer_Open(MsuPlayer *mp, int orig_track, bool resume_from_snapshot);
|
|
|
|
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,
|
|
};
|
|
|
|
static const uint8 kIsMusicOwOrDungeon[] = {
|
|
0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, // 1 = ow songs : 2 = lw, 5 = forest, 7 = town, 9 = dark world, 13 = mountain
|
|
2, 2, 2, 0, 0, 0, 2, 2, 0, 0, 0, 2, 0, 0, 0, 0, // 2 = indoor songs : 16, 17, 18, 22, 23, 27
|
|
};
|
|
|
|
|
|
static const uint8 kMsuDeluxe_OW_Songs[] = {
|
|
37, 37, 42, 38, 38, 38, 38, 39, 37, 37, 42, 38, 38, 38, 38, 41, // lw
|
|
42, 42, 42, 42, 42, 42, 40, 40, 43, 43, 42, 47, 47, 42, 45, 45,
|
|
43, 43, 43, 47, 47, 42, 45, 45, 112, 112, 48, 42, 42, 42, 42, 45,
|
|
44, 44, 48, 48, 48, 46, 46, 46, 44, 44, 44, 48, 48, 46, 46, 46,
|
|
49, 49, 51, 50, 50, 50, 50, 50, 49, 49, 51, 50, 50, 50, 50, 51, // dw
|
|
51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 51, 56, 56, 51, 54, 54,
|
|
52, 52, 52, 56, 56, 51, 54, 54, 58, 52, 57, 51, 51, 51, 51, 54,
|
|
53, 53, 57, 57, 57, 55, 55, 110, 53, 53, 57, 57, 57, 55, 55, 110,
|
|
37, 41, 41, 42, 42, 42, 42, 42, 42, 41, 41, 42, 42, 42, 42, 42, // special
|
|
42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42,
|
|
};
|
|
|
|
static const uint8 kMsuDeluxe_Entrance_Songs[] = {
|
|
59, 59, 60, 61, 61, 61, 62, 62, 63, 64, 64, 64, 105, 65, 65, 66,
|
|
66, 62, 67, 62, 62, 68, 62, 62, 68, 68, 62, 62, 62, 62, 62, 62,
|
|
62, 62, 62, 62, 69, 70, 71, 72, 73, 73, 73, 106, 102, 74, 62, 62,
|
|
75, 75, 76, 77, 78, 68, 79, 80, 81, 62, 62, 62, 82, 75, 242, 59,
|
|
59, 76, 242, 242, 242, 96, 83, 99, 59, 242, 242, 242, 84, 95, 104, 62,
|
|
85, 62, 62, 86, 242, 67, 103, 83, 83, 87, 76, 88, 81, 98, 81, 88,
|
|
83, 89, 75, 97, 90, 91, 91, 100, 92, 93, 92, 242, 93, 107, 62, 75,
|
|
62, 67, 62, 242, 242, 242, 73, 73, 73, 73, 102, 114, 81, 76, 62, 67,
|
|
62, 61, 94, 62, 103,
|
|
};
|
|
|
|
|
|
// Remap an track number into a potentially different track number (used for msu deluxe)
|
|
static uint8 RemapMsuDeluxeTrack(MsuPlayer *mp, uint8 track) {
|
|
if (!(mp->enabled & kMsuEnabled_MsuDeluxe) || track >= sizeof(kIsMusicOwOrDungeon))
|
|
return track;
|
|
switch (kIsMusicOwOrDungeon[track]) {
|
|
case 1:
|
|
return BYTE(overworld_area_index) < sizeof(kMsuDeluxe_OW_Songs) ? kMsuDeluxe_OW_Songs[BYTE(overworld_area_index)] : track;
|
|
case 2:
|
|
if (which_entrance >= sizeof(kMsuDeluxe_Entrance_Songs) || kMsuDeluxe_Entrance_Songs[which_entrance] == 242)
|
|
return track;
|
|
return kMsuDeluxe_Entrance_Songs[which_entrance];
|
|
default:
|
|
return track;
|
|
}
|
|
}
|
|
|
|
bool ZeldaIsPlayingMusicTrack(uint8 track) {
|
|
MsuPlayer *mp = &g_msu_player;
|
|
if (mp->state != kMsuState_Idle && mp->enabled & kMsuEnabled_MsuDeluxe)
|
|
return RemapMsuDeluxeTrack(mp, track) == mp->resume_info.actual_track;
|
|
else
|
|
return track == music_unk1;
|
|
}
|
|
|
|
bool ZeldaIsPlayingMusicTrackWithBug(uint8 track) {
|
|
MsuPlayer *mp = &g_msu_player;
|
|
if (mp->state != kMsuState_Idle && mp->enabled & kMsuEnabled_MsuDeluxe)
|
|
return RemapMsuDeluxeTrack(mp, track) == mp->resume_info.actual_track;
|
|
else
|
|
return track == (enhanced_features0 & kFeatures0_MiscBugFixes ? music_unk1 : last_music_control);
|
|
}
|
|
|
|
uint8 ZeldaGetEntranceMusicTrack(int i) {
|
|
MsuPlayer *mp = &g_msu_player;
|
|
uint8 rv = kEntranceData_musicTrack[i];
|
|
|
|
// For some entrances the original performs a fade out, while msu deluxe has new tracks.
|
|
if (mp->state != kMsuState_Idle && mp->enabled & kMsuEnabled_MsuDeluxe) {
|
|
if (rv == 242 && kMsuDeluxe_Entrance_Songs[which_entrance] != 242)
|
|
rv = 16;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
static const uint8 kVolumeTransitionTarget[3] = { 0, 64, 255};
|
|
static const uint8 kVolumeTransitionStep[3] = { 7, 3, 3};
|
|
static float kVolumeTransitionStepFloat;
|
|
|
|
void ZeldaPlayMsuAudioTrack(uint8 music_ctrl) {
|
|
MsuPlayer *mp = &g_msu_player;
|
|
if (!mp->enabled) {
|
|
mp->resume_info.tag = 0;
|
|
zelda_apu_write(APUI00, music_ctrl);
|
|
return;
|
|
}
|
|
ZeldaApuLock();
|
|
if ((music_ctrl & 0xf0) != 0xf0)
|
|
MsuPlayer_Open(mp, music_ctrl, false);
|
|
else if (music_ctrl >= 0xf1 && music_ctrl <= 0xf3) {
|
|
mp->volume_target = kVolumeTransitionTarget[music_ctrl - 0xf1] * (1.0f / 255);
|
|
mp->volume_step = kVolumeTransitionStepFloat * kVolumeTransitionStep[music_ctrl - 0xf1];
|
|
}
|
|
|
|
if (mp->state == 0) {
|
|
zelda_apu_write(APUI00, music_ctrl);
|
|
} else {
|
|
zelda_apu_write(APUI00, 0xf0); // pause spc player
|
|
}
|
|
ZeldaApuUnlock();
|
|
}
|
|
|
|
static void MsuPlayer_CloseFile(MsuPlayer *mp) {
|
|
if (mp->f)
|
|
fclose(mp->f);
|
|
opus_decoder_destroy(mp->opus);
|
|
mp->opus = NULL;
|
|
mp->f = NULL;
|
|
if (mp->state != kMsuState_FinishedPlaying)
|
|
mp->state = kMsuState_Idle;
|
|
memset(&mp->resume_info, 0, sizeof(mp->resume_info));
|
|
}
|
|
|
|
static void MsuPlayer_Open(MsuPlayer *mp, int orig_track, bool resume_from_snapshot) {
|
|
MsuPlayerResumeInfo resume;
|
|
int actual_track = RemapMsuDeluxeTrack(mp, orig_track);
|
|
|
|
if (!resume_from_snapshot) {
|
|
resume.tag = 0;
|
|
// Attempt to resume MSU playback when exiting back to the overworld.
|
|
if (main_module_index == 9 &&
|
|
actual_track == ((MsuPlayerResumeInfo *)msu_resume_info_alt)->actual_track && g_config.resume_msu) {
|
|
memcpy(&resume, msu_resume_info_alt, sizeof(mp->resume_info));
|
|
}
|
|
if (mp->state >= kMsuState_Resuming)
|
|
memcpy(msu_resume_info_alt, &mp->resume_info, sizeof(mp->resume_info));
|
|
} else {
|
|
memcpy(&resume, msu_resume_info, sizeof(mp->resume_info));
|
|
}
|
|
|
|
mp->volume_target = 1.0f;
|
|
mp->volume_step = kVolumeTransitionStepFloat * 16;
|
|
|
|
mp->state = kMsuState_Idle;
|
|
MsuPlayer_CloseFile(mp);
|
|
if (actual_track == 0)
|
|
return;
|
|
char fname[256], buf[8];
|
|
snprintf(fname, sizeof(fname), "%s%d.%s", g_config.msu_path ? g_config.msu_path : "", actual_track, mp->enabled & kMsuEnabled_Opuz ? "opuz" : "pcm");
|
|
mp->f = fopen(fname, "rb");
|
|
if (mp->f == NULL)
|
|
goto READ_ERROR;
|
|
setvbuf(mp->f, NULL, _IOFBF, 16384);
|
|
if (fread(buf, 1, 8, mp->f) != 8) READ_ERROR: {
|
|
fprintf(stderr, "Unable to read MSU file %s\n", fname);
|
|
MsuPlayer_CloseFile(mp);
|
|
return;
|
|
}
|
|
uint32 file_tag = *(uint32 *)(buf + 0);
|
|
mp->repeat_position = *(uint32 *)(buf + 4);
|
|
mp->state = (resume.actual_track == actual_track && resume.tag == file_tag) ? kMsuState_Resuming : kMsuState_Playing;
|
|
if (mp->state == kMsuState_Resuming) {
|
|
memcpy(&mp->resume_info, &resume, sizeof(mp->resume_info));
|
|
} else {
|
|
mp->resume_info.orig_track = orig_track;
|
|
mp->resume_info.actual_track = actual_track;
|
|
mp->resume_info.tag = file_tag;
|
|
mp->resume_info.range_cur = 8;
|
|
}
|
|
mp->cur_file_offs = mp->resume_info.offset;
|
|
mp->samples_until_repeat = mp->resume_info.samples_until_repeat;
|
|
mp->range_cur = mp->resume_info.range_cur;
|
|
mp->range_repeat = mp->resume_info.range_repeat;
|
|
mp->buffer_size = mp->buffer_pos = 0;
|
|
mp->preskip = 0;
|
|
if (file_tag == (('Z' << 24) | ('U' << 16) | ('P' << 8) | 'O')) {
|
|
mp->opus = opus_decoder_create(48000, 2, NULL);
|
|
if (!mp->opus)
|
|
goto READ_ERROR;
|
|
if (mp->state == kMsuState_Resuming)
|
|
fseek(mp->f, mp->cur_file_offs, SEEK_SET);
|
|
} else if (file_tag == (('1' << 24) | ('U' << 16) | ('S' << 8) | 'M')) {
|
|
fseek(mp->f, 0, SEEK_END);
|
|
mp->total_samples_in_file = (ftell(mp->f) - 8) / 4;
|
|
mp->samples_until_repeat = mp->total_samples_in_file - mp->cur_file_offs;
|
|
fseek(mp->f, mp->cur_file_offs * 4 + 8, SEEK_SET);
|
|
} else {
|
|
goto READ_ERROR;
|
|
}
|
|
}
|
|
|
|
static void MixToBufferWithVolume(int16 *dst, const int16 *src, size_t n, float volume) {
|
|
if (volume == 1.0f) {
|
|
for (size_t i = 0; i < n; i++) {
|
|
dst[i * 2 + 0] += src[i * 2 + 0];
|
|
dst[i * 2 + 1] += src[i * 2 + 1];
|
|
}
|
|
} else {
|
|
uint32 vol = 65536 * volume;
|
|
for (size_t i = 0; i < n; i++) {
|
|
dst[i * 2 + 0] += src[i * 2 + 0] * vol >> 16;
|
|
dst[i * 2 + 1] += src[i * 2 + 1] * vol >> 16;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void MixToBufferWithVolumeRamp(int16 *dst, const int16 *src, size_t n, float volume, float volume_step, float ideal_target) {
|
|
uint64 vol = volume * 281474976710656.0f;
|
|
uint64 step = volume_step * 281474976710656.0f;
|
|
for (size_t i = 0; i < n; i++) {
|
|
uint32 v = (vol >> 32);
|
|
dst[i * 2 + 0] += src[i * 2 + 0] * v >> 16;
|
|
dst[i * 2 + 1] += src[i * 2 + 1] * v >> 16;
|
|
vol += step;
|
|
}
|
|
}
|
|
|
|
static void MixToBuffer(MsuPlayer *mp, int16 *dst, const int16 *src, uint32 n) {
|
|
if (mp->volume != mp->volume_target) {
|
|
float step = mp->volume < mp->volume_target ? mp->volume_step : -mp->volume_step;
|
|
float new_vol = mp->volume + step * n;
|
|
uint32 curn = n;
|
|
if (step >= 0 ? new_vol >= mp->volume_target : new_vol < mp->volume_target) {
|
|
uint32 maxn = (uint32)((mp->volume_target - mp->volume) / step);
|
|
curn = UintMin(maxn, curn);
|
|
new_vol = mp->volume_target;
|
|
}
|
|
float vol = mp->volume;
|
|
mp->volume = new_vol;
|
|
MixToBufferWithVolumeRamp(dst, src, curn, vol, step, new_vol);
|
|
dst += curn * 2, src += curn * 2, n -= curn;
|
|
}
|
|
MixToBufferWithVolume(dst, src, n, mp->volume);
|
|
}
|
|
|
|
void MsuPlayer_Mix(MsuPlayer *mp, int16 *audio_buffer, int audio_samples) {
|
|
int r;
|
|
|
|
do {
|
|
if (mp->buffer_size - mp->buffer_pos == 0) {
|
|
if (mp->opus != NULL) {
|
|
if (mp->samples_until_repeat == 0) {
|
|
if (mp->range_cur == 0) FINISHED_PLAYING: {
|
|
mp->state = kMsuState_FinishedPlaying;
|
|
MsuPlayer_CloseFile(mp);
|
|
return;
|
|
}
|
|
opus_decoder_ctl(mp->opus, OPUS_RESET_STATE);
|
|
fseek(mp->f, mp->range_cur, SEEK_SET);
|
|
uint8 *file_data = (uint8 *)mp->buffer;
|
|
if (fread(file_data, 1, 10, mp->f) != 10) READ_ERROR: {
|
|
fprintf(stderr, "MSU read/decode error!\n");
|
|
zelda_apu_write(APUI00, mp->resume_info.orig_track);
|
|
MsuPlayer_CloseFile(mp);
|
|
return;
|
|
}
|
|
uint32 file_offs = *(uint32 *)&file_data[0];
|
|
assert((file_offs & 0xF0000000) == 0);
|
|
uint32 samples_until_repeat = *(uint32 *)&file_data[4];
|
|
uint16 preskip = *(uint32 *)&file_data[8];
|
|
mp->samples_until_repeat = samples_until_repeat;
|
|
mp->preskip = preskip & 0x3fff;
|
|
if (preskip & 0x4000)
|
|
mp->range_repeat = mp->range_cur;
|
|
mp->range_cur = (preskip & 0x8000) ? mp->range_repeat : mp->range_cur + 10;
|
|
mp->cur_file_offs = file_offs;
|
|
mp->resume_info.range_repeat = mp->range_repeat;
|
|
mp->resume_info.range_cur = mp->range_cur;
|
|
fseek(mp->f, file_offs, SEEK_SET);
|
|
}
|
|
assert(mp->samples_until_repeat != 0);
|
|
for (;;) {
|
|
uint8 *file_data = (uint8 *)mp->buffer;
|
|
*(uint64 *)file_data = 0;
|
|
if (fread(file_data, 1, 2, mp->f) != 2)
|
|
goto READ_ERROR;
|
|
int size = *(uint16 *)file_data & 0x7fff;
|
|
if (size > 1275)
|
|
goto READ_ERROR;
|
|
int n = (*(uint16 *)file_data >> 15);
|
|
if (fread(&file_data[2], 1, size, mp->f) != size)
|
|
goto READ_ERROR;
|
|
// Verify if the snapshot matches the file on disk.
|
|
uint64 initial_file_data = *(uint64 *)file_data;
|
|
if (mp->state == kMsuState_Resuming) {
|
|
mp->state = kMsuState_Playing;
|
|
if (mp->resume_info.initial_packet_bytes != initial_file_data)
|
|
goto READ_ERROR;
|
|
}
|
|
mp->resume_info.initial_packet_bytes = initial_file_data;
|
|
mp->resume_info.samples_until_repeat = mp->samples_until_repeat + mp->preskip;
|
|
mp->resume_info.offset = mp->cur_file_offs;
|
|
mp->cur_file_offs += 2 + size;
|
|
file_data[1] = 0xfc;
|
|
r = opus_decode(mp->opus, &file_data[2 - n], size + n, mp->buffer, 960, 0);
|
|
if (r <= 0)
|
|
goto READ_ERROR;
|
|
if (r > mp->preskip)
|
|
break;
|
|
mp->preskip -= r;
|
|
}
|
|
} else {
|
|
if (mp->samples_until_repeat == 0) {
|
|
if (mp->resume_info.actual_track < sizeof(kMsuTracksWithRepeat) && !kMsuTracksWithRepeat[mp->resume_info.actual_track])
|
|
goto FINISHED_PLAYING;
|
|
mp->samples_until_repeat = mp->total_samples_in_file - mp->repeat_position;
|
|
if (mp->samples_until_repeat == 0)
|
|
goto READ_ERROR; // impossible to make progress
|
|
mp->cur_file_offs = mp->repeat_position;
|
|
fseek(mp->f, mp->cur_file_offs * 4 + 8, SEEK_SET);
|
|
}
|
|
r = UintMin(960, mp->samples_until_repeat);
|
|
if (fread(mp->buffer, 4, r, mp->f) != r)
|
|
goto READ_ERROR;
|
|
mp->resume_info.offset = mp->cur_file_offs;
|
|
mp->cur_file_offs += r;
|
|
}
|
|
uint32 n = UintMin(r - mp->preskip, mp->samples_until_repeat);
|
|
mp->samples_until_repeat -= n;
|
|
mp->buffer_pos = mp->preskip;
|
|
mp->buffer_size = mp->buffer_pos + n;
|
|
mp->preskip = 0;
|
|
}
|
|
#if 0
|
|
if (mp->samples_to_play > 44100 * 5) {
|
|
mp->buffer_pos = mp->buffer_size;
|
|
}
|
|
#endif
|
|
int nr = IntMin(audio_samples, mp->buffer_size - mp->buffer_pos);
|
|
int16 *buf = mp->buffer + mp->buffer_pos * 2;
|
|
mp->buffer_pos += nr;
|
|
|
|
#if 0
|
|
static int t;
|
|
for (int i = 0; i < nr; i++) {
|
|
buf[i * 2 + 0] = buf[i * 2 + 1] = 5000 * sinf(2 * 3.1415 * t++ / 440);
|
|
}
|
|
#endif
|
|
MixToBuffer(mp, audio_buffer, buf, nr);
|
|
|
|
#if 0
|
|
static FILE *f;
|
|
if (!f)f = fopen("out.pcm", "wb");
|
|
fwrite(audio_buffer, 4, nr, f);
|
|
fflush(f);
|
|
#endif
|
|
audio_samples -= nr, audio_buffer += nr * 2;
|
|
} while (audio_samples != 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) {
|
|
g_apu_write.ports[adr & 0x3] = val;
|
|
}
|
|
|
|
void ZeldaPushApuState() {
|
|
ZeldaApuLock();
|
|
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++;
|
|
ZeldaApuUnlock();
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
}
|
|
|
|
static void ZeldaResetApuQueue() {
|
|
g_apu_write_ent_pos = g_apu_total_write = g_apu_write_count = 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
|
|
// dependent on the apu timings, so relocated this value
|
|
// to 0x648.
|
|
return g_ram[kRam_APUI00];
|
|
}
|
|
|
|
uint8_t zelda_apu_read(uint32_t adr) {
|
|
return g_zenv.player->port_to_snes[adr & 0x3];
|
|
}
|
|
|
|
void ZeldaRenderAudio(int16 *audio_buffer, int samples, int channels) {
|
|
ZeldaApuLock();
|
|
ZeldaPopApuState();
|
|
SpcPlayer_GenerateSamples(g_zenv.player);
|
|
dsp_getSamples(g_zenv.player->dsp, audio_buffer, samples, channels);
|
|
if (g_msu_player.f && channels == 2)
|
|
MsuPlayer_Mix(&g_msu_player, audio_buffer, samples);
|
|
ZeldaApuUnlock();
|
|
}
|
|
|
|
bool ZeldaIsMusicPlaying() {
|
|
if (g_msu_player.state != kMsuState_Idle) {
|
|
return g_msu_player.state != kMsuState_FinishedPlaying;
|
|
} else {
|
|
return g_zenv.player->port_to_snes[0] != 0;
|
|
}
|
|
}
|
|
|
|
void ZeldaRestoreMusicAfterLoad_Locked(bool is_reset) {
|
|
// Restore spc variables from the ram dump.
|
|
SpcPlayer_CopyVariablesFromRam(g_zenv.player);
|
|
// This is not stored in the snapshot
|
|
g_zenv.player->timer_cycles = 0;
|
|
|
|
// Restore input ports state
|
|
SpcPlayer *spc_player = g_zenv.player;
|
|
memcpy(spc_player->input_ports, &spc_player->ram[0x410], 4);
|
|
memcpy(g_apu_write.ports, spc_player->input_ports, 4);
|
|
|
|
if (is_reset) {
|
|
SpcPlayer_Initialize(g_zenv.player);
|
|
}
|
|
|
|
MsuPlayer *mp = &g_msu_player;
|
|
if (mp->enabled) {
|
|
mp->volume = 0.0;
|
|
MsuPlayer_Open(mp, music_unk1, true);
|
|
|
|
// If resuming in the middle of a transition, then override
|
|
// the volume with that of the transition.
|
|
if (last_music_control >= 0xf1 && last_music_control <= 0xf3) {
|
|
uint8 target = kVolumeTransitionTarget[last_music_control - 0xf1];
|
|
if (target != msu_volume) {
|
|
mp->volume = msu_volume * (1.0f / 255);
|
|
mp->volume_target = target * (1.0f / 255);
|
|
mp->volume_step = kVolumeTransitionStepFloat * kVolumeTransitionStep[last_music_control - 0xf1];
|
|
}
|
|
}
|
|
|
|
if (g_msu_player.state)
|
|
zelda_apu_write(APUI00, 0xf0); // pause spc player
|
|
}
|
|
ZeldaResetApuQueue();
|
|
}
|
|
|
|
void ZeldaSaveMusicStateToRam_Locked() {
|
|
SpcPlayer_CopyVariablesToRam(g_zenv.player);
|
|
// SpcPlayer.input_ports is not saved to the SpcPlayer ram by SpcPlayer_CopyVariablesToRam,
|
|
// in any case, we want to save the most recently written data, and that might still
|
|
// be in the queue. 0x410 is a free memory location in the SPC ram, so store it there.
|
|
SpcPlayer *spc_player = g_zenv.player;
|
|
memcpy(&spc_player->ram[0x410], g_apu_write.ports, 4);
|
|
|
|
msu_volume = g_msu_player.volume * 255;
|
|
memcpy(msu_resume_info, &g_msu_player.resume_info, sizeof(g_msu_player.resume_info));
|
|
}
|
|
|
|
void ZeldaEnableMsu(uint8 enable) {
|
|
g_msu_player.volume = 1.0f;
|
|
g_msu_player.enabled = enable;
|
|
if (enable & kMsuEnabled_Opuz) {
|
|
if (g_config.audio_freq != 48000)
|
|
fprintf(stderr, "Warning: MSU Opuz requires: AudioFreq = 48000\n");
|
|
} else if (enable) {
|
|
if (g_config.audio_freq != 44100)
|
|
fprintf(stderr, "Warning: MSU requires: AudioFreq = 44100\n");
|
|
}
|
|
|
|
kVolumeTransitionStepFloat = (60 / 256.0f) / g_config.audio_freq;
|
|
}
|
|
|
|
void LoadSongBank(const uint8 *p) { // 808888
|
|
ZeldaApuLock();
|
|
SpcPlayer_Upload(g_zenv.player, p);
|
|
ZeldaApuUnlock();
|
|
}
|