diff --git a/src/core/imgui_overlays.cpp b/src/core/imgui_overlays.cpp index d050d64e8..1d117c580 100644 --- a/src/core/imgui_overlays.cpp +++ b/src/core/imgui_overlays.cpp @@ -171,6 +171,15 @@ bool ImGuiManager::AreAnyDebugWindowsEnabled(const SettingsInterface& si) return false; } +bool ImGuiManager::IsSPUDebugWindowEnabled() +{ +#ifndef __ANDROID__ + return (s_debug_window_state[1].window_handle != nullptr); +#else + return false; +#endif +} + bool ImGuiManager::UpdateDebugWindowConfig() { #ifndef __ANDROID__ diff --git a/src/core/imgui_overlays.h b/src/core/imgui_overlays.h index ca947b555..13aa45c11 100644 --- a/src/core/imgui_overlays.h +++ b/src/core/imgui_overlays.h @@ -18,6 +18,7 @@ static constexpr const char* LOGO_IMAGE_NAME = "images/duck.png"; void UpdateInputOverlay(); void RenderTextOverlays(const GPUBackend* gpu); bool AreAnyDebugWindowsEnabled(const SettingsInterface& si); +bool IsSPUDebugWindowEnabled(); void RenderDebugWindows(); bool UpdateDebugWindowConfig(); void DestroyAllDebugWindows(); diff --git a/src/core/spu.cpp b/src/core/spu.cpp index 8de6aaca6..bffb23062 100644 --- a/src/core/spu.cpp +++ b/src/core/spu.cpp @@ -1,11 +1,11 @@ -// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2025 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "spu.h" #include "cdrom.h" #include "dma.h" #include "host.h" -#include "imgui.h" +#include "imgui_overlays.h" #include "interrupt_controller.h" #include "system.h" #include "timing_event.h" @@ -25,6 +25,7 @@ #include "IconsEmoji.h" #include "fmt/format.h" +#include "imgui.h" #include @@ -33,6 +34,11 @@ LOG_CHANNEL(SPU); // Enable to dump all voices of the SPU audio individually. // #define SPU_DUMP_ALL_VOICES 1 +// VU meter is only enabled in devel builds due to speed impact. +#ifdef _DEVEL +#define SPU_ENABLE_VU_METER 1 +#endif + ALWAYS_INLINE static constexpr s32 Clamp16(s32 value) { return (value < -0x8000) ? -0x8000 : (value > 0x7FFF) ? 0x7FFF : value; @@ -424,6 +430,13 @@ struct SPUState // +1 for reverb output std::array, NUM_VOICES + 1> s_voice_dump_writers; #endif + +#ifdef _DEVEL + s16 output_peaks[2] = {}; + s16 cd_audio_peaks[2] = {}; + s16 reverb_peaks[2] = {}; + s16 voice_peaks[NUM_VOICES][2] = {}; +#endif }; } // namespace @@ -433,6 +446,21 @@ ALIGN_TO_CACHE_LINE static std::array s_muted_output_buff } // namespace SPU +#ifdef SPU_ENABLE_VU_METER + +static bool IsVUMeterActive() +{ + return ImGuiManager::IsSPUDebugWindowEnabled(); +} + +ALWAYS_INLINE_RELEASE static void UpdateDebugPeaks(s16 peaks[2], s32 left, s32 right) +{ + peaks[0] = std::max(static_cast(std::abs(Clamp16(left))), peaks[0]); + peaks[1] = std::max(static_cast(std::abs(Clamp16(right))), peaks[1]); +} + +#endif + void SPU::Initialize() { // (X * D) / N / 768 -> (X * D) / (N * 768) @@ -2127,6 +2155,11 @@ ALWAYS_INLINE_RELEASE std::tuple SPU::SampleVoice(u32 voice_index) voice.left_volume.Tick(); voice.right_volume.Tick(); +#ifdef SPU_ENABLE_VU_METER + if (IsVUMeterActive()) + UpdateDebugPeaks(s_state.voice_peaks[voice_index], left, right); +#endif + #ifdef SPU_DUMP_ALL_VOICES if (s_state.s_voice_dump_writers[voice_index]) { @@ -2339,6 +2372,11 @@ void SPU::ProcessReverb(s32 left_in, s32 right_in, s32* left_out, s32* right_out s_state.last_reverb_output[0] = *left_out = ApplyVolume(out[0], s_state.reverb_registers.vLOUT); s_state.last_reverb_output[1] = *right_out = ApplyVolume(out[1], s_state.reverb_registers.vROUT); +#ifdef SPU_ENABLE_VU_METER + if (IsVUMeterActive()) + UpdateDebugPeaks(s_state.reverb_peaks, *left_out, *right_out); +#endif + #ifdef SPU_DUMP_ALL_VOICES if (s_state.s_voice_dump_writers[NUM_VOICES]) { @@ -2433,6 +2471,11 @@ void SPU::Execute(void* param, TickCount ticks, TickCount ticks_late) reverb_in_left += cd_audio_volume_left; reverb_in_right += cd_audio_volume_right; } + +#ifdef SPU_ENABLE_VU_METER + if (IsVUMeterActive()) + UpdateDebugPeaks(s_state.cd_audio_peaks, cd_audio_volume_left, cd_audio_volume_right); +#endif } // Compute reverb. @@ -2444,8 +2487,20 @@ void SPU::Execute(void* param, TickCount ticks, TickCount ticks_late) right_sum += reverb_out_right; // Apply main volume after clamping. A maximum volume should not overflow here because both are 16-bit values. +#ifdef SPU_ENABLE_VU_METER + const s16 final_left = static_cast(ApplyVolume(Clamp16(left_sum), s_state.main_volume_left.current_level)); + const s16 final_right = + static_cast(ApplyVolume(Clamp16(right_sum), s_state.main_volume_right.current_level)); + *(output_frame++) = final_left; + *(output_frame++) = final_right; + + if (IsVUMeterActive()) + UpdateDebugPeaks(s_state.output_peaks, final_left, final_right); +#else *(output_frame++) = static_cast(ApplyVolume(Clamp16(left_sum), s_state.main_volume_left.current_level)); *(output_frame++) = static_cast(ApplyVolume(Clamp16(right_sum), s_state.main_volume_right.current_level)); +#endif + s_state.main_volume_left.Tick(); s_state.main_volume_right.Tick(); @@ -2519,6 +2574,45 @@ void SPU::DrawDebugStateWindow(float scale) static const ImVec4 active_color{1.0f, 1.0f, 1.0f, 1.0f}; static const ImVec4 inactive_color{0.4f, 0.4f, 0.4f, 1.0f}; +#ifdef SPU_ENABLE_VU_METER + const auto draw_vu_meter = [&scale](s16* peaks) { + constexpr s32 num_sections = 12; + constexpr s32 amp_per_section = 32767 / num_sections; + const s32 lidx = peaks[0] / amp_per_section; + const s32 ridx = peaks[1] / amp_per_section; + + const ImVec2 section_size = ImVec2(std::floor(scale * 8.0f), std::floor(scale * 4.0f)); + const float divider_size = std::floor(scale * 1.0f); + ImVec2 left_start = ImGui::GetCursorPos() + ImVec2(0.0f, std::floor(scale * 4.0f)); + ImVec2 right_start = left_start + ImVec2(0.0f, section_size.y + divider_size); + for (s32 i = 0; i < num_sections; i++) + { + u32 left_color = IM_COL32(30, 30, 30, 255); + if (peaks[0] > 0 && lidx >= i) + { + left_color = IM_COL32(255, 0, 0, 255); + left_color = (i <= 8) ? IM_COL32(255, 255, 0, 255) : left_color; + left_color = (i <= 6) ? IM_COL32(0, 255, 0, 255) : left_color; + } + u32 right_color = IM_COL32(30, 30, 30, 255); + if (peaks[1] > 0 && ridx >= i) + { + right_color = IM_COL32(255, 0, 0, 255); + right_color = (i <= 8) ? IM_COL32(255, 255, 0, 255) : right_color; + right_color = (i <= 6) ? IM_COL32(0, 255, 0, 255) : right_color; + } + + ImGui::GetWindowDrawList()->AddRectFilled(left_start, left_start + section_size, left_color); + ImGui::GetWindowDrawList()->AddRectFilled(right_start, right_start + section_size, right_color); + left_start.x += section_size.x + divider_size; + right_start.x += section_size.x + divider_size; + } + + peaks[0] = 0; + peaks[1] = 0; + }; +#endif + // status if (ImGui::CollapsingHeader("Status", ImGuiTreeNodeFlags_DefaultOpen)) { @@ -2565,6 +2659,11 @@ void SPU::DrawDebugStateWindow(float scale) ImGui::Text("Left: %d%%", ApplyVolume(100, s_state.main_volume_left.current_level)); ImGui::SameLine(offsets[1]); ImGui::Text("Right: %d%%", ApplyVolume(100, s_state.main_volume_right.current_level)); +#ifdef SPU_ENABLE_VU_METER + ImGui::SameLine(offsets[2]); + draw_vu_meter(s_state.output_peaks); + ImGui::NewLine(); +#endif ImGui::Text("CD Audio: "); ImGui::SameLine(offsets[0]); @@ -2576,6 +2675,11 @@ void SPU::DrawDebugStateWindow(float scale) ImGui::SameLine(offsets[3]); ImGui::TextColored(s_state.SPUCNT.cd_audio_enable ? active_color : inactive_color, "Right Volume: %d%%", ApplyVolume(100, s_state.cd_audio_volume_left)); +#ifdef SPU_ENABLE_VU_METER + ImGui::SameLine(offsets[5]); + draw_vu_meter(s_state.cd_audio_peaks); + ImGui::NewLine(); +#endif ImGui::Text("Transfer FIFO: "); ImGui::SameLine(offsets[0]); @@ -2586,18 +2690,21 @@ void SPU::DrawDebugStateWindow(float scale) // draw voice states if (ImGui::CollapsingHeader("Voice State", ImGuiTreeNodeFlags_DefaultOpen)) { - static constexpr u32 NUM_COLUMNS = 12; + static constexpr std::array column_titles = { + "#", "StartAddr", "RepeatAddr", "CurAddr", "SampleIdx", "SampleRate", + "VolLeft", "VolRight", "ADSRPhase", "ADSRVol", "ADSRTicks", +#ifdef SPU_ENABLE_VU_METER + , "VUMeter" +#endif + }; + static constexpr std::array adsr_phases = {"Off", "Attack", "Decay", "Sustain", "Release"}; - ImGui::Columns(NUM_COLUMNS); + ImGui::Columns(static_cast(column_titles.size())); // headers - static constexpr std::array column_titles = { - {"#", "InterpIndex", "SampleIndex", "CurAddr", "StartAddr", "RepeatAddr", "SampleRate", "VolLeft", "VolRight", - "ADSRPhase", "ADSRVol", "ADSRTicks"}}; - static constexpr std::array adsr_phases = {{"Off", "Attack", "Decay", "Sustain", "Release"}}; - for (u32 i = 0; i < NUM_COLUMNS; i++) + for (const char* column_title : column_titles) { - ImGui::TextUnformatted(column_titles[i]); + ImGui::TextUnformatted(column_title); ImGui::NextColumn(); } @@ -2608,19 +2715,17 @@ void SPU::DrawDebugStateWindow(float scale) ImVec4 color = v.IsOn() ? ImVec4(1.0f, 1.0f, 1.0f, 1.0f) : ImVec4(0.5f, 0.5f, 0.5f, 1.0f); ImGui::TextColored(color, "%u", ZeroExtend32(voice_index)); ImGui::NextColumn(); - if (IsVoiceNoiseEnabled(voice_index)) - ImGui::TextColored(color, "NOISE"); - else - ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.interpolation_index.GetValue())); - ImGui::NextColumn(); - ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.sample_index.GetValue())); - ImGui::NextColumn(); - ImGui::TextColored(color, "%04X", ZeroExtend32(v.current_address)); - ImGui::NextColumn(); ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_start_address)); ImGui::NextColumn(); ImGui::TextColored(color, "%04X", ZeroExtend32(v.regs.adpcm_repeat_address)); ImGui::NextColumn(); + ImGui::TextColored(color, "%04X", ZeroExtend32(v.current_address)); + ImGui::NextColumn(); + if (IsVoiceNoiseEnabled(voice_index)) + ImGui::TextColored(color, "NOISE"); + else + ImGui::TextColored(color, "%u", ZeroExtend32(v.counter.sample_index.GetValue())); + ImGui::NextColumn(); ImGui::TextColored(color, "%.2f", (float(v.regs.adpcm_sample_rate) / 4096.0f) * 44100.0f); ImGui::NextColumn(); ImGui::TextColored(color, "%d%%", ApplyVolume(100, v.left_volume.current_level)); @@ -2633,6 +2738,10 @@ void SPU::DrawDebugStateWindow(float scale) ImGui::NextColumn(); ImGui::TextColored(color, "%d", v.adsr_envelope.counter); ImGui::NextColumn(); +#ifdef SPU_ENABLE_VU_METER + draw_vu_meter(s_state.voice_peaks[voice_index]); + ImGui::NextColumn(); +#endif } ImGui::Columns(1); @@ -2664,6 +2773,11 @@ void SPU::DrawDebugStateWindow(float scale) s_state.last_reverb_input[1], s_state.last_reverb_output[0], s_state.last_reverb_output[1]); ImGui::Text("Output Volume: Left %d%% Right %d%%", ApplyVolume(100, s_state.reverb_registers.vLOUT), ApplyVolume(100, s_state.reverb_registers.vROUT)); +#ifdef SPU_ENABLE_VU_METER + ImGui::SameLine(); + draw_vu_meter(s_state.reverb_peaks); + ImGui::NewLine(); +#endif ImGui::Text("Pitch Modulation: "); for (u32 i = 1; i < NUM_VOICES; i++)