diff --git a/tui/src/display_model.cpp b/tui/src/display_model.cpp index c780be4..cbda6f6 100644 --- a/tui/src/display_model.cpp +++ b/tui/src/display_model.cpp @@ -151,4 +151,14 @@ std::string formatLufs(float value) { return formatDb(value, 1); } +std::string formatMaxTruePeakDb(float value, bool compact) { + std::string formatted = formatDb(value, 1); + if (std::isfinite(value) && value > -60.0f && value >= 0.0f) { + formatted.insert(formatted.begin(), '+'); + } + return compact + ? formatted + "dBTP" + : "MAX TP " + formatted + " dBTP"; +} + } // namespace Prism::Tui diff --git a/tui/src/display_model.h b/tui/src/display_model.h index 77e752c..13c2f4d 100644 --- a/tui/src/display_model.h +++ b/tui/src/display_model.h @@ -26,5 +26,6 @@ std::string buildFrequencyAxis(size_t columns, float maxFrequency); std::string buildMeterBar(float levelDb, float peakDb, size_t columns); std::string formatDb(float value, int precision = 1); std::string formatLufs(float value); +std::string formatMaxTruePeakDb(float value, bool compact = false); } // namespace Prism::Tui diff --git a/tui/src/tui_runtime.cpp b/tui/src/tui_runtime.cpp index e44136b..bc8a01b 100644 --- a/tui/src/tui_runtime.cpp +++ b/tui/src/tui_runtime.cpp @@ -1230,11 +1230,18 @@ ftxui::Element renderLUFSMeterPanel(const DisplayFrame& frame, static_cast(std::max(0, meterRows - 1)))); Elements rows; - rows.push_back(hbox({ - text(" L R LUFS") | dim, - filler(), - text("target -14") | color(terminalColor(palette().lufsTarget)), - })); + Elements heading; + heading.push_back(text(" L R LUFS") | dim); + heading.push_back(filler()); + heading.push_back(text(formatMaxTruePeakDb( + frame.lufs.maxTruePeakDb, width < 60)) | + color(terminalColor(palette().lufsLabels))); + if (width >= 52) { + heading.push_back(text(" ")); + heading.push_back(text("target -14") | + color(terminalColor(palette().lufsTarget))); + } + rows.push_back(hbox(std::move(heading))); for (int row = 0; row < meterRows; ++row) { Elements parts; const auto gap = [&]() { @@ -1246,11 +1253,11 @@ ftxui::Element renderLUFSMeterPanel(const DisplayFrame& frame, parts.push_back(text(lufsScaleLabel(row, meterRows)) | dim); parts.push_back(gap()); parts.push_back(renderLufsBarCell( - frame.lufs.barLDb, frame.lufs.peakLDb, + frame.lufs.barLDb, frame.lufs.truePeakLDb, row, meterRows, 2, true, row == targetRow)); parts.push_back(gap()); parts.push_back(renderLufsBarCell( - frame.lufs.barRDb, frame.lufs.peakRDb, + frame.lufs.barRDb, frame.lufs.truePeakRDb, row, meterRows, 2, true, row == targetRow)); parts.push_back(gap()); parts.push_back(renderLufsBarCell( diff --git a/tui/test/tui_tests.cpp b/tui/test/tui_tests.cpp index 56c5f3d..0cc8b7f 100644 --- a/tui/test/tui_tests.cpp +++ b/tui/test/tui_tests.cpp @@ -429,6 +429,10 @@ void testMeterDisplayModels() { require(Prism::Tui::selectLufsReadout( -10.0f, -12.0f, -14.0f, Prism::Tui::LUFSReadout::Integrated) == -14.0f, "LUFS readout selection should drive the dedicated loudness bar"); + require(Prism::Tui::formatMaxTruePeakDb(3.0f) == "MAX TP +3.0 dBTP" && + Prism::Tui::formatMaxTruePeakDb(3.0f, true) == "+3.0dBTP" && + Prism::Tui::formatMaxTruePeakDb(-60.0f) == "MAX TP -inf dBTP", + "true-peak maximum formatting should cover normal, compact, and reset states"); } void testSpectrumPeakModel() { @@ -1040,6 +1044,10 @@ void testPipelineAndFakeCapture() { "momentary LUFS should match the deterministic stereo tone"); require(std::abs(frame.lufs.integratedLUFS + 12.03f) < 0.5f, "integrated LUFS should match the deterministic stereo tone"); + require(std::abs(frame.lufs.maxTruePeakDb + 12.04f) < 0.2f && + std::abs(frame.lufs.truePeakLDb - frame.lufs.maxTruePeakDb) < 0.1f && + std::abs(frame.lufs.truePeakRDb - frame.lufs.maxTruePeakDb) < 0.1f, + "TUI loudness snapshots should expose calibrated stereo true peak"); Prism::Tui::AnalysisPipeline trimmedPipeline(48000.0f); trimmedPipeline.setInputTrimDb(6.0f); @@ -1072,6 +1080,8 @@ void testPipelineAndFakeCapture() { pipeline.reset(); const auto reset = pipeline.snapshot(); require(reset.lufs.integratedLUFS <= -59.0f, "reset should clear integrated loudness"); + require(reset.lufs.maxTruePeakDb <= -59.0f, + "reset should clear the maximum true-peak readout"); require(!reset.oscilloscope.signalPresent, "reset should clear the oscilloscope display window"); require(reset.oscilloscope.detectedPitch == 0.0f,