95 if (!can_play) ImGui::BeginDisabled();
98 if (state.is_playing && !state.is_paused) {
99 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.5f, 0.2f, 1.0f));
101 ImGui::PopStyleColor();
102 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Pause (Space)");
103 }
else if (state.is_paused) {
104 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.5f, 0.4f, 0.1f, 1.0f));
106 ImGui::PopStyleColor();
107 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Resume (Space)");
111 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Play (Space)");
116 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Stop (Escape)");
118 if (!can_play) ImGui::EndDisabled();
123 if (state.is_playing && !state.is_paused) {
124 float t =
static_cast<float>(ImGui::GetTime() * 3.0);
125 float alpha = 0.5f + 0.5f * std::sin(t);
128 }
else if (state.is_paused) {
129 ImGui::TextColored(ImVec4(0.9f, 0.7f, 0.2f, 1.0f),
133 ImGui::Text(
"%s", song->name.c_str());
134 if (song->modified) {
136 ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.2f, 1.0f),
ICON_MD_EDIT);
139 ImGui::TextDisabled(
"No song selected");
143 if (state.is_playing || state.is_paused) {
145 float seconds = state.ticks_per_second > 0
146 ? state.current_tick / state.ticks_per_second
148 int mins =
static_cast<int>(seconds) / 60;
149 int secs =
static_cast<int>(seconds) % 60;
150 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.8f, 1.0f),
" %d:%02d", mins,
155 float right_offset = ImGui::GetWindowWidth() - 200;
156 if (right_offset > 200) {
157 ImGui::SameLine(right_offset);
161 ImGui::SetNextItemWidth(70);
162 float speed = state.playback_speed;
167 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Playback speed (+/- keys)");
172 ImGui::SetNextItemWidth(60);
176 if (ImGui::IsItemHovered()) ImGui::SetTooltip(
"Volume");
273 if (ImGui::Button(
"Snapshot")) {
278 ImGui::TextDisabled(
"(Freeze display to read values)");
289 auto now = std::chrono::steady_clock::now();
298 auto elapsed = std::chrono::duration<double>(now -
last_stats_time_).count();
301 if (elapsed >= 0.5) {
324 ? ImVec4(0.3f, 0.9f, 0.3f, 1.0f)
325 : ImVec4(0.6f, 0.6f, 0.6f, 1.0f);
335 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
338 ImGui::TextColored(ImVec4(1.0f, 0.6f, 0.0f, 1.0f),
341 ImGui::TextColored(ImVec4(0.5f, 0.8f, 0.5f, 1.0f),
348 ImGui::Text(
"APU Rate: %.2fx expected", rate_ratio);
349 if (rate_ratio > 1.1f) {
351 ImGui::TextColored(ImVec4(1.0f, 0.3f, 0.3f, 1.0f),
352 "(APU running too fast!)");
359 if (ImGui::TreeNode(
"DSP Buffer")) {
362 ImGui::Text(
"Sample Offset: %u / 2048", dsp.sample_offset);
363 ImGui::Text(
"Frame Boundary: %u", dsp.frame_boundary);
366 float fill = dsp.sample_offset / 2048.0f;
368 snprintf(overlay,
sizeof(overlay),
"%.1f%%", fill * 100.0f);
369 ImGui::ProgressBar(fill, ImVec2(-1, 0), overlay);
372 int32_t drift =
static_cast<int32_t
>(dsp.sample_offset) -
373 static_cast<int32_t
>(dsp.frame_boundary);
374 ImVec4 drift_color = (std::abs(drift) > 100)
375 ? ImVec4(1.0f, 0.3f, 0.3f, 1.0f)
376 : ImVec4(0.5f, 0.8f, 0.5f, 1.0f);
377 ImGui::TextColored(drift_color,
"Drift: %+d samples", drift);
379 ImGui::Text(
"Master Vol: L=%d R=%d", dsp.master_vol_l, dsp.master_vol_r);
390 if (dsp.echo_enabled) {
391 ImGui::TextColored(ImVec4(0.5f, 0.8f, 1.0f, 1.0f),
399 if (ImGui::TreeNode(
"Audio Queue")) {
403 if (audio.is_playing) {
404 ImGui::TextColored(ImVec4(0.3f, 0.9f, 0.3f, 1.0f),
407 ImGui::TextColored(ImVec4(0.6f, 0.6f, 0.6f, 1.0f),
411 ImGui::Text(
"Queued: %u frames (%u bytes)",
412 audio.queued_frames, audio.queued_bytes);
413 ImGui::Text(
"Sample Rate: %d Hz", audio.sample_rate);
414 ImGui::Text(
"Backend: %s", audio.backend_name.c_str());
417 if (audio.has_underrun) {
418 ImGui::TextColored(ImVec4(1.0f, 0.2f, 0.2f, 1.0f),
423 float queue_level = audio.queued_frames / 6000.0f;
424 queue_level = std::clamp(queue_level, 0.0f, 1.0f);
425 ImVec4 queue_color = (queue_level < 0.2f)
426 ? ImVec4(1.0f, 0.3f, 0.3f, 1.0f)
427 : ImVec4(0.3f, 0.8f, 0.3f, 1.0f);
428 ImGui::PushStyleColor(ImGuiCol_PlotHistogram, queue_color);
429 ImGui::ProgressBar(queue_level, ImVec2(-1, 0),
"Queue Level");
430 ImGui::PopStyleColor();
436 if (ImGui::TreeNode(
"APU Timing")) {
439 ImGui::Text(
"Cycles: %llu",
static_cast<unsigned long long>(apu.cycles));
442 if (ImGui::BeginTable(
"Timers", 4, ImGuiTableFlags_Borders)) {
443 ImGui::TableSetupColumn(
"Timer");
444 ImGui::TableSetupColumn(
"Enabled");
445 ImGui::TableSetupColumn(
"Counter");
446 ImGui::TableSetupColumn(
"Target");
447 ImGui::TableHeadersRow();
450 ImGui::TableNextRow();
451 ImGui::TableNextColumn(); ImGui::Text(
"T0");
452 ImGui::TableNextColumn();
453 ImGui::TextColored(apu.timer0_enabled ? ImVec4(0.3f, 0.9f, 0.3f, 1.0f)
454 : ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
455 apu.timer0_enabled ?
"ON" :
"off");
456 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer0_counter);
457 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer0_target);
460 ImGui::TableNextRow();
461 ImGui::TableNextColumn(); ImGui::Text(
"T1");
462 ImGui::TableNextColumn();
463 ImGui::TextColored(apu.timer1_enabled ? ImVec4(0.3f, 0.9f, 0.3f, 1.0f)
464 : ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
465 apu.timer1_enabled ?
"ON" :
"off");
466 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer1_counter);
467 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer1_target);
470 ImGui::TableNextRow();
471 ImGui::TableNextColumn(); ImGui::Text(
"T2");
472 ImGui::TableNextColumn();
473 ImGui::TextColored(apu.timer2_enabled ? ImVec4(0.3f, 0.9f, 0.3f, 1.0f)
474 : ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
475 apu.timer2_enabled ?
"ON" :
"off");
476 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer2_counter);
477 ImGui::TableNextColumn(); ImGui::Text(
"%u", apu.timer2_target);
483 ImGui::Text(
"Ports IN: [0]=%02X [1]=%02X", apu.port0_in, apu.port1_in);
484 ImGui::Text(
"Ports OUT: [0]=%02X [1]=%02X", apu.port0_out, apu.port1_out);
490 if (ImGui::TreeNode(
"Channels")) {
493 ImGui::Text(
"Key Status:");
495 for (
int i = 0; i < 8; i++) {
496 ImVec4 color = channels[i].key_on
497 ? ImVec4(0.2f, 0.9f, 0.2f, 1.0f)
498 : ImVec4(0.4f, 0.4f, 0.4f, 1.0f);
499 ImGui::TextColored(color,
"%d", i);
500 if (i < 7) ImGui::SameLine();
504 if (ImGui::BeginTable(
"ChannelDetails", 6,
505 ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg)) {
506 ImGui::TableSetupColumn(
"Ch", ImGuiTableColumnFlags_WidthFixed, 25);
507 ImGui::TableSetupColumn(
"Key");
508 ImGui::TableSetupColumn(
"Sample");
509 ImGui::TableSetupColumn(
"Pitch");
510 ImGui::TableSetupColumn(
"Vol L/R");
511 ImGui::TableSetupColumn(
"ADSR");
512 ImGui::TableHeadersRow();
514 const char* adsr_names[] = {
"Atk",
"Dec",
"Sus",
"Rel"};
515 for (
int i = 0; i < 8; i++) {
516 ImGui::TableNextRow();
517 ImGui::TableNextColumn(); ImGui::Text(
"%d", i);
518 ImGui::TableNextColumn();
519 ImGui::TextColored(channels[i].key_on ? ImVec4(0.2f, 0.9f, 0.2f, 1.0f)
520 : ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
521 channels[i].key_on ?
"ON" :
"--");
522 ImGui::TableNextColumn(); ImGui::Text(
"%02X", channels[i].sample_index);
523 ImGui::TableNextColumn(); ImGui::Text(
"%04X", channels[i].pitch);
524 ImGui::TableNextColumn();
525 ImGui::Text(
"%02X/%02X", channels[i].volume_l, channels[i].volume_r);
526 ImGui::TableNextColumn();
527 int state = channels[i].adsr_state & 0x03;
528 ImGui::Text(
"%s", adsr_names[state]);
539 ImGui::Text(
"Actions:");
544 if (ImGui::IsItemHovered())
545 ImGui::SetTooltip(
"Clear SDL audio queue immediately");
551 if (ImGui::IsItemHovered())
552 ImGui::SetTooltip(
"Reset DSP sample ring buffer");
558 if (ImGui::IsItemHovered())
559 ImGui::SetTooltip(
"Force DSP NewFrame() call");
565 if (ImGui::IsItemHovered())
566 ImGui::SetTooltip(
"Full audio system reinitialization");