8#include "absl/strings/str_format.h"
25#include "imgui/imgui.h"
38constexpr int kNativeSampleRate = 32040;
94 wasm_backend->HandleUserInteraction();
103 int safe_type = std::clamp(type, 0, 4);
104 snes_.
apu().dsp().interpolation_type =
111 return static_cast<int>(
snes_.
apu().dsp().interpolation_type);
115 const std::vector<uint8_t>& rom_data) {
121 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
122 if (env_value && std::atoi(env_value) != 0) {
153 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
155 LOG_INFO(
"Emulator",
"Audio backend initialized: %s",
162 snes_.
cpu().on_breakpoint_hit_ = [
this](uint32_t pc) ->
bool {
168 snes_.
cpu().on_instruction_executed_ =
169 [
this](uint32_t address, uint8_t opcode,
170 const std::vector<uint8_t>& operands,
const std::string& mnemonic,
171 const std::string& operand_str) {
173 mnemonic, operand_str);
187 LOG_INFO(
"Emulator",
"Using external (shared) audio backend");
203 if (!backend->Initialize(config)) {
205 "Failed to initialize audio backend; falling back to Null");
208 if (!backend->Initialize(config)) {
209 LOG_ERROR(
"Emulator",
"Failed to initialize Null audio backend");
216 LOG_INFO(
"Emulator",
"Audio backend initialized for headless mode: %s",
228 const double frame_rate =
229 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
235 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
242 LOG_INFO(
"Emulator",
"SNES initialized for headless mode");
248 const double frame_rate =
249 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
252 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
273 audio_backend_->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
276 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate, 2);
283 uint64_t current_count = SDL_GetPerformanceCounter();
297 int frames_processed = 0;
298 constexpr int kMaxFramesPerUpdate = 2;
301 static int16_t native_audio_buffer[2048];
304 frames_processed < kMaxFramesPerUpdate) {
317 const uint32_t max_buffer =
static_cast<uint32_t
>(
wanted_samples_ * 6);
319 if (status.queued_frames < max_buffer) {
325 static int log_counter = 0;
326 if (++log_counter % 60 == 0) {
329 "Resampling failed (Native=%d, Backend=%d) - Dropping "
330 "audio to prevent speedup/pitch shift",
331 kNativeSampleRate, backend_rate);
360 static int entry_count = 0;
361 if (entry_count < 5 || entry_count % 300 == 0) {
363 "RunAudioFrame ENTRY #%d: init=%d, running=%d, backend=%p "
364 "(external=%p, owned=%p)",
366 static_cast<void*
>(backend),
373 static int skip_count = 0;
374 if (skip_count < 5) {
375 LOG_WARN(
"Emulator",
"RunAudioFrame SKIPPED: init=%d, running=%d",
385 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
397 static int16_t audio_buffer[2048];
400 bool queued = backend->QueueSamplesNative(audio_buffer,
wanted_samples_, 2,
404 static int frame_log_count = 0;
405 if (frame_log_count < 5 || frame_log_count % 300 == 0) {
406 LOG_INFO(
"Emulator",
"RunAudioFrame: wanted=%d, queued=%s, stream=%s",
412 if (!queued && backend->SupportsAudioStream()) {
415 "RunAudioFrame: First queue failed, re-enabling resampling");
416 backend->SetAudioStreamResampling(
true, kNativeSampleRate, 2);
420 LOG_INFO(
"Emulator",
"RunAudioFrame: Retry queued=%s",
421 queued ?
"YES" :
"NO");
426 "RunAudioFrame: AUDIO DROPPED - resampling not working!");
433 const char* env_value = std::getenv(
"YAZE_USE_SDL_AUDIO_STREAM");
434 if (env_value && std::atoi(env_value) != 0) {
442 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
443 "Emulator renderer not initialized");
466 LOG_ERROR(
"Emulator",
"Failed to initialize audio backend");
468 LOG_INFO(
"Emulator",
"Audio backend initialized (lazy): %s",
479 LOG_ERROR(
"Emulator",
"Failed to initialize input manager");
482 LOG_INFO(
"Emulator",
"Input manager initialized: %s",
496 512, 480, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING);
498 printf(
"Failed to create PPU texture: %s\n", SDL_GetError());
517 const double frame_rate =
518 snes_.
memory().pal_timing() ? kPalFrameRate : kNtscFrameRate;
523 static_cast<int>(std::lround(kNativeSampleRate / frame_rate));
542 static bool was_running_before_resize =
false;
546 was_running_before_resize =
true;
549 was_running_before_resize) {
552 was_running_before_resize =
false;
564 uint64_t current_count = SDL_GetPerformanceCounter();
576 int frames_to_process = 0;
587 if (frames_to_process > max_frames) {
591 frames_to_process = max_frames;
596 constexpr int kTurboFrames = 8;
597 for (
int i = 0; i < kTurboFrames; i++) {
606 frames_to_process = 1;
612 for (
int i = 0; i < frames_to_process; i++) {
614 uint64_t frame_start = SDL_GetPerformanceCounter();
615 bool should_render = (i == frames_to_process - 1);
616 uint32_t queued_frames = 0;
617 float audio_rms_left = 0.0f;
618 float audio_rms_right = 0.0f;
633 int16_t temp_audio_buffer[2048];
634 int16_t* frame_buffer =
642 "Enabling audio stream resampling (32040Hz -> Device Rate)");
647 LOG_INFO(
"Emulator",
"Disabling audio stream resampling");
648 audio_backend_->SetAudioStreamResampling(
false, kNativeSampleRate,
656 queued_frames = audio_status.queued_frames;
659 const uint32_t max_buffer = samples_per_frame * 6;
660 const uint32_t optimal_buffer = 2048;
662 if (queued_frames < max_buffer) {
669 auto compute_rms = [&](
int total_samples) {
670 if (total_samples <= 0 || frame_buffer ==
nullptr) {
671 audio_rms_left = 0.0f;
672 audio_rms_right = 0.0f;
677 const int frames = total_samples / 2;
678 for (
int s = 0; s < frames; ++s) {
679 const float l =
static_cast<float>(frame_buffer[2 * s]);
680 const float r =
static_cast<float>(frame_buffer[2 * s + 1]);
685 frames > 0 ? std::sqrt(sum_l / frames) / 32768.0f : 0.0f;
687 frames > 0 ? std::sqrt(sum_r / frames) / 32768.0f : 0.0f;
689 compute_rms(num_samples);
693 int effective_rate = kNativeSampleRate;
694 if (queued_frames > optimal_buffer + 256) {
695 effective_rate += 60;
696 }
else if (queued_frames < optimal_buffer - 256) {
697 effective_rate -= 60;
714 static int error_count = 0;
715 if (++error_count % 300 == 0) {
718 "Resampling failed, dropping audio to prevent 1.5x speed "
725 static int overflow_count = 0;
726 if (++overflow_count % 60 == 0) {
728 "Audio buffer overflow (count: %d, queued: %u)",
729 overflow_count, queued_frames);
747 const uint64_t frame_end = SDL_GetPerformanceCounter();
748 const double elapsed_ms =
749 1000.0 * (
static_cast<double>(frame_end - frame_start) /
753 audio_rms_left, audio_rms_right);
764#ifndef __EMSCRIPTEN__
782 uint64_t dma_bytes, uint64_t vram_bytes,
783 float audio_rms_left,
float audio_rms_right) {
799 const std::array<float, Emulator::kMetricHistorySize>& data,
int head,
801 std::vector<float> out;
805 for (
int i = 0; i < count; ++i) {
807 out.push_back(data[idx]);
849 constexpr size_t kBankSize = 0x8000;
853 const size_t bank_count =
rom_data_.size() / kBankSize;
854 std::vector<float> free_bytes;
855 free_bytes.reserve(bank_count);
856 for (
size_t bank = 0; bank < bank_count; ++bank) {
857 size_t free_count = 0;
858 const size_t base = bank * kBankSize;
859 for (
size_t i = 0; i < kBankSize && (base + i) <
rom_data_.size(); ++i) {
864 free_bytes.push_back(
static_cast<float>(free_count));
896 if (cpu_visible && *cpu_visible) {
897 if (cpu_card.
Begin(cpu_visible)) {
905 if (ppu_visible && *ppu_visible) {
906 if (ppu_card.
Begin(ppu_visible)) {
913 bool* memory_visible =
915 if (memory_visible && *memory_visible) {
916 if (memory_card.
Begin(memory_visible)) {
922 bool* breakpoints_visible =
924 if (breakpoints_visible && *breakpoints_visible) {
925 if (breakpoints_card.
Begin(breakpoints_visible)) {
928 breakpoints_card.
End();
931 bool* performance_visible =
933 if (performance_visible && *performance_visible) {
934 if (performance_card.
Begin(performance_visible)) {
937 performance_card.
End();
940 bool* ai_agent_visible =
942 if (ai_agent_visible && *ai_agent_visible) {
943 if (ai_card.
Begin(ai_agent_visible)) {
949 bool* save_states_visible =
951 if (save_states_visible && *save_states_visible) {
952 if (save_states_card.
Begin(save_states_visible)) {
955 save_states_card.
End();
958 bool* keyboard_config_visible =
960 if (keyboard_config_visible && *keyboard_config_visible) {
961 if (keyboard_card.
Begin(keyboard_config_visible)) {
970 bool* virtual_controller_visible =
972 if (virtual_controller_visible && *virtual_controller_visible) {
973 if (controller_card.
Begin(virtual_controller_visible)) {
976 controller_card.
End();
979 bool* apu_debugger_visible =
981 if (apu_debugger_visible && *apu_debugger_visible) {
982 if (apu_card.
Begin(apu_debugger_visible)) {
988 bool* audio_mixer_visible =
990 if (audio_mixer_visible && *audio_mixer_visible) {
991 if (audio_card.
Begin(audio_mixer_visible)) {
997 }
catch (
const std::exception& e) {
999 ImGui::Text(
"Error loading emulator UI: %s", e.what());
1000 if (ImGui::Button(
"Retry")) {
1003 theme_manager.InitializeBuiltInThemes();
1036 const auto& theme = theme_manager.GetCurrentTheme();
1059 static char bp_addr[16] =
"00FFD9";
1061 ImGui::PushItemWidth(100);
1062 ImGui::InputText(
"##BPAddr", bp_addr, IM_ARRAYSIZE(bp_addr),
1063 ImGuiInputTextFlags_CharsHexadecimal |
1064 ImGuiInputTextFlags_CharsUppercase);
1065 ImGui::PopItemWidth();
1068 uint32_t addr = std::strtoul(bp_addr,
nullptr, 16);
1072 absl::StrFormat(
"BP at $%06X", addr));
1076 ImGui::BeginChild(
"##BPList", ImVec2(0, 100),
true);
1079 bool enabled = bp.enabled;
1080 if (ImGui::Checkbox(absl::StrFormat(
"##en%d", bp.id).c_str(),
1085 ImGui::Text(
"$%06X", bp.address);
1087 ImGui::TextDisabled(
"(hits: %d)", bp.hit_count);
1089 if (ImGui::SmallButton(
1099 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"CPU Status");
1102 "##CpuStatus", ImVec2(0, 180),
1103 {.bg = ConvertColorToImVec4(theme.child_bg)},
true);
1106 if (ImGui::BeginTable(
1108 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1109 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1111 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1112 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1114 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 80);
1115 ImGui::TableHeadersRow();
1117 ImGui::TableNextRow();
1118 ImGui::TableNextColumn();
1120 ImGui::TableNextColumn();
1121 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1123 ImGui::TableNextColumn();
1125 ImGui::TableNextColumn();
1126 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1129 ImGui::TableNextRow();
1130 ImGui::TableNextColumn();
1132 ImGui::TableNextColumn();
1133 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1135 ImGui::TableNextColumn();
1137 ImGui::TableNextColumn();
1138 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1141 ImGui::TableNextRow();
1142 ImGui::TableNextColumn();
1144 ImGui::TableNextColumn();
1145 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%04X",
1147 ImGui::TableNextColumn();
1149 ImGui::TableNextColumn();
1150 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1153 ImGui::TableNextRow();
1154 ImGui::TableNextColumn();
1156 ImGui::TableNextColumn();
1157 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1159 ImGui::TableNextColumn();
1161 ImGui::TableNextColumn();
1162 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1165 ImGui::TableNextRow();
1166 ImGui::TableNextColumn();
1168 ImGui::TableNextColumn();
1169 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
"0x%02X",
1171 ImGui::TableNextColumn();
1172 ImGui::Text(
"Cycle");
1173 ImGui::TableNextColumn();
1174 ImGui::TextColored(ConvertColorToImVec4(theme.info),
"%llu",
1182 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"SPC700 Status");
1185 "##SpcStatus", ImVec2(0, 150),
1186 {.bg = ConvertColorToImVec4(theme.child_bg)},
true);
1188 if (ImGui::BeginTable(
1190 ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit)) {
1191 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1193 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1194 ImGui::TableSetupColumn(
"Register", ImGuiTableColumnFlags_WidthFixed,
1196 ImGui::TableSetupColumn(
"Value", ImGuiTableColumnFlags_WidthFixed, 60);
1197 ImGui::TableHeadersRow();
1199 ImGui::TableNextRow();
1200 ImGui::TableNextColumn();
1202 ImGui::TableNextColumn();
1203 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1205 ImGui::TableNextColumn();
1207 ImGui::TableNextColumn();
1208 ImGui::TextColored(ConvertColorToImVec4(theme.success),
"0x%04X",
1211 ImGui::TableNextRow();
1212 ImGui::TableNextColumn();
1214 ImGui::TableNextColumn();
1215 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1217 ImGui::TableNextColumn();
1219 ImGui::TableNextColumn();
1220 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1223 ImGui::TableNextRow();
1224 ImGui::TableNextColumn();
1226 ImGui::TableNextColumn();
1227 ImGui::TextColored(ConvertColorToImVec4(theme.accent),
"0x%02X",
1229 ImGui::TableNextColumn();
1231 ImGui::TableNextColumn();
1233 ConvertColorToImVec4(theme.warning),
"0x%02X",
1241 if (ImGui::CollapsingHeader(
"Disassembly Viewer",
1242 ImGuiTreeNodeFlags_DefaultOpen)) {
1243 uint32_t current_pc =
1245 auto& disasm =
snes_.
cpu().disassembly_viewer();
1246 if (disasm.IsAvailable()) {
1247 disasm.Render(current_pc,
snes_.
cpu().breakpoints_);
1249 ImGui::TextColored(ConvertColorToImVec4(theme.error),
1250 "Disassembly viewer unavailable.");
1253 }
catch (
const std::exception& e) {
1255 ImGui::Text(
"CPU Debugger Error: %s", e.what());
1270 const std::vector<InstructionEntry>& instruction_log) {
1278 const auto& theme = theme_manager.GetCurrentTheme();
1280 ImGui::TextColored(ConvertColorToImVec4(theme.warning),
1282 ImGui::TextWrapped(
"Save state functionality will be implemented here.");
1307 if (ImGui::SliderFloat(
"Master Volume", &volume, 0.0f, 1.0f,
"%.2f")) {
1312 ImGui::Text(
"Channel Mutes (Debug)");
1316 if (ImGui::BeginTable(
"AudioChannels", 4)) {
1317 for (
int i = 0; i < 8; ++i) {
1318 ImGui::TableNextColumn();
1319 bool mute = dsp.GetChannelMute(i);
1320 std::string label =
"Ch " + std::to_string(i + 1);
1321 if (ImGui::Checkbox(label.c_str(), &mute)) {
1322 dsp.SetChannelMute(i, mute);
1329 if (ImGui::Button(
"Mute All")) {
1330 for (
int i = 0; i < 8; ++i)
1331 dsp.SetChannelMute(i,
true);
1334 if (ImGui::Button(
"Unmute All")) {
1335 for (
int i = 0; i < 8; ++i)
1336 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)
RAII guard for ImGui child windows with optional styling.
static ThemeManager & Get()
#define ICON_MD_PLAY_ARROW
#define ICON_MD_VIDEOGAME_ASSET
#define ICON_MD_BUG_REPORT
#define ICON_MD_AUDIOTRACK
#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...