| 1129 | } |
| 1130 | |
| 1131 | void StripFinalOutputPanel::tickMeters() |
| 1132 | { |
| 1133 | if (!m_audio) return; |
| 1134 | auto* lim = m_audio->clientFinalLimiterTx(); |
| 1135 | if (!lim) return; |
| 1136 | |
| 1137 | const float inPeak = lim->inputPeakDb(); |
| 1138 | const float outPeak = lim->outputPeakDb(); |
| 1139 | const float outRms = lim->outputRmsDb(); |
| 1140 | const float gr = lim->gainReductionDb(); |
| 1141 | const bool active = lim->active(); |
| 1142 | const float activity = lim->limiterActivityPct(); |
| 1143 | const quint64 clipCount = lim->clipPreLimiterCount(); |
| 1144 | |
| 1145 | // Feed the smoothers in normalised [0, 1] space — setTarget every |
| 1146 | // tick with the latest accessor value, no "only rise" guard. The |
| 1147 | // smoother's asymmetric attack/release does the right thing on |
| 1148 | // its own (matches ClientCompMeter, ClientCompApplet, etc.). |
| 1149 | // GR is mapped via -gr/30 so a 30 dB cut reads as a full bar |
| 1150 | // (matches the existing red-fill overlay scale in HorizMeter). |
| 1151 | auto dbRatio = [](float db) { |
| 1152 | if (db <= kMeterMinDb) return 0.0f; |
| 1153 | if (db >= kMeterMaxDb) return 1.0f; |
| 1154 | return (db - kMeterMinDb) / (kMeterMaxDb - kMeterMinDb); |
| 1155 | }; |
| 1156 | m_inPeakSmooth.setTarget(dbRatio(inPeak)); |
| 1157 | m_outPeakSmooth.setTarget(dbRatio(outPeak)); |
| 1158 | m_outRmsSmooth.setTarget(dbRatio(outRms)); |
| 1159 | m_grSmooth.setTarget(std::clamp(-gr / 30.0f, 0.0f, 1.0f)); |
| 1160 | |
| 1161 | const qint64 dt = m_animClock.restart(); |
| 1162 | m_inPeakSmooth.tick(dt); |
| 1163 | m_outPeakSmooth.tick(dt); |
| 1164 | m_outRmsSmooth.tick(dt); |
| 1165 | m_grSmooth.tick(dt); |
| 1166 | |
| 1167 | constexpr float kRange = kMeterMaxDb - kMeterMinDb; |
| 1168 | m_inPeakDb = kMeterMinDb + m_inPeakSmooth.value() * kRange; |
| 1169 | m_outPeakDb = kMeterMinDb + m_outPeakSmooth.value() * kRange; |
| 1170 | m_outRmsDb = kMeterMinDb + m_outRmsSmooth.value() * kRange; |
| 1171 | m_grDb = -m_grSmooth.value() * 30.0f; |
| 1172 | |
| 1173 | m_active = active; |
| 1174 | |
| 1175 | // OVR latch — clip count is monotonic; any increase since last |
| 1176 | // tick lights the LED for ~1.5 s. |
| 1177 | const qint64 nowMs = QDateTime::currentMSecsSinceEpoch(); |
| 1178 | if (clipCount > m_lastClipCount) { |
| 1179 | m_ovrLatchUntilMs = nowMs + 1500; |
| 1180 | m_lastClipCount = clipCount; |
| 1181 | } |
| 1182 | const bool ovrActive = nowMs < m_ovrLatchUntilMs; |
| 1183 | |
| 1184 | auto fmtDb = [](float v) { |
| 1185 | if (v <= -100.0f) return QString("-∞"); |
| 1186 | return QString::number(v, 'f', 1); |
| 1187 | }; |
| 1188 | // Peak-hold latches worst-case-since-engaged (#2887). PK / RMS |
nothing calls this directly
no test coverage detected