mirror of
https://github.com/outbackdingo/UltraGrid.git
synced 2026-03-20 17:40:12 +00:00
291 lines
7.3 KiB
C++
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);
|
|
}
|
|
|
|
}
|
|
|
|
}
|