Files
UltraGrid/gui/QT/widget/vuMeterWidget.cpp
2023-10-11 14:45:44 +02:00

291 lines
7.3 KiB
C++

#include <algorithm>
#include <cassert>
#include <cmath>
#include <thread>
#include <functional>
#include <QPainter>
#include <QBrush>
#include <QLinearGradient>
#include <QRect>
#include <QString>
#include <QStyle>
#include "vuMeterWidget.hpp"
#include "control_port.hpp"
#include "utils/string_view_utils.hpp"
#include "astat.h"
namespace{
static constexpr int meterVerticalPad = 5;
static constexpr int meterBarPad = 2;
static constexpr double zeroLevel = -40.0;
}//anon namespace
VuMeterWidget::VuMeterWidget(QWidget *parent) :
QWidget(parent),
peak {0.0},
rms {0.0},
barLevel {0.0},
rmsLevel {0.0},
updatesPerSecond(24)
{
connect(&timer, SIGNAL(timeout()), this, SLOT(updateVal()));
timer.start(1000/updatesPerSecond);
}
void VuMeterWidget::parseLine(std::string_view line){
if(!sv_is_prefix(line, parsePrefix))
return;
line.remove_prefix(parsePrefix.size());
for(channels = 0; !line.empty() && channels < max_channels; channels++){
auto rmsTag = tokenize(line, ' ');
if(!sv_is_prefix(rmsTag, "volrms"))
return;
auto rms_sv = tokenize(line, ' ');
auto peakTag = tokenize(line, ' ');
if(!sv_is_prefix(peakTag, "volpeak"))
return;
auto peak_sv = tokenize(line, ' ');
double ch_rms = 0;
double ch_peak = 0;
if(!parse_num(rms_sv, ch_rms)
|| !parse_num(peak_sv, ch_peak))
{
return;
}
peak[channels] = ch_peak;
rms[channels] = ch_rms;
}
lastUpdate = clock::now();
}
void VuMeterWidget::setControlPort(ControlPort *controlPort){
this->controlPort = controlPort;
using namespace std::placeholders;
controlPort->addLineCallback(std::bind(&VuMeterWidget::parseLine, this, _1));
connect(controlPort, &ControlPort::connected, this, &VuMeterWidget::onControlPortConnect);
}
void VuMeterWidget::setParsePrefix(std::string_view prefix){
parsePrefix = prefix;
}
void VuMeterWidget::updateVal(){
const double fallSpeed = 200.0;
const double maximumPeakFallSpeed = 10.0;
auto maximumPeakHoldTime = std::chrono::seconds(2);
connected = controlPort->getState() == QAbstractSocket::ConnectedState;
for(int i = 0; i < channels; i++){
auto& maxPeak = maximumLevel[i];
auto now = clock::now();
barLevel[i] = std::max(barLevel[i] - fallSpeed / updatesPerSecond, 0.0);
rmsLevel[i] = std::max(rmsLevel[i] - fallSpeed / updatesPerSecond, 0.0);
if(now - maxPeak.ts > maximumPeakHoldTime){
maxPeak.val = std::max(maxPeak.val - maximumPeakFallSpeed / updatesPerSecond, 0.0);
}
double newPeakHeight = 100 - std::max(peak[i], zeroLevel) * (100 / zeroLevel);
double newRmsHeight = 100 - std::max(rms[i], zeroLevel) * (100 / zeroLevel);
barLevel[i] = std::max(barLevel[i], newPeakHeight);
rmsLevel[i] = std::max(rmsLevel[i], newRmsHeight);
if(barLevel[i] >= maxPeak.val){
maxPeak.val = barLevel[i];
maxPeak.ts = now;
}
}
update();
}
void VuMeterWidget::onControlPortConnect(){
assert(controlPort);
controlPort->writeLine("stats on\r\n");
lastUpdate = clock::time_point();
}
void VuMeterWidget::paintMeter(QPainter& painter,
int x,
int y,
int width,
int height,
double peak,
double rms,
double maxPeak)
{
const int meterBarPadHorizontal = 1;
const int in_width = width - meterBarPadHorizontal * 2;
const int in_full_height = height - meterBarPad * 2;
int barHeight = in_full_height * (peak / 100);
int rmsHeight = in_full_height * (rms / 100);
int maxPeakHeight = in_full_height * (maxPeak / 100);
QLinearGradient gradient(0, 0, in_width, in_full_height);
gradient.setColorAt(0, Qt::red);
gradient.setColorAt(0.25, Qt::yellow);
gradient.setColorAt(0.5, Qt::green);
gradient.setColorAt(1, Qt::green);
QBrush brush(gradient);
using namespace std::chrono_literals;
bool enabled = connected && (clock::now() - lastUpdate <= 2s);
painter.fillRect(x, y + 1, width, height - 2, enabled ? Qt::black : Qt::gray);
if(!enabled)
return;
painter.fillRect(x + meterBarPadHorizontal,
y + meterBarPad + (in_full_height - barHeight),
in_width, barHeight,
brush);
int maxPos = y + meterBarPad + (in_full_height - maxPeakHeight);
QPen maxLevelPen(brush, 1);
painter.setPen(maxLevelPen);
painter.drawLine(x + meterBarPadHorizontal, maxPos, x + in_width, maxPos);
QPen rmsPen(Qt::blue);
rmsPen.setWidth(4);
painter.setPen(rmsPen);
int pos = y + meterBarPad + (in_full_height - rmsHeight);
painter.drawLine(x + meterBarPadHorizontal, pos, x + in_width, pos);
}
void VuMeterWidget::paintScale(QPainter& painter,
int x,
int y,
int width,
int height,
bool leftTicks,
bool rightTicks)
{
const int barHeight = height - meterVerticalPad * 2 - meterBarPad * 2;
const int barStart = y + meterVerticalPad + meterBarPad;
const float stepPx = (float) barHeight / std::fabs(zeroLevel);
painter.setPen(connected ? Qt::black : Qt::gray);
int i = 0;
int textPosX;
if(leftTicks && rightTicks){
textPosX = x + width / 2 - 8;
} else if(leftTicks){
textPosX = x + width - 16;
} else {
textPosX = x;
}
for(float y = barStart; y <= barStart + barHeight + 0.1; y += stepPx){
static const int lineLenghtSmall = 2;
static const int lineLenghtMid = 4;
static const int lineLenghtLarge = 6;
static const int drawThreshold = 4;
if(i % 10 == 0){
if(leftTicks) painter.drawLine(x, y, x + lineLenghtLarge, y);
if(rightTicks) painter.drawLine(x + width - lineLenghtLarge, y, x + width, y);
painter.drawText(QRect(textPosX, y - 6, 16, 12), Qt::AlignCenter,QString::number(i));
} else if(i % 5 == 0){
if(leftTicks) painter.drawLine(x, y, x + lineLenghtMid, y);
if(rightTicks) painter.drawLine(x + width - lineLenghtMid, y, x + width, y);
} else if(stepPx >= drawThreshold){
if(leftTicks) painter.drawLine(x, y, x + lineLenghtSmall, y);
if(rightTicks) painter.drawLine(x + width - lineLenghtSmall, y, x + width, y);
}
i++;
}
if(!connected){
int iconDim = 32;
QIcon icon = style()->standardIcon(QStyle::SP_MessageBoxWarning);
QPixmap pixmap = icon.pixmap(iconDim,iconDim);
painter.eraseRect(x+width/2 - (iconDim+4)/2, y + height / 2 - (iconDim+4)/2,
iconDim+4, iconDim+4);
painter.drawPixmap(x+width/2 - iconDim/2, y + height / 2 - iconDim/2,
iconDim, iconDim,
pixmap,
0, 0,
iconDim, iconDim);
}
}
void VuMeterWidget::paintEvent(QPaintEvent * /*paintEvent*/){
const int scaleWidth = 30;
const int meterCount = std::max(1, channels);
const int meterWidth = (width() - scaleWidth) / meterCount;
QPainter painter(this);
if(meterCount % 2 != 0){
int scaleX = onRightSide ? 0 : meterCount * meterWidth;
int metersStartX = onRightSide ? scaleWidth : 0;
for(int i = 0; i < meterCount; i++){
paintMeter(painter,
metersStartX + i * meterWidth,
meterVerticalPad,
meterWidth,
height() - meterVerticalPad * 2,
barLevel[i],
rmsLevel[i],
maximumLevel[i].val);
}
paintScale(painter,
scaleX,
0,
scaleWidth,
height(),
!onRightSide,
onRightSide);
} else {
int i = 0;
for(; i < meterCount / 2; i++){
paintMeter(painter,
i * meterWidth,
meterVerticalPad,
meterWidth,
height() - meterVerticalPad * 2,
barLevel[i],
rmsLevel[i],
maximumLevel[i].val);
}
paintScale(painter,
i * meterWidth,
0,
scaleWidth,
height(),
true,
true);
for(; i < meterCount; i++){
paintMeter(painter,
i * meterWidth + scaleWidth,
meterVerticalPad,
meterWidth,
height() - meterVerticalPad * 2,
barLevel[i],
rmsLevel[i],
maximumLevel[i].val);
}
}
}