#include #include #include #include #include #include #include #include #include #include #include #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); } } }