21 : music_bank_(music_bank) {}
48 if (
mode_ == new_mode)
return;
60 if (is_active && !was_active) {
61 LOG_INFO(
"MusicPlayer",
"Requesting exclusive audio control");
63 }
else if (!is_active && was_active) {
64 LOG_INFO(
"MusicPlayer",
"Releasing exclusive audio control");
69 LOG_DEBUG(
"MusicPlayer",
"State transition: %d -> %d",
70 static_cast<int>(old_mode),
static_cast<int>(new_mode));
75 LOG_ERROR(
"MusicPlayer",
"PrepareAudioPlayback: No emulator");
81 LOG_ERROR(
"MusicPlayer",
"PrepareAudioPlayback: No audio backend");
86 auto config = audio->GetConfig();
87 LOG_INFO(
"MusicPlayer",
"PrepareAudioPlayback: backend=%s, device_rate=%dHz, "
88 "resampling=%s, native_rate=%dHz",
89 audio->GetBackendName().c_str(), config.sample_rate,
90 audio->IsAudioStreamEnabled() ?
"ENABLED" :
"DISABLED",
95 dsp.ResetSampleBuffer();
104 constexpr int kInitialSamples = 533;
105 static int16_t prime_buffer[2048];
106 std::memset(prime_buffer, 0,
sizeof(prime_buffer));
109 bool queued = audio->QueueSamplesNative(prime_buffer, kInitialSamples, 2,
kNativeSampleRate);
112 LOG_INFO(
"MusicPlayer",
"PrepareAudioPlayback: queued=%s, samples=%d, "
113 "resampling_after=%s",
114 queued ?
"YES" :
"NO", kInitialSamples,
115 audio->IsAudioStreamEnabled() ?
"ENABLED" :
"DISABLED");
118 LOG_ERROR(
"MusicPlayer",
"PrepareAudioPlayback: CRITICAL - Failed to queue samples! "
119 "Audio will not play correctly.");
151 static int update_count = 0;
152 static bool first_update =
true;
153 if (first_update || update_count % 300 == 0) {
154 LOG_INFO(
"MusicPlayer",
"Update() #%d: emu=%p, init=%d, running=%d, focus=%d",
160 first_update =
false;
171 if (!audio->IsAudioStreamEnabled()) {
172 LOG_ERROR(
"MusicPlayer",
"AUDIO STREAM DISABLED during playback! Re-enabling...");
178 auto now = std::chrono::steady_clock::now();
179 auto elapsed = std::chrono::duration<double>(now -
last_frame_time_).count();
183 if (frame_time <= 0.0) {
184 frame_time = 1.0 / 60.0988;
187 if (elapsed >= frame_time) {
189 static int speed_log_counter = 0;
190 if (++speed_log_counter % 60 == 0) {
191 double current_fps = 1.0 / elapsed;
192 LOG_INFO(
"MusicPlayer",
"Playback Speed: %.2f FPS (Target: %.2f), FrameTime: %.4fms, Wanted: %.4fms",
193 current_fps, 1.0/frame_time, elapsed*1000.0, frame_time*1000.0);
199 static int frame_exec_count = 0;
200 if (frame_exec_count < 5 || frame_exec_count % 300 == 0) {
201 LOG_INFO(
"MusicPlayer",
"Executing frame #%d: focus_mode=%d",
215 static int debug_counter = 0;
216 static uint64_t last_apu_cycles = 0;
217 static auto last_log_time = std::chrono::steady_clock::now();
219 if (++debug_counter % 60 == 0) {
221 auto now_log = std::chrono::steady_clock::now();
222 auto log_elapsed = std::chrono::duration<double>(now_log - last_log_time).count();
224 uint64_t cycle_delta = apu_cycles - last_apu_cycles;
225 double cycles_per_sec = cycle_delta / log_elapsed;
226 double rate_ratio = cycles_per_sec / 1024000.0;
228 LOG_INFO(
"MusicPlayer",
"APU: %llu cycles in %.2fs = %.0f/sec (%.2fx expected)",
229 cycle_delta, log_elapsed, cycles_per_sec, rate_ratio);
231 last_apu_cycles = apu_cycles;
232 last_log_time = now_log;
235 auto status = audio->GetStatus();
236 LOG_DEBUG(
"MusicPlayer",
"Audio: playing=%d queued=%u bytes=%u",
237 status.is_playing, status.queued_frames, status.queued_bytes);
277 return rom_ !=
nullptr;
282 LOG_WARN(
"MusicPlayer",
"EnsureAudioReady: No ROM loaded");
287 LOG_ERROR(
"MusicPlayer",
"EnsureAudioReady: No emulator set");
292 LOG_INFO(
"MusicPlayer",
"Initializing SNES for audio playback...");
294 LOG_ERROR(
"MusicPlayer",
"Failed to initialize emulator");
301 LOG_INFO(
"MusicPlayer",
"Set use_sdl_audio_stream=true, wanted_samples=%d",
306 auto config = audio->GetConfig();
307 LOG_INFO(
"MusicPlayer",
"Audio backend: %s, config=%dHz/%dch, initialized=%d",
308 audio->GetBackendName().c_str(), config.sample_rate, config.channels,
309 audio->IsInitialized());
311 if (audio->SupportsAudioStream()) {
312 LOG_INFO(
"MusicPlayer",
"Calling SetAudioStreamResampling(%d Hz -> %d Hz)",
319 LOG_ERROR(
"MusicPlayer",
"No audio backend available!");
326 LOG_ERROR(
"MusicPlayer",
"Failed to initialize SPC");
350 LOG_INFO(
"MusicPlayer",
"Initializing direct SPC playback");
381 LOG_INFO(
"MusicPlayer",
"Initializing preview mode");
395 for (
int i = 0; i < 1000; i++) {
404 LOG_WARN(
"MusicPlayer",
"No ROM loaded - cannot play song");
420 LOG_INFO(
"MusicPlayer",
"Pre-requesting exclusive audio control for game-based playback");
436 constexpr int kPrimeFrames = 6;
437 constexpr int kPrimeSamples = 533 * kPrimeFrames;
438 std::vector<int16_t> silence(kPrimeSamples * 2, 0);
443 if (!audio->GetStatus().is_playing) {
449 emulator_->
snes().Write(0x7E012C,
static_cast<uint8_t
>(song_index + 1));
481 LOG_INFO(
"MusicPlayer",
"Requesting exclusive audio control (backend ready)");
485 int song_index = song_id - 1;
488 if (
music_bank_ && song_index >= 0 && song_index < music_bank_->GetSongCount()) {
499 uint8_t song_bank = song->
bank;
500 bool is_expanded = (song_bank == 3 || song_bank == 4);
502 LOG_INFO(
"MusicPlayer",
"Playing song %d (%s) from song_bank=%d",
503 song_id, song->
name.c_str(), song_bank);
508 uint8_t rom_bank = song_bank + 1;
524 uint8_t spc_song_index;
526 int vanilla_count = 34;
527 int expanded_index = (song_id - 1) - vanilla_count;
528 spc_song_index =
static_cast<uint8_t
>(expanded_index + 1);
530 spc_song_index =
static_cast<uint8_t
>(song_id);
534 static uint8_t trigger_byte = 0x00;
535 trigger_byte ^= 0x01;
537 apu.in_ports_[0] = spc_song_index;
538 apu.in_ports_[1] = trigger_byte;
554 dsp.ResetSampleBuffer();
565 constexpr int kInitialSamples = 533;
566 static int16_t prime_buffer[2048];
567 std::memset(prime_buffer, 0,
sizeof(prime_buffer));
570 bool queued = audio->QueueSamplesNative(prime_buffer, kInitialSamples, 2,
kNativeSampleRate);
571 LOG_INFO(
"MusicPlayer",
"Initial samples queued: %s", queued ?
"YES" :
"NO (RESAMPLING FAILED!)");
578 constexpr int kPrimeFrames = 6;
579 constexpr int kPrimeSamples = 533 * kPrimeFrames;
580 std::vector<int16_t> silence(kPrimeSamples * 2, 0);
583 constexpr int kNativeSampleRate2 = 32040;
584 bool silenceQueued = audio->QueueSamplesNative(silence.data(), kPrimeSamples, 2, kNativeSampleRate2);
585 LOG_INFO(
"MusicPlayer",
"Silence buffer queued: %s", silenceQueued ?
"YES" :
"NO (RESAMPLING FAILED!)");
587 auto status = audio->GetStatus();
588 LOG_INFO(
"MusicPlayer",
"Audio status before Play(): playing=%d, queued_frames=%u",
589 status.is_playing, status.queued_frames);
595 status = audio->GetStatus();
596 LOG_INFO(
"MusicPlayer",
"Audio status after Play(): playing=%d, queued_frames=%u",
597 status.is_playing, status.queued_frames);
618 LOG_INFO(
"MusicPlayer",
"Started playing song %d at %.1f ticks/sec",
668 apu.in_ports_[0] = 0x00;
669 apu.in_ports_[1] = 0xFF;
692 LOG_DEBUG(
"MusicPlayer",
"Stopped playback");
721 const uint8_t* rom_data =
rom_->
data();
722 const size_t rom_size =
rom_->
size();
724 LOG_INFO(
"MusicPlayer",
"Uploading sound bank from ROM offset 0x%X", rom_offset);
727 while (rom_offset + 4 < rom_size) {
728 uint16_t block_size = rom_data[rom_offset] | (rom_data[rom_offset + 1] << 8);
729 uint16_t aram_addr = rom_data[rom_offset + 2] | (rom_data[rom_offset + 3] << 8);
731 if (block_size == 0 || block_size > 0x10000) {
735 if (rom_offset + 4 + block_size > rom_size) {
736 LOG_WARN(
"MusicPlayer",
"Block at 0x%X extends past ROM end", rom_offset);
740 apu.WriteDma(aram_addr, &rom_data[rom_offset + 4], block_size);
742 rom_offset += 4 + block_size;
750 for (
size_t i = 0; i < data.size(); ++i) {
751 apu.ram[aram_address + i] = data[i];
764 case 0:
return song_id - 1;
765 case 1:
return song_id - 12;
766 case 2:
return song_id - 32;
772 constexpr uint8_t kDefaultTempo = 150;
773 if (song.
segments.empty())
return kDefaultTempo;
775 const auto& segment = song.
segments[0];
776 for (
const auto& track : segment.tracks) {
777 for (
const auto& event : track.events) {
780 return event.command.params[0];
784 return kDefaultTempo;
795 return 500.0f * (
static_cast<float>(tempo) / 256.0f);
803 auto now = std::chrono::steady_clock::now();
812 int segment_index,
int channel_index) {
823 if (segment_index >= 0 && segment_index <
static_cast<int>(song.
segments.size())) {
824 segment = &song.
segments[segment_index];
828 int instrument_index = -1;
829 if (segment && channel_index >= 0 && channel_index < 8) {
830 const auto& track = segment->
tracks[channel_index];
831 for (
const auto& evt : track.events) {
832 if (evt.tick > event.
tick)
break;
834 instrument_index = evt.command.params[0];
841 int ch_base = channel_index * 0x10;
842 int inst_idx = instrument ? instrument->
sample_index : 0;
844 apu.WriteToDsp(ch_base +
kDspSrcn, inst_idx);
848 apu.WriteToDsp(ch_base +
kDspPitchHigh, (pitch >> 8) & 0x3F);
850 apu.WriteToDsp(ch_base +
kDspVolL, 0x7F);
851 apu.WriteToDsp(ch_base +
kDspVolR, 0x7F);
854 apu.WriteToDsp(ch_base +
kDspAdsr1, instrument->GetADByte());
855 apu.WriteToDsp(ch_base +
kDspAdsr2, instrument->GetSRByte());
857 apu.WriteToDsp(ch_base +
kDspAdsr1, 0xFF);
858 apu.WriteToDsp(ch_base +
kDspAdsr2, 0xE0);
861 apu.WriteToDsp(
kDspKeyOn, 1 << channel_index);
875 const auto& ch = dsp.GetChannel(channel_index);
878 state.
pitch = ch.pitch;
879 state.
volume_l =
static_cast<uint8_t
>(std::abs(ch.volumeL));
880 state.
volume_r =
static_cast<uint8_t
>(std::abs(ch.volumeR));
881 state.
gain = ch.gain;
887 std::array<ChannelState, 8> states;
893 for (
int i = 0; i < 8; ++i) {
894 const auto& ch = dsp.GetChannel(i);
895 states[i].key_on = ch.keyOn;
896 states[i].sample_index = ch.srcn;
897 states[i].pitch = ch.pitch;
898 states[i].volume_l =
static_cast<uint8_t
>(std::abs(ch.volumeL));
899 states[i].volume_r =
static_cast<uint8_t
>(std::abs(ch.volumeR));
900 states[i].gain = ch.gain;
901 states[i].adsr_state = ch.adsrState;
908 if (segment_index < 0 || segment_index >=
static_cast<int>(song.
segments.size()))
return;
911 temp_song.
name =
"Preview Segment";
919 LOG_ERROR(
"MusicPlayer",
"Failed to serialize segment: %s", result.status().message().data());
928 static uint8_t trigger = 0x00;
931 apu.in_ports_[0] = 1;
932 apu.in_ports_[1] = trigger;
937 uint32_t segment_start_tick = 0;
938 for (
int i = 0; i < segment_index; ++i) {
939 segment_start_tick += song.
segments[i].GetDuration();
950 LOG_DEBUG(
"MusicPlayer",
"Previewing segment %d at tick %u", segment_index, segment_start_tick);
957 if (!instrument)
return;
961 int ch_base = ch * 0x10;
969 for(
int i=0; i<500; ++i) apu.Cycle();
971 apu.WriteToDsp(ch_base +
kDspSrcn, instrument->sample_index);
972 apu.WriteToDsp(ch_base +
kDspAdsr1, instrument->GetADByte());
973 apu.WriteToDsp(ch_base +
kDspAdsr2, instrument->GetSRByte());
974 apu.WriteToDsp(ch_base +
kDspGain, instrument->gain);
977 pitch = (
static_cast<uint32_t
>(pitch) * instrument->pitch_mult) >> 12;
980 apu.WriteToDsp(ch_base +
kDspPitchHigh, (pitch >> 8) & 0x3F);
982 apu.WriteToDsp(ch_base +
kDspVolL, 0x7F);
983 apu.WriteToDsp(ch_base +
kDspVolR, 0x7F);
997 uint16_t temp_addr = 0x8000;
1000 uint16_t loop_addr = temp_addr + sample->loop_point;
1001 std::vector<uint8_t> dir = {
1002 static_cast<uint8_t
>(temp_addr & 0xFF),
1003 static_cast<uint8_t
>(temp_addr >> 8),
1004 static_cast<uint8_t
>(loop_addr & 0xFF),
1005 static_cast<uint8_t
>(loop_addr >> 8)
1011 int ch_base = ch * 0x10;
1019 for(
int i=0; i<500; ++i) apu.Cycle();
1021 apu.WriteToDsp(ch_base +
kDspSrcn, 0x00);
1022 apu.WriteToDsp(ch_base +
kDspAdsr1, 0xFF);
1023 apu.WriteToDsp(ch_base +
kDspAdsr2, 0xE0);
1024 apu.WriteToDsp(ch_base +
kDspGain, 0x7F);
1026 uint16_t pitch = 0x1000;
1028 apu.WriteToDsp(ch_base +
kDspPitchHigh, (pitch >> 8) & 0x3F);
1030 apu.WriteToDsp(ch_base +
kDspVolL, 0x7F);
1031 apu.WriteToDsp(ch_base +
kDspVolR, 0x7F);
1045 LOG_INFO(
"MusicPlayer",
"Previewing custom song: %s", song->name.c_str());
1051 LOG_ERROR(
"MusicPlayer",
"Failed to serialize song: %s", result.status().message().data());
1064 apu.in_ports_[0] = 1;
1065 apu.in_ports_[1] = 0x00;
1086 if (!song || segment_index < 0 ||
1087 segment_index >=
static_cast<int>(song->segments.size())) {
1092 uint32_t tick_offset = 0;
1093 for (
int i = 0; i < segment_index; ++i) {
1094 tick_offset += song->
segments[i].GetDuration();
1102 const auto& segment = song->segments[segment_index];
1103 if (!segment.tracks.empty()) {
1104 for (
const auto& event : segment.tracks[0].events) {
1116 uint16_t tick)
const {
1117 if (channel_index < 0 || channel_index >= 8)
return nullptr;
1119 int instrument_index = -1;
1120 const auto& track = segment.
tracks[channel_index];
1122 for (
const auto& evt : track.events) {
1123 if (evt.tick > tick)
break;
1125 evt.command.opcode == 0xE0) {
1126 instrument_index = evt.command.params[0];
1130 if (instrument_index == -1)
return nullptr;
1147 status.
mute = dsp.IsMuted();
1148 status.
reset = dsp.IsReset();
1161 status.
cycles = apu.GetCycles();
1164 const auto& t0 = apu.GetTimer(0);
1170 const auto& t1 = apu.GetTimer(1);
1176 const auto& t2 = apu.GetTimer(2);
1182 status.
port0_in = apu.in_ports_[0];
1183 status.
port1_in = apu.in_ports_[1];
1197 auto backend_status = audio->GetStatus();
1198 status.
is_playing = backend_status.is_playing;
1203 auto config = audio->GetConfig();
1218 LOG_INFO(
"MusicPlayer",
"Audio queue cleared");
1226 dsp.ResetSampleBuffer();
1227 LOG_INFO(
"MusicPlayer",
"DSP buffer reset");
1235 LOG_INFO(
"MusicPlayer",
"Forced DSP NewFrame()");
1250 LOG_INFO(
"MusicPlayer",
"Audio system marked for reinitialization");
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
void PreviewSegment(const zelda3::music::MusicSong &song, int segment_index)
Preview a specific segment of a song.
emu::Emulator * emulator()
ApuDebugStatus GetApuStatus() const
Get APU timing diagnostic status.
void Stop()
Stop playback completely.
void PreviewCustomSong(int song_index)
Preview a custom (modified) song from memory.
void InitializeDirectSpc()
void ReinitAudio()
Reinitialize the audio system.
float CalculateTicksPerSecond(uint8_t tempo) const
void SetPlaybackSpeed(float speed)
Set the playback speed (0.25x to 2.0x).
uint32_t playback_start_tick_
std::function< void(bool)> audio_exclusivity_callback_
void ForceNewFrame()
Force a DSP NewFrame() call.
ChannelState GetChannelState(int channel_index) const
int GetSongIndexInBank(int song_id, uint8_t bank) const
uint8_t current_spc_bank_
void SetInterpolationType(int type)
Set the DSP interpolation type for audio quality.
void SetVolume(float volume)
Set the master volume (0.0 to 1.0).
uint8_t GetSongTempo(const zelda3::music::MusicSong &song) const
std::chrono::steady_clock::time_point playback_start_time_
bool preview_initialized_
void Update()
Call once per frame to update playback state.
void UploadSoundBankFromRom(uint32_t rom_offset)
void PrepareAudioPlayback()
Prepare audio pipeline for playback.
bool EnsurePreviewReady()
void Pause()
Pause the current playback.
uint32_t GetCurrentPlaybackTick() const
AudioQueueStatus GetAudioQueueStatus() const
Get audio queue diagnostic status.
void InitializePreviewMode()
std::array< ChannelState, 8 > GetChannelStates() const
void TransitionTo(PlaybackMode new_mode)
void PlaySong(int song_index)
Start playing a song by index.
void UploadSongToAram(const std::vector< uint8_t > &data, uint16_t aram_address)
DspDebugStatus GetDspStatus() const
Get DSP buffer diagnostic status.
void SetDirectSpcMode(bool enabled)
Enable/disable direct SPC mode (bypasses game CPU).
void Resume()
Resume paused playback.
bool IsAudioReady() const
Check if the audio system is ready for playback.
uint32_t GetBankRomOffset(uint8_t bank) const
void PreviewNote(const zelda3::music::MusicSong &song, const zelda3::music::TrackEvent &event, int segment_index, int channel_index)
Preview a single note with the current instrument.
std::chrono::steady_clock::time_point last_frame_time_
void TogglePlayPause()
Toggle between play/pause states.
const zelda3::music::MusicInstrument * ResolveInstrumentForEvent(const zelda3::music::MusicSegment &segment, int channel_index, uint16_t tick) const
Resolve the instrument used at a specific tick in a track.
void SetEmulator(emu::Emulator *emulator)
Set the main emulator instance to use for playback.
PlaybackState GetState() const
zelda3::music::MusicBank * music_bank_
void ResetDspBuffer()
Reset the DSP sample buffer.
void PreviewSample(int sample_index)
Preview a raw BRR sample.
void PreviewInstrument(int instrument_index)
Preview an instrument at middle C.
emu::Emulator * emulator_
void SeekToSegment(int segment_index)
Seek to a specific segment in the current song.
void ClearAudioQueue()
Clear the audio queue (stops sound immediately).
int playback_segment_index_
void PlaySongDirect(int song_id)
MusicPlayer(zelda3::music::MusicBank *music_bank)
A class for emulating and debugging SNES games.
auto wanted_samples() const -> int
void set_use_sdl_audio_stream(bool enabled)
auto wanted_frames() const -> float
bool is_audio_focus_mode() const
void set_interpolation_type(int type)
bool EnsureInitialized(Rom *rom)
void set_audio_focus_mode(bool focus)
void set_running(bool running)
bool is_snes_initialized() const
audio::IAudioBackend * audio_backend()
auto running() const -> bool
void mark_audio_stream_configured()
virtual void SetVolume(float volume)=0
Manages the collection of songs, instruments, and samples from a ROM.
MusicInstrument * GetInstrument(int index)
Get an instrument by index.
MusicSong * GetSong(int index)
Get a song by index.
MusicSample * GetSample(int index)
Get a sample by index.
bool HasExpandedMusicPatch() const
Check if the ROM has the Oracle of Secrets expanded music patch.
static absl::StatusOr< SerializeResult > SerializeSong(const MusicSong &song, uint16_t base_address)
Serialize a complete song to binary format.
#define LOG_DEBUG(category, format,...)
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
constexpr int kSpcResetCycles
constexpr uint8_t kDspMainVolL
constexpr uint32_t kSoundBankOffsets[]
constexpr uint8_t kDspKeyOn
constexpr uint8_t kDspSrcn
constexpr int kSpcPreviewCycles
constexpr int kSpcInitCycles
constexpr uint16_t kDriverEntryPoint
constexpr uint8_t kDspGain
constexpr uint8_t kDspMainVolR
constexpr uint8_t kDspEchoVolR
constexpr uint8_t kOpcodeTempo
constexpr uint8_t kDspAdsr1
constexpr uint8_t kDspVolL
constexpr uint8_t kDspFlg
constexpr int kNativeSampleRate
constexpr uint8_t kDspKeyOff
constexpr uint8_t kDspVolR
constexpr int kSpcStopCycles
constexpr uint8_t kDspPitchLow
constexpr uint8_t kDspEchoVolL
constexpr uint8_t kDspDir
PlaybackMode
Playback mode for the music player.
constexpr uint8_t kDspPitchHigh
constexpr uint8_t kDspAdsr2
constexpr uint16_t kSongTableAram
uint16_t LookupNSpcPitch(uint8_t note_byte)
Look up the DSP pitch value for an N-SPC note byte.
APU timing diagnostic status for debug UI.
Audio queue diagnostic status for debug UI.
Represents the state of a single DSP channel for visualization.
DSP buffer diagnostic status for debug UI.
Represents the current playback state of the music player.
int current_segment_index
An instrument definition with ADSR envelope.
A segment containing 8 parallel tracks.
std::array< MusicTrack, 8 > tracks
A complete song composed of segments.
std::vector< MusicSegment > segments
A single event in a music track (note, command, or control).