8#include "absl/strings/str_format.h"
24#include "imgui/imgui.h"
37constexpr int kNativeSampleRate = 32040;
93 wasm_backend->HandleUserInteraction();
102 int safe_type = std::clamp(type, 0, 4);
103 snes_.
apu().dsp().interpolation_type =
110 return static_cast<int>(
snes_.
apu().dsp().interpolation_type);
114 const std::vector<uint8_t>& rom_data) {
120 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
121 if (env_value && std::atoi(env_value) != 0) {
152 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
154 LOG_INFO(
"Emulator",
"Audio backend initialized: %s",
161 snes_.
cpu().on_breakpoint_hit_ = [
this](uint32_t pc) ->
bool {
167 snes_.
cpu().on_instruction_executed_ =
168 [
this](uint32_t address, uint8_t opcode,
169 const std::vector<uint8_t>& operands,
const std::string& mnemonic,
170 const std::string& operand_str) {
172 mnemonic, operand_str);
201 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
204 LOG_INFO(
"Emulator",
"Audio backend initialized for headless mode");
206 LOG_INFO(
"Emulator",
"Using external (shared) audio backend");
217 const double frame_rate =
218 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
224 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
231 LOG_INFO(
"Emulator",
"SNES initialized for headless mode");
237 const double frame_rate =
238 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
241 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
262 audio_backend_->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
265 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate, 2);
272 uint64_t current_count = SDL_GetPerformanceCounter();
286 int frames_processed = 0;
287 constexpr int kMaxFramesPerUpdate = 2;
290 static int16_t native_audio_buffer[2048];
293 frames_processed < kMaxFramesPerUpdate) {
306 const uint32_t max_buffer =
static_cast<uint32_t
>(
wanted_samples_ * 6);
308 if (status.queued_frames < max_buffer) {
314 static int log_counter = 0;
315 if (++log_counter % 60 == 0) {
318 "Resampling failed (Native=%d, Backend=%d) - Dropping "
319 "audio to prevent speedup/pitch shift",
320 kNativeSampleRate, backend_rate);
349 static int entry_count = 0;
350 if (entry_count < 5 || entry_count % 300 == 0) {
352 "RunAudioFrame ENTRY #%d: init=%d, running=%d, backend=%p "
353 "(external=%p, owned=%p)",
355 static_cast<void*
>(backend),
362 static int skip_count = 0;
363 if (skip_count < 5) {
364 LOG_WARN(
"Emulator",
"RunAudioFrame SKIPPED: init=%d, running=%d",
374 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
386 static int16_t audio_buffer[2048];
389 bool queued = backend->QueueSamplesNative(audio_buffer,
wanted_samples_, 2,
393 static int frame_log_count = 0;
394 if (frame_log_count < 5 || frame_log_count % 300 == 0) {
395 LOG_INFO(
"Emulator",
"RunAudioFrame: wanted=%d, queued=%s, stream=%s",
401 if (!queued && backend->SupportsAudioStream()) {
404 "RunAudioFrame: First queue failed, re-enabling resampling");
405 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
409 LOG_INFO(
"Emulator",
"RunAudioFrame: Retry queued=%s",
410 queued ?
"YES" :
"NO");
415 "RunAudioFrame: AUDIO DROPPED - resampling not working!");
422 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
423 if (env_value && std::atoi(env_value) != 0) {
431 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
432 "Emulator renderer not initialized");
455 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
457 LOG_INFO(
"Emulator",
"Audio backend initialized (lazy): %s",
468 LOG_ERROR(
"Emulator",
"Failed to initialize input manager");
471 LOG_INFO(
"Emulator",
"Input manager initialized: %s",
485 512, 480, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING);
487 printf(
"Failed to create PPU texture: %s\n", SDL_GetError());
506 const double frame_rate =
507 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
512 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
531 static bool was_running_before_resize =
false;
535 was_running_before_resize =
true;
538 was_running_before_resize) {
541 was_running_before_resize =
false;
553 uint64_t current_count = SDL_GetPerformanceCounter();
565 int frames_to_process = 0;
576 if (frames_to_process > max_frames) {
580 frames_to_process = max_frames;
585 constexpr int kTurboFrames = 8;
586 for (
int i = 0; i < kTurboFrames; i++) {
595 frames_to_process = 1;
601 for (
int i = 0; i < frames_to_process; i++) {
603 uint64_t frame_start = SDL_GetPerformanceCounter();
604 bool should_render = (i == frames_to_process - 1);
605 uint32_t queued_frames = 0;
606 float audio_rms_left = 0.0f;
607 float audio_rms_right = 0.0f;
622 int16_t temp_audio_buffer[2048];
623 int16_t* frame_buffer =
631 "Enabling audio stream resampling (32040Hz -> Device Rate)");
636 LOG_INFO(
"Emulator",
"Disabling audio stream resampling");
637 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate,
645 queued_frames = audio_status.queued_frames;
648 const uint32_t max_buffer = samples_per_frame * 6;
649 const uint32_t optimal_buffer = 2048;
651 if (queued_frames < max_buffer) {
658 auto compute_rms = [&](
int total_samples) {
659 if (total_samples <= 0 || frame_buffer ==
nullptr) {
660 audio_rms_left = 0.0f;
661 audio_rms_right = 0.0f;
666 const int frames = total_samples / 2;
667 for (
int s = 0; s < frames; ++s) {
668 const float l =
static_cast<float>(frame_buffer[2 * s]);
669 const float r =
static_cast<float>(frame_buffer[2 * s + 1]);
674 frames > 0 ? std::sqrt(sum_l / frames) / 32768.0f : 0.0f;
676 frames > 0 ? std::sqrt(sum_r / frames) / 32768.0f : 0.0f;
678 compute_rms(num_samples);
682 int effective_rate = kNativeSampleRate;
683 if (queued_frames > optimal_buffer + 256) {
684 effective_rate += 60;
685 }
else if (queued_frames < optimal_buffer - 256) {
686 effective_rate -= 60;
703 static int error_count = 0;
704 if (++error_count % 300 == 0) {
707 "Resampling failed, dropping audio to prevent 1.5x speed "
714 static int overflow_count = 0;
715 if (++overflow_count % 60 == 0) {
717 "Audio buffer overflow (count: %d, queued: %u)",
718 overflow_count, queued_frames);
736 const uint64_t frame_end = SDL_GetPerformanceCounter();
737 const double elapsed_ms =
738 1000.0 * (
static_cast<double>(frame_end - frame_start) /
742 audio_rms_left, audio_rms_right);
753#ifndef __EMSCRIPTEN__
771 uint64_t dma_bytes, uint64_t vram_bytes,
772 float audio_rms_left,
float audio_rms_right) {
788 const std::array<float, Emulator::kMetricHistorySize>& data,
int head,
790 std::vector<float> out;
794 for (
int i = 0; i < count; ++i) {
796 out.push_back(data[idx]);
838 constexpr size_t kBankSize = 0x8000;
842 const size_t bank_count =
rom_data_.size() / kBankSize;
843 std::vector<float> free_bytes;
844 free_bytes.reserve(bank_count);
845 for (
size_t bank = 0; bank < bank_count; ++bank) {
846 size_t free_count = 0;
847 const size_t base = bank * kBankSize;
848 for (
size_t i = 0; i < kBankSize && (base + i) <
rom_data_.size(); ++i) {
853 free_bytes.push_back(
static_cast<float>(free_count));
885 if (cpu_visible && *cpu_visible) {
886 if (cpu_card.
Begin(cpu_visible)) {
894 if (ppu_visible && *ppu_visible) {
895 if (ppu_card.
Begin(ppu_visible)) {
902 bool* memory_visible =
904 if (memory_visible && *memory_visible) {
905 if (memory_card.
Begin(memory_visible)) {
911 bool* breakpoints_visible =
913 if (breakpoints_visible && *breakpoints_visible) {
914 if (breakpoints_card.
Begin(breakpoints_visible)) {
917 breakpoints_card.
End();
920 bool* performance_visible =
922 if (performance_visible && *performance_visible) {
923 if (performance_card.
Begin(performance_visible)) {
926 performance_card.
End();
929 bool* ai_agent_visible =
931 if (ai_agent_visible && *ai_agent_visible) {
932 if (ai_card.
Begin(ai_agent_visible)) {
938 bool* save_states_visible =
940 if (save_states_visible && *save_states_visible) {
941 if (save_states_card.
Begin(save_states_visible)) {
944 save_states_card.
End();
947 bool* keyboard_config_visible =
949 if (keyboard_config_visible && *keyboard_config_visible) {
950 if (keyboard_card.
Begin(keyboard_config_visible)) {
959 bool* virtual_controller_visible =
961 if (virtual_controller_visible && *virtual_controller_visible) {
962 if (controller_card.
Begin(virtual_controller_visible)) {
965 controller_card.
End();
968 bool* apu_debugger_visible =
970 if (apu_debugger_visible && *apu_debugger_visible) {
971 if (apu_card.
Begin(apu_debugger_visible)) {
977 bool* audio_mixer_visible =
979 if (audio_mixer_visible && *audio_mixer_visible) {
980 if (audio_card.
Begin(audio_mixer_visible)) {
986 }
catch (
const std::exception& e) {
988 ImGui::Text(
"Error loading emulator UI: %s", e.what());
989 if (ImGui::Button(
"Retry")) {
992 theme_manager.InitializeBuiltInThemes();
1025 const auto& theme = theme_manager.GetCurrentTheme();
1048 static char bp_addr[16] =
"00FFD9";
1050 ImGui::PushItemWidth(100);
1051 ImGui::InputText(
"##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr),
1052 ImGuiInputTextFlags_CharsHexadecimal |
1053 ImGuiInputTextFlags_CharsUppercase);
1054 ImGui::PopItemWidth();
1057 uint32_t addr = std::strtoul(bp_addr,
nullptr, 16);
1061 absl::StrFormat(
"BP at $%06X", addr));
1065 ImGui::BeginChild(
"##BPList", ImVec2(0, 100),
true);
1068 bool enabled = bp.enabled;
1069 if (ImGui::Checkbox(absl::StrFormat(
"##en%d", bp.id).c_str(),
1074 ImGui::Text(
"$%06X", bp.address);
1076 ImGui::TextDisabled(
"(hits: %d)", bp.hit_count);
1078 if (ImGui::SmallButton(
1088 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"CPU Status");
1089 ImGui::PushStyleColor(ImGuiCol_ChildBg,
1090 ConvertColorToImVec4(theme.child_bg));
1091 ImGui::BeginChild(
"##CpuStatus", ImVec2(0, 180),
true);
1094 if (ImGui::BeginTable(
1096 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1097 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed, 60);
1098 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1099 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed, 60);
1100 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1101 ImGui::TableHeadersRow();
1103 ImGui::TableNextRow();
1104 ImGui::TableNextColumn();
1106 ImGui::TableNextColumn();
1107 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1109 ImGui::TableNextColumn();
1111 ImGui::TableNextColumn();
1112 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1115 ImGui::TableNextRow();
1116 ImGui::TableNextColumn();
1118 ImGui::TableNextColumn();
1119 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1121 ImGui::TableNextColumn();
1123 ImGui::TableNextColumn();
1124 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1127 ImGui::TableNextRow();
1128 ImGui::TableNextColumn();
1130 ImGui::TableNextColumn();
1131 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1133 ImGui::TableNextColumn();
1135 ImGui::TableNextColumn();
1136 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1139 ImGui::TableNextRow();
1140 ImGui::TableNextColumn();
1142 ImGui::TableNextColumn();
1143 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1145 ImGui::TableNextColumn();
1147 ImGui::TableNextColumn();
1148 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1151 ImGui::TableNextRow();
1152 ImGui::TableNextColumn();
1154 ImGui::TableNextColumn();
1155 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
"0x%02X",
1157 ImGui::TableNextColumn();
1158 ImGui::Text(
"Cycle");
1159 ImGui::TableNextColumn();
1160 ImGui::TextColored(ConvertColorToImVec4(theme.info),
"%llu",
1167 ImGui::PopStyleColor();
1170 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"SPC700 Status");
1171 ImGui::PushStyleColor(ImGuiCol_ChildBg,
1172 ConvertColorToImVec4(theme.child_bg));
1173 ImGui::BeginChild(
"##SpcStatus", ImVec2(0, 150),
true);
1175 if (ImGui::BeginTable(
1177 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1178 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed, 50);
1179 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1180 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed, 50);
1181 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1182 ImGui::TableHeadersRow();
1184 ImGui::TableNextRow();
1185 ImGui::TableNextColumn();
1187 ImGui::TableNextColumn();
1188 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1190 ImGui::TableNextColumn();
1192 ImGui::TableNextColumn();
1193 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1196 ImGui::TableNextRow();
1197 ImGui::TableNextColumn();
1199 ImGui::TableNextColumn();
1200 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1202 ImGui::TableNextColumn();
1204 ImGui::TableNextColumn();
1205 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1208 ImGui::TableNextRow();
1209 ImGui::TableNextColumn();
1211 ImGui::TableNextColumn();
1212 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1214 ImGui::TableNextColumn();
1216 ImGui::TableNextColumn();
1218 ConvertColorToImVec4(theme.warning),
"0x%02X",
1225 ImGui::PopStyleColor();
1228 if (ImGui::CollapsingHeader(
"Disassembly Viewer",
1229 ImGuiTreeNodeFlags_DefaultOpen)) {
1230 uint32_t current_pc =
1232 auto& disasm =
snes_.
cpu().disassembly_viewer();
1233 if (disasm.IsAvailable()) {
1234 disasm.Render(current_pc,
snes_.
cpu().breakpoints_);
1236 ImGui::TextColored(ConvertColorToImVec4(theme.error),
1237 "Disassembly viewer unavailable.");
1240 }
catch (
const std::exception& e) {
1243 ImGui::PopStyleColor();
1247 ImGui::Text(
"CPU Debugger Error: %s", e.what());
1262 const std::vector<InstructionEntry>& instruction_log) {
1270 const auto& theme = theme_manager.GetCurrentTheme();
1272 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
1274 ImGui::TextWrapped(
"Save state functionality will be implemented here.");
1299 if (ImGui::SliderFloat(
"Master Volume", &volume, 0.0f, 1.0f,
"%.2f")) {
1304 ImGui::Text(
"Channel Mutes (Debug)");
1308 if (ImGui::BeginTable(
"AudioChannels", 4)) {
1309 for (
int i = 0; i < 8; ++i) {
1310 ImGui::TableNextColumn();
1311 bool mute = dsp.GetChannelMute(i);
1312 std::string label =
"Ch " + std::to_string(i + 1);
1313 if (ImGui::Checkbox(label.c_str(), &mute)) {
1314 dsp.SetChannelMute(i, mute);
1321 if (ImGui::Button(
"Mute All")) {
1322 for (
int i = 0; i < 8; ++i)
1323 dsp.SetChannelMute(i,
true);
1326 if (ImGui::Button(
"Unmute All")) {
1327 for (
int i = 0; i < 8; ++i)
1328 dsp.SetChannelMute(i,
false);
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
const auto & vector() const
bool * GetVisibilityFlag(size_t session_id, const std::string &base_card_id)
bool ShouldBreakOnExecute(uint32_t pc, CpuType cpu)
Check if execution should break at this address.
void RemoveBreakpoint(uint32_t id)
Remove a breakpoint by ID.
void SetEnabled(uint32_t id, bool enabled)
Enable or disable a breakpoint.
std::vector< Breakpoint > GetAllBreakpoints() const
Get all breakpoints.
uint32_t AddBreakpoint(uint32_t address, Type type, CpuType cpu, const std::string &condition="", const std::string &description="")
Add a new breakpoint.
std::array< float, kMetricHistorySize > audio_queue_history_
gfx::IRenderer * renderer()
void Initialize(gfx::IRenderer *renderer, const std::vector< uint8_t > &rom_data)
void RenderModernCpuDebugger()
std::unique_ptr< audio::IAudioBackend > audio_backend_
std::vector< float > FrameTimeHistory() const
void SetInputConfig(const input::InputConfig &config)
std::array< float, kMetricHistorySize > vram_bytes_history_
int metric_history_count_
bool audio_stream_env_checked_
static constexpr int kMetricHistorySize
void set_use_sdl_audio_stream(bool enabled)
std::vector< float > RomBankFreeBytes() const
std::array< float, kMetricHistorySize > audio_rms_left_history_
std::vector< float > AudioRmsRightHistory() const
void RenderMemoryViewer()
audio::IAudioBackend * external_audio_backend_
bool audio_stream_config_dirty_
std::array< float, kMetricHistorySize > frame_time_history_
void set_interpolation_type(int type)
bool use_sdl_audio_stream_
std::vector< float > VramBytesHistory() const
std::array< float, kMetricHistorySize > fps_history_
std::vector< float > AudioQueueHistory() const
void RenderKeyboardConfig()
std::vector< float > DmaBytesHistory() const
debug::DisassemblyViewer disassembly_viewer_
std::function< void(const input::InputConfig &) input_config_changed_callback_)
void PushFrameMetrics(float frame_ms, uint32_t audio_frames, uint64_t dma_bytes, uint64_t vram_bytes, float audio_rms_left, float audio_rms_right)
std::vector< uint8_t > rom_data_
bool audio_stream_active_
void RenderPerformanceMonitor()
bool EnsureInitialized(Rom *rom)
void RenderAIAgentPanel()
input::InputConfig input_config_
void RenderBreakpointList()
BreakpointManager breakpoint_manager_
editor::PanelManager * panel_manager_
std::vector< float > FpsHistory() const
input::InputManager input_manager_
std::array< float, kMetricHistorySize > audio_rms_right_history_
void RenderCpuInstructionLog(const std::vector< InstructionEntry > &instructionLog)
std::vector< float > AudioRmsLeftHistory() const
audio::IAudioBackend * audio_backend()
std::array< float, kMetricHistorySize > dma_bytes_history_
void RenderEmulatorInterface()
gfx::IRenderer * renderer_
int get_interpolation_type() const
auto mutable_cycles() -> uint64_t &
void SetSamples(int16_t *sample_data, int wanted_samples)
void Reset(bool hard=false)
uint64_t vram_bytes_frame() const
void Init(const std::vector< uint8_t > &rom_data)
uint64_t dma_bytes_frame() const
auto memory() -> MemoryImpl &
void SetPixels(uint8_t *pixel_data)
static std::unique_ptr< IAudioBackend > Create(BackendType type)
void RecordInstruction(uint32_t address, uint8_t opcode, const std::vector< uint8_t > &operands, const std::string &mnemonic, const std::string &operand_str)
Record an instruction execution.
Defines an abstract interface for all rendering operations.
virtual void UnlockTexture(TextureHandle texture)=0
virtual bool LockTexture(TextureHandle texture, SDL_Rect *rect, void **pixels, int *pitch)=0
virtual TextureHandle CreateTextureWithFormat(int width, int height, uint32_t format, int access)=0
Creates a new texture with a specific pixel format.
Draggable, dockable panel for editor sub-windows.
bool Begin(bool *p_open=nullptr)
void SetDefaultSize(float width, float height)
static ThemeManager & Get()
#define ICON_MD_PLAY_ARROW
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_BUG_REPORT
#define ICON_MD_MUSIC_NOTE
#define ICON_MD_SKIP_NEXT
#define ICON_MD_SPORTS_ESPORTS
#define ICON_MD_AUDIO_FILE
#define ICON_MD_SMART_TOY
#define LOG_ERROR(category, format,...)
#define LOG_WARN(category, format,...)
#define LOG_INFO(category, format,...)
bool g_window_is_resizing
constexpr double kNtscFrameRate
constexpr double kSpeedCalibration
constexpr double kPalFrameRate
constexpr int kMusicEditorSampleRate
std::vector< float > CopyHistoryOrdered(const std::array< float, Emulator::kMetricHistorySize > &data, int head, int count)
void RenderKeyboardConfig(input::InputManager *manager, const std::function< void(const input::InputConfig &)> &on_config_changed)
Render keyboard configuration UI.
void RenderPerformanceMonitor(Emulator *emu)
Performance metrics (FPS, frame time, audio status)
void RenderAIAgentPanel(Emulator *emu)
AI Agent panel for automated testing/gameplay.
void RenderSnesPpu(Emulator *emu)
SNES PPU output display.
void RenderNavBar(Emulator *emu)
Navigation bar with play/pause, step, reset controls.
void RenderBreakpointList(Emulator *emu)
Breakpoint list and management.
void RenderApuDebugger(Emulator *emu)
APU/Audio debugger with handshake tracker.
void RenderMemoryViewer(Emulator *emu)
Memory viewer/editor.
void RenderCpuInstructionLog(Emulator *emu, uint32_t log_size)
CPU instruction log (legacy, prefer DisassemblyViewer)
void RenderVirtualController(Emulator *emu)
Virtual SNES controller for testing input without keyboard Useful for debugging input issues - bypass...