diff --git a/UI/data/themes/Dark.qss b/UI/data/themes/Dark.qss index 4e5e2c11e..9571a3c33 100644 --- a/UI/data/themes/Dark.qss +++ b/UI/data/themes/Dark.qss @@ -468,10 +468,16 @@ QSlider::handle:disabled { /* Volume Control */ VolumeMeter { - qproperty-bkColor: rgb(31,30,31); /* veryDark */ - qproperty-magColor:; - qproperty-peakColor:; - qproperty-peakHoldColor: rgb(225,224,225); /* veryLight */ + qproperty-backgroundNominalColor: rgb(38, 127, 38); + qproperty-backgroundWarningColor: rgb(127, 127, 38); + qproperty-backgroundErrorColor: rgb(127, 38, 38); + qproperty-foregroundNominalColor: rgb(76, 255, 76); + qproperty-foregroundWarningColor: rgb(255, 255, 76); + qproperty-foregroundErrorColor: rgb(255, 76, 76); + qproperty-magnitudeColor: rgb(0, 0, 0); + qproperty-majorTickColor: rgb(225,224,225); /* veryLight */ + qproperty-minorTickColor: rgb(122,121,122); /* light */ + qproperty-peakDecayRate: 23.4; /* Override of the standard PPM Type I rate. */ } diff --git a/UI/data/themes/Default.qss b/UI/data/themes/Default.qss index 1665d6892..772f8af05 100644 --- a/UI/data/themes/Default.qss +++ b/UI/data/themes/Default.qss @@ -59,10 +59,16 @@ OBSHotkeyLabel[hotkeyPairHover=true] { /* Volume Control */ VolumeMeter { - qproperty-bkColor: rgb(221, 221, 221); - qproperty-magColor: rgb(32, 125, 23); - qproperty-peakColor: rgb(62, 241, 43); - qproperty-peakHoldColor: rgb(0, 0, 0); + qproperty-backgroundNominalColor: rgb(15, 100, 15); + qproperty-backgroundWarningColor: rgb(100, 100, 15); + qproperty-backgroundErrorColor: rgb(100, 15, 15); + qproperty-foregroundNominalColor: rgb(50, 200, 50); + qproperty-foregroundWarningColor: rgb(255, 200, 50); + qproperty-foregroundErrorColor: rgb(200, 50, 50); + qproperty-magnitudeColor: rgb(0, 0, 0); + qproperty-majorTickColor: rgb(0, 0, 0); + qproperty-minorTickColor: rgb(50, 50, 50); + qproperty-peakDecayRate: 23.4; /* Override of the standard PPM Type I rate. */ } diff --git a/UI/data/themes/Rachni.qss b/UI/data/themes/Rachni.qss index e84d0c899..1c6844e11 100644 --- a/UI/data/themes/Rachni.qss +++ b/UI/data/themes/Rachni.qss @@ -689,12 +689,16 @@ QProgressBar::chunk { /**************************/ VolumeMeter { - qproperty-bkColor: rgb(35, 38, 41); /* Dark Gray */ - qproperty-magColor: rgb(153, 204, 0); - qproperty-peakColor: rgb(96, 128, 0); - qproperty-peakHoldColor: rgb(210, 255, 77); - qproperty-clipColor1: rgb(230, 40, 50); - qproperty-clipColor2: rgb(140, 0, 40); + qproperty-backgroundNominalColor: rgb(0, 128, 79); + qproperty-backgroundWarningColor: rgb(128, 57, 0); + qproperty-backgroundErrorColor: rgb(128, 9, 0); + qproperty-foregroundNominalColor: rgb(119, 255, 143); + qproperty-foregroundWarningColor: rgb(255, 157, 76); + qproperty-foregroundErrorColor: rgb(255, 89, 76); + qproperty-magnitudeColor: rgb(49, 54, 59); /* Blue-gray */ + qproperty-majorTickColor: rgb(239, 240, 241); /* White */ + qproperty-minorTickColor: rgb(118, 121, 124); /* Light Gray */ + qproperty-peakDecayRate: 23.4; /* Override of the standard PPM Type I rate. */ } /*******************/ diff --git a/UI/volume-control.cpp b/UI/volume-control.cpp index 5d08b1b89..3da05ce23 100644 --- a/UI/volume-control.cpp +++ b/UI/volume-control.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,8 @@ using namespace std; +#define CLAMP(x, min, max) ((x) < min ? min : ((x) > max ? max : (x))) + QWeakPointer VolumeMeter::updateTimer; void VolControl::OBSVolumeChanged(void *data, float db) @@ -29,15 +32,14 @@ void VolControl::OBSVolumeChanged(void *data, float db) QMetaObject::invokeMethod(volControl, "VolumeChanged"); } -void VolControl::OBSVolumeLevel(void *data, float level, float mag, - float peak, float muted) +void VolControl::OBSVolumeLevel(void *data, + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float inputPeak[MAX_AUDIO_CHANNELS]) { VolControl *volControl = static_cast(data); - if (muted) - level = mag = peak = 0.0f; - - volControl->volMeter->setLevels(mag, level, peak); + volControl->volMeter->setLevels(magnitude, peak, inputPeak); } void VolControl::OBSVolumeMuted(void *data, calldata_t *calldata) @@ -121,7 +123,7 @@ VolControl::VolControl(OBSSource source_, bool showConfig) nameLabel = new QLabel(); volLabel = new QLabel(); - volMeter = new VolumeMeter(); + volMeter = new VolumeMeter(0, obs_volmeter); mute = new MuteCheckBox(); slider = new QSlider(Qt::Horizontal); @@ -215,81 +217,222 @@ VolControl::~VolControl() obs_volmeter_destroy(obs_volmeter); } -QColor VolumeMeter::getBkColor() const +QColor VolumeMeter::getBackgroundNominalColor() const { - return bkColor; + return backgroundNominalColor; } -void VolumeMeter::setBkColor(QColor c) +void VolumeMeter::setBackgroundNominalColor(QColor c) { - bkColor = c; + backgroundNominalColor = c; } -QColor VolumeMeter::getMagColor() const +QColor VolumeMeter::getBackgroundWarningColor() const { - return magColor; + return backgroundWarningColor; } -void VolumeMeter::setMagColor(QColor c) +void VolumeMeter::setBackgroundWarningColor(QColor c) { - magColor = c; + backgroundWarningColor = c; } -QColor VolumeMeter::getPeakColor() const +QColor VolumeMeter::getBackgroundErrorColor() const { - return peakColor; + return backgroundErrorColor; } -void VolumeMeter::setPeakColor(QColor c) +void VolumeMeter::setBackgroundErrorColor(QColor c) { - peakColor = c; + backgroundErrorColor = c; } -QColor VolumeMeter::getPeakHoldColor() const +QColor VolumeMeter::getForegroundNominalColor() const { - return peakHoldColor; + return foregroundNominalColor; } -void VolumeMeter::setPeakHoldColor(QColor c) +void VolumeMeter::setForegroundNominalColor(QColor c) { - peakHoldColor = c; + foregroundNominalColor = c; } -QColor VolumeMeter::getClipColor1() const +QColor VolumeMeter::getForegroundWarningColor() const { - return clipColor1; + return foregroundWarningColor; } -void VolumeMeter::setClipColor1(QColor c) +void VolumeMeter::setForegroundWarningColor(QColor c) { - clipColor1 = c; + foregroundWarningColor = c; } -QColor VolumeMeter::getClipColor2() const +QColor VolumeMeter::getForegroundErrorColor() const { - return clipColor2; + return foregroundErrorColor; } -void VolumeMeter::setClipColor2(QColor c) +void VolumeMeter::setForegroundErrorColor(QColor c) { - clipColor2 = c; + foregroundErrorColor = c; } - -VolumeMeter::VolumeMeter(QWidget *parent) - : QWidget(parent) +QColor VolumeMeter::getClipColor() const { - setMinimumSize(1, 3); + return clipColor; +} - //Default meter color settings, they only show if there is no stylesheet, do not remove. - bkColor.setRgb(0xDD, 0xDD, 0xDD); - magColor.setRgb(0x20, 0x7D, 0x17); - peakColor.setRgb(0x3E, 0xF1, 0x2B); - peakHoldColor.setRgb(0x00, 0x00, 0x00); +void VolumeMeter::setClipColor(QColor c) +{ + clipColor = c; +} - clipColor1.setRgb(0x7F, 0x00, 0x00); - clipColor2.setRgb(0xFF, 0x00, 0x00); +QColor VolumeMeter::getMagnitudeColor() const +{ + return magnitudeColor; +} +void VolumeMeter::setMagnitudeColor(QColor c) +{ + magnitudeColor = c; +} + +QColor VolumeMeter::getMajorTickColor() const +{ + return majorTickColor; +} + +void VolumeMeter::setMajorTickColor(QColor c) +{ + majorTickColor = c; +} + +QColor VolumeMeter::getMinorTickColor() const +{ + return minorTickColor; +} + +void VolumeMeter::setMinorTickColor(QColor c) +{ + minorTickColor = c; +} + +qreal VolumeMeter::getMinimumLevel() const +{ + return minimumLevel; +} + +void VolumeMeter::setMinimumLevel(qreal v) +{ + minimumLevel = v; +} + +qreal VolumeMeter::getWarningLevel() const +{ + return warningLevel; +} + +void VolumeMeter::setWarningLevel(qreal v) +{ + warningLevel = v; +} + +qreal VolumeMeter::getErrorLevel() const +{ + return errorLevel; +} + +void VolumeMeter::setErrorLevel(qreal v) +{ + errorLevel = v; +} + +qreal VolumeMeter::getClipLevel() const +{ + return clipLevel; +} + +void VolumeMeter::setClipLevel(qreal v) +{ + clipLevel = v; +} + +qreal VolumeMeter::getMinimumInputLevel() const +{ + return minimumInputLevel; +} + +void VolumeMeter::setMinimumInputLevel(qreal v) +{ + minimumInputLevel = v; +} + +qreal VolumeMeter::getPeakDecayRate() const +{ + return peakDecayRate; +} + +void VolumeMeter::setPeakDecayRate(qreal v) +{ + peakDecayRate = v; +} + +qreal VolumeMeter::getMagnitudeIntegrationTime() const +{ + return magnitudeIntegrationTime; +} + +void VolumeMeter::setMagnitudeIntegrationTime(qreal v) +{ + magnitudeIntegrationTime = v; +} + +qreal VolumeMeter::getPeakHoldDuration() const +{ + return peakHoldDuration; +} + +void VolumeMeter::setPeakHoldDuration(qreal v) +{ + peakHoldDuration = v; +} + +qreal VolumeMeter::getInputPeakHoldDuration() const +{ + return inputPeakHoldDuration; +} + +void VolumeMeter::setInputPeakHoldDuration(qreal v) +{ + inputPeakHoldDuration = v; +} + +VolumeMeter::VolumeMeter(QWidget *parent, obs_volmeter_t *obs_volmeter) + : QWidget(parent), obs_volmeter(obs_volmeter) +{ + // Default meter color settings, they only show if + // there is no stylesheet, do not remove. + backgroundNominalColor.setRgb(0x26, 0x7f, 0x26); // Dark green + backgroundWarningColor.setRgb(0x7f, 0x7f, 0x26); // Dark yellow + backgroundErrorColor.setRgb(0x7f, 0x26, 0x26); // Dark red + foregroundNominalColor.setRgb(0x4c, 0xff, 0x4c); // Bright green + foregroundWarningColor.setRgb(0xff, 0xff, 0x4c); // Bright yellow + foregroundErrorColor.setRgb(0xff, 0x4c, 0x4c); // Bright red + clipColor.setRgb(0xff, 0xff, 0xff); // Bright white + magnitudeColor.setRgb(0x00, 0x00, 0x00); // Black + majorTickColor.setRgb(0xff, 0xff, 0xff); // Black + minorTickColor.setRgb(0xcc, 0xcc, 0xcc); // Black + minimumLevel = -60.0; // -60 dB + warningLevel = -20.0; // -20 dB + errorLevel = -9.0; // -9 dB + clipLevel = -0.5; // -0.5 dB + minimumInputLevel = -50.0; // -50 dB + peakDecayRate = 11.7; // 20 dB / 1.7 sec + magnitudeIntegrationTime = 0.3; // 99% in 300 ms + peakHoldDuration = 20.0; // 20 seconds + inputPeakHoldDuration = 1.0; // 1 second + + handleChannelCofigurationChange(); updateTimerRef = updateTimer.toStrongRef(); if (!updateTimerRef) { updateTimerRef = QSharedPointer::create(); @@ -305,35 +448,350 @@ VolumeMeter::~VolumeMeter() updateTimerRef->RemoveVolControl(this); } -void VolumeMeter::setLevels(float nmag, float npeak, float npeakHold) +void VolumeMeter::setLevels( + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float inputPeak[MAX_AUDIO_CHANNELS]) { uint64_t ts = os_gettime_ns(); QMutexLocker locker(&dataMutex); - mag += nmag; - peak += npeak; - peakHold += npeakHold; - multiple += 1.0f; - lastUpdateTime = ts; -} - -inline void VolumeMeter::calcLevels() -{ - uint64_t ts = os_gettime_ns(); - QMutexLocker locker(&dataMutex); - - if (lastUpdateTime && ts - lastUpdateTime > 1000000000) { - mag = peak = peakHold = 0.0f; - multiple = 1.0f; - lastUpdateTime = 0; + currentLastUpdateTime = ts; + for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { + currentMagnitude[channelNr] = magnitude[channelNr]; + currentPeak[channelNr] = peak[channelNr]; + currentInputPeak[channelNr] = inputPeak[channelNr]; } - if (multiple > 0.0f) { - curMag = mag / multiple; - curPeak = peak / multiple; - curPeakHold = peakHold / multiple; + // In case there are more updates then redraws we must make sure + // that the ballistics of peak and hold are recalculated. + locker.unlock(); + calculateBallistics(ts); +} - mag = peak = peakHold = multiple = 0.0f; +inline void VolumeMeter::resetLevels() +{ + currentLastUpdateTime = 0; + for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { + currentMagnitude[channelNr] = -M_INFINITE; + currentPeak[channelNr] = -M_INFINITE; + currentInputPeak[channelNr] = -M_INFINITE; + + displayMagnitude[channelNr] = -M_INFINITE; + displayPeak[channelNr] = -M_INFINITE; + displayPeakHold[channelNr] = -M_INFINITE; + displayPeakHoldLastUpdateTime[channelNr] = 0; + displayInputPeakHold[channelNr] = -M_INFINITE; + displayInputPeakHoldLastUpdateTime[channelNr] = 0; + } +} + +inline void VolumeMeter::handleChannelCofigurationChange() +{ + QMutexLocker locker(&dataMutex); + + int currentNrAudioChannels = obs_volmeter_get_nr_channels(obs_volmeter); + if (displayNrAudioChannels != currentNrAudioChannels) { + displayNrAudioChannels = currentNrAudioChannels; + + // Make room for 3 pixels high meter, with one pixel between + // each. Then 9 pixels below it for ticks and numbers. + setMinimumSize(130, displayNrAudioChannels * 4 + 8); + + resetLevels(); + } +} + +inline bool VolumeMeter::detectIdle(uint64_t ts) +{ + float timeSinceLastUpdate = (ts - currentLastUpdateTime) * 0.000000001; + if (timeSinceLastUpdate > 0.5) { + resetLevels(); + return true; + } else { + return false; + } +} + +inline void VolumeMeter::calculateBallisticsForChannel(int channelNr, + uint64_t ts, qreal timeSinceLastRedraw) +{ + if (currentPeak[channelNr] >= displayPeak[channelNr] || + isnan(displayPeak[channelNr])) { + // Attack of peak is immediate. + displayPeak[channelNr] = currentPeak[channelNr]; + } else { + // Decay of peak is 20 dB / 1.7 seconds. + qreal decay = peakDecayRate * timeSinceLastRedraw; + displayPeak[channelNr] = CLAMP(displayPeak[channelNr] - decay, + currentPeak[channelNr], 0); + } + + if (currentPeak[channelNr] >= displayPeakHold[channelNr] || + !isfinite(displayPeakHold[channelNr])) { + // Attack of peak-hold is immediate, but keep track + // when it was last updated. + displayPeakHold[channelNr] = currentPeak[channelNr]; + displayPeakHoldLastUpdateTime[channelNr] = ts; + } else { + // The peak and hold falls back to peak + // after 20 seconds. + qreal timeSinceLastPeak = (uint64_t)(ts - + displayPeakHoldLastUpdateTime[channelNr]) * 0.000000001; + if (timeSinceLastPeak > peakHoldDuration) { + displayPeakHold[channelNr] = currentPeak[channelNr]; + displayPeakHoldLastUpdateTime[channelNr] = ts; + } + } + + if (currentInputPeak[channelNr] >= displayInputPeakHold[channelNr] || + !isfinite(displayInputPeakHold[channelNr])) { + // Attack of peak-hold is immediate, but keep track + // when it was last updated. + displayInputPeakHold[channelNr] = currentInputPeak[channelNr]; + displayInputPeakHoldLastUpdateTime[channelNr] = ts; + } else { + // The peak and hold falls back to peak after 1 second. + qreal timeSinceLastPeak = (uint64_t)(ts - + displayInputPeakHoldLastUpdateTime[channelNr]) * + 0.000000001; + if (timeSinceLastPeak > inputPeakHoldDuration) { + displayInputPeakHold[channelNr] = + currentInputPeak[channelNr]; + displayInputPeakHoldLastUpdateTime[channelNr] = + ts; + } + } + + if (!isfinite(displayMagnitude[channelNr])) { + // The statements in the else-leg do not work with + // NaN and infinite displayMagnitude. + displayMagnitude[channelNr] = + currentMagnitude[channelNr]; + } else { + // A VU meter will integrate to the new value to 99% in 300 ms. + // The calculation here is very simplified and is more accurate + // with higher frame-rate. + qreal attack = (currentMagnitude[channelNr] - + displayMagnitude[channelNr]) * + (timeSinceLastRedraw / + magnitudeIntegrationTime) * 0.99; + displayMagnitude[channelNr] = CLAMP( + displayMagnitude[channelNr] + attack, + minimumLevel, 0); + } +} + +inline void VolumeMeter::calculateBallistics(uint64_t ts, + qreal timeSinceLastRedraw) +{ + QMutexLocker locker(&dataMutex); + + for (int channelNr = 0; channelNr < MAX_AUDIO_CHANNELS; channelNr++) { + calculateBallisticsForChannel(channelNr, ts, + timeSinceLastRedraw); + } +} + +void VolumeMeter::paintInputMeter(QPainter &painter, int x, int y, + int width, int height, float peakHold) +{ + QMutexLocker locker(&dataMutex); + + if (peakHold < minimumInputLevel) { + painter.fillRect(x, y, width, height, backgroundNominalColor); + } else if (peakHold < warningLevel) { + painter.fillRect(x, y, width, height, foregroundNominalColor); + } else if (peakHold < errorLevel) { + painter.fillRect(x, y, width, height, foregroundWarningColor); + } else if (peakHold <= clipLevel) { + painter.fillRect(x, y, width, height, foregroundErrorColor); + } else { + painter.fillRect(x, y, width, height, clipColor); + } +} + +void VolumeMeter::paintTicks(QPainter &painter, int x, int y, + int width, int height) +{ + qreal scale = width / minimumLevel; + + // Use a font that can be rendered small. + QFont font = QFont("Arial"); + font.setPixelSize(7); + + painter.setFont(font); + painter.setPen(majorTickColor); + + // Draw major tick lines and numeric indicators. + for (int i = 0; i > minimumLevel; i-= 5) { + int position = x + width - (i * scale) - 1; + char str[5]; + + snprintf(str, sizeof (str), "%i", i); + + if (i == 0 || i == 5) { + painter.drawText(position - 3, height, QString(str)); + } else { + painter.drawText(position - 5, height, QString(str)); + } + painter.drawLine(position, y, position, y + 2); + } + + // Draw minor tick lines. + painter.setPen(minorTickColor); + for (int i = 0; i > minimumLevel; i--) { + int position = x + width - (i * scale) - 1; + + if (i % 5 != 0) { + painter.drawLine(position, y, position, y + 1); + } + } +} + +void VolumeMeter::paintMeter(QPainter &painter, int x, int y, + int width, int height, float magnitude, float peak, float peakHold) +{ + qreal scale = width / minimumLevel; + + QMutexLocker locker(&dataMutex); + int minimumPosition = x + 0; + int maximumPosition = x + width; + int magnitudePosition = x + width - (magnitude * scale); + int peakPosition = x + width - (peak * scale); + int peakHoldPosition = x + width - (peakHold * scale); + int warningPosition = x + width - (warningLevel * scale); + int errorPosition = x + width - (errorLevel * scale); + + int nominalLength = warningPosition - minimumPosition; + int warningLength = errorPosition - warningPosition; + int errorLength = maximumPosition - errorPosition; + locker.unlock(); + + if (peakPosition < minimumPosition) { + painter.fillRect( + minimumPosition, y, + nominalLength, height, + backgroundNominalColor); + painter.fillRect( + warningPosition, y, + warningLength, height, + backgroundWarningColor); + painter.fillRect( + errorPosition, y, + errorLength, height, + backgroundErrorColor); + + } else if (peakPosition < warningPosition) { + painter.fillRect( + minimumPosition, y, + peakPosition - minimumPosition, height, + foregroundNominalColor); + painter.fillRect( + peakPosition, y, + warningPosition - peakPosition, height, + backgroundNominalColor); + painter.fillRect( + warningPosition, y, + warningLength, height, + backgroundWarningColor); + painter.fillRect(errorPosition, y, + errorLength, height, + backgroundErrorColor); + + } else if (peakPosition < errorPosition) { + painter.fillRect( + minimumPosition, y, + nominalLength, height, + foregroundNominalColor); + painter.fillRect( + warningPosition, y, + peakPosition - warningPosition, height, + foregroundWarningColor); + painter.fillRect( + peakPosition, y, + errorPosition - peakPosition, height, + backgroundWarningColor); + painter.fillRect( + errorPosition, y, + errorLength, height, + backgroundErrorColor); + + } else if (peakPosition < maximumPosition) { + painter.fillRect( + minimumPosition, y, + nominalLength, height, + foregroundNominalColor); + painter.fillRect( + warningPosition, y, + warningLength, height, + foregroundWarningColor); + painter.fillRect( + errorPosition, y, + peakPosition - errorPosition, height, + foregroundErrorColor); + painter.fillRect( + peakPosition, y, + maximumPosition - peakPosition, height, + backgroundErrorColor); + + } else { + painter.fillRect( + minimumPosition, y, + nominalLength, height, + foregroundNominalColor); + painter.fillRect( + warningPosition, y, + warningLength, height, + foregroundWarningColor); + painter.fillRect( + errorPosition, y, + errorLength, height, + foregroundErrorColor); + } + + if (peakHoldPosition - 3 < minimumPosition) { + // Peak-hold below minimum, no drawing. + + } else if (peakHoldPosition < warningPosition) { + painter.fillRect( + peakHoldPosition - 3, y, + 3, height, + foregroundNominalColor); + + } else if (peakHoldPosition < errorPosition) { + painter.fillRect( + peakHoldPosition - 3, y, + 3, height, + foregroundWarningColor); + + } else { + painter.fillRect( + peakHoldPosition - 3, y, + 3, height, + foregroundErrorColor); + } + + if (magnitudePosition - 3 < minimumPosition) { + // Magnitude below minimum, no drawing. + + } else if (magnitudePosition < warningPosition) { + painter.fillRect( + magnitudePosition - 3, y, + 3, height, + magnitudeColor); + + } else if (magnitudePosition < errorPosition) { + painter.fillRect( + magnitudePosition - 3, y, + 3, height, + magnitudeColor); + + } else { + painter.fillRect( + magnitudePosition - 3, y, + 3, height, + magnitudeColor); } } @@ -341,48 +799,54 @@ void VolumeMeter::paintEvent(QPaintEvent *event) { UNUSED_PARAMETER(event); - QPainter painter(this); - QLinearGradient gradient; + uint64_t ts = os_gettime_ns(); + qreal timeSinceLastRedraw = (ts - lastRedrawTime) * 0.000000001; int width = size().width(); int height = size().height(); - calcLevels(); + handleChannelCofigurationChange(); + calculateBallistics(ts, timeSinceLastRedraw); + bool idle = detectIdle(ts); - int scaledMag = int((float)width * curMag); - int scaledPeak = int((float)width * curPeak); - int scaledPeakHold = int((float)width * curPeakHold); + // Draw the ticks in a off-screen buffer when the widget changes size. + QSize tickPaintCacheSize = QSize(width - 5, 9); + if (tickPaintCache == NULL || + tickPaintCache->size() != tickPaintCacheSize) { + delete tickPaintCache; + tickPaintCache = new QPixmap(tickPaintCacheSize); - float db = obs_volmeter_get_cur_db(OBS_FADER_LOG, curPeakHold); + QColor clearColor(0, 0, 0, 0); + tickPaintCache->fill(clearColor); - gradient.setStart(qreal(scaledMag), 0); - gradient.setFinalStop(qreal(scaledPeak), 0); - gradient.setColorAt(0, db == 0.0f ? clipColor1 : magColor); - gradient.setColorAt(1, db == 0.0f ? clipColor2 : peakColor); + QPainter tickPainter(tickPaintCache); + paintTicks(tickPainter, 0, 0, tickPaintCacheSize.width(), + tickPaintCacheSize.height()); + tickPainter.end(); + } - // RMS - painter.fillRect(0, 0, - scaledMag, height, - db == 0.0f ? clipColor1 : magColor); + // Actual painting of the widget starts here. + QPainter painter(this); + painter.drawPixmap(5, height - 9, *tickPaintCache); - // RMS - Peak gradient - painter.fillRect(scaledMag, 0, - scaledPeak - scaledMag + 1, height, - QBrush(gradient)); + for (int channelNr = 0; channelNr < displayNrAudioChannels; + channelNr++) { + paintMeter(painter, + 5, channelNr * 4, width - 5, 3, + displayMagnitude[channelNr], displayPeak[channelNr], + displayPeakHold[channelNr]); - // Background - painter.fillRect(scaledPeak, 0, - width - scaledPeak, height, - bkColor); - - // Peak hold - if (peakHold == 1.0f) - scaledPeakHold--; - - painter.setPen(peakHoldColor); - painter.drawLine(scaledPeakHold, 0, - scaledPeakHold, height); + if (!idle) { + // By not drawing the input meter boxes the user can + // see that the audio stream has been stopped, without + // having too much visual impact. + paintInputMeter(painter, + 0, channelNr * 4, 3, 3, + displayInputPeakHold[channelNr]); + } + } + lastRedrawTime = ts; } void VolumeMeterTimer::AddVolControl(VolumeMeter *meter) diff --git a/UI/volume-control.hpp b/UI/volume-control.hpp index 02ada6ece..f565007c6 100644 --- a/UI/volume-control.hpp +++ b/UI/volume-control.hpp @@ -13,45 +13,178 @@ class VolumeMeterTimer; class VolumeMeter : public QWidget { Q_OBJECT - Q_PROPERTY(QColor bkColor READ getBkColor WRITE setBkColor DESIGNABLE true) - Q_PROPERTY(QColor magColor READ getMagColor WRITE setMagColor DESIGNABLE true) - Q_PROPERTY(QColor peakColor READ getPeakColor WRITE setPeakColor DESIGNABLE true) - Q_PROPERTY(QColor peakHoldColor READ getPeakHoldColor WRITE setPeakHoldColor DESIGNABLE true) - Q_PROPERTY(QColor clipColor1 READ getClipColor1 WRITE setClipColor1 DESIGNABLE true) - Q_PROPERTY(QColor clipColor2 READ getClipColor2 WRITE setClipColor2 DESIGNABLE true) + Q_PROPERTY(QColor backgroundNominalColor + READ getBackgroundNominalColor + WRITE setBackgroundNominalColor DESIGNABLE true) + Q_PROPERTY(QColor backgroundWarningColor + READ getBackgroundWarningColor + WRITE setBackgroundWarningColor DESIGNABLE true) + Q_PROPERTY(QColor backgroundErrorColor + READ getBackgroundErrorColor + WRITE setBackgroundErrorColor DESIGNABLE true) + Q_PROPERTY(QColor foregroundNominalColor + READ getForegroundNominalColor + WRITE setForegroundNominalColor DESIGNABLE true) + Q_PROPERTY(QColor foregroundWarningColor + READ getForegroundWarningColor + WRITE setForegroundWarningColor DESIGNABLE true) + Q_PROPERTY(QColor foregroundErrorColor + READ getForegroundErrorColor + WRITE setForegroundErrorColor DESIGNABLE true) + Q_PROPERTY(QColor clipColor + READ getClipColor + WRITE setClipColor DESIGNABLE true) + Q_PROPERTY(QColor magnitudeColor + READ getMagnitudeColor + WRITE setMagnitudeColor DESIGNABLE true) + Q_PROPERTY(QColor majorTickColor + READ getMajorTickColor + WRITE setMajorTickColor DESIGNABLE true) + Q_PROPERTY(QColor minorTickColor + READ getMinorTickColor + WRITE setMinorTickColor DESIGNABLE true) + + // Levels are denoted in dBFS. + Q_PROPERTY(qreal minimumLevel + READ getMinimumLevel + WRITE setMinimumLevel DESIGNABLE true) + Q_PROPERTY(qreal warningLevel + READ getWarningLevel + WRITE setWarningLevel DESIGNABLE true) + Q_PROPERTY(qreal errorLevel + READ getErrorLevel + WRITE setErrorLevel DESIGNABLE true) + Q_PROPERTY(qreal clipLevel + READ getClipLevel + WRITE setClipLevel DESIGNABLE true) + Q_PROPERTY(qreal minimumInputLevel + READ getMinimumInputLevel + WRITE setMinimumInputLevel DESIGNABLE true) + + // Rates are denoted in dB/second. + Q_PROPERTY(qreal peakDecayRate + READ getPeakDecayRate + WRITE setPeakDecayRate DESIGNABLE true) + + // Time in seconds for the VU meter to integrate over. + Q_PROPERTY(qreal magnitudeIntegrationTime + READ getMagnitudeIntegrationTime + WRITE setMagnitudeIntegrationTime DESIGNABLE true) + + // Duration is denoted in seconds. + Q_PROPERTY(qreal peakHoldDuration + READ getPeakHoldDuration + WRITE setPeakHoldDuration DESIGNABLE true) + Q_PROPERTY(qreal inputPeakHoldDuration + READ getInputPeakHoldDuration + WRITE setInputPeakHoldDuration DESIGNABLE true) private: + obs_volmeter_t *obs_volmeter; static QWeakPointer updateTimer; QSharedPointer updateTimerRef; - float curMag = 0.0f, curPeak = 0.0f, curPeakHold = 0.0f; - inline void calcLevels(); + inline void resetLevels(); + inline void handleChannelCofigurationChange(); + inline bool detectIdle(uint64_t ts); + inline void calculateBallistics(uint64_t ts, + qreal timeSinceLastRedraw=0.0); + inline void calculateBallisticsForChannel(int channelNr, + uint64_t ts, qreal timeSinceLastRedraw); + + void paintInputMeter(QPainter &painter, int x, int y, + int width, int height, float peakHold); + void paintMeter(QPainter &painter, int x, int y, + int width, int height, + float magnitude, float peak, float peakHold); + void paintTicks(QPainter &painter, int x, int y, int width, int height); QMutex dataMutex; - float mag = 0.0f, peak = 0.0f, peakHold = 0.0f; - float multiple = 0.0f; - uint64_t lastUpdateTime = 0; - QColor bkColor, magColor, peakColor, peakHoldColor; - QColor clipColor1, clipColor2; + uint64_t currentLastUpdateTime = 0; + float currentMagnitude[MAX_AUDIO_CHANNELS]; + float currentPeak[MAX_AUDIO_CHANNELS]; + float currentInputPeak[MAX_AUDIO_CHANNELS]; + + QPixmap *tickPaintCache = NULL; + int displayNrAudioChannels = 0; + float displayMagnitude[MAX_AUDIO_CHANNELS]; + float displayPeak[MAX_AUDIO_CHANNELS]; + float displayPeakHold[MAX_AUDIO_CHANNELS]; + uint64_t displayPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; + float displayInputPeakHold[MAX_AUDIO_CHANNELS]; + uint64_t displayInputPeakHoldLastUpdateTime[MAX_AUDIO_CHANNELS]; + + QColor backgroundNominalColor; + QColor backgroundWarningColor; + QColor backgroundErrorColor; + QColor foregroundNominalColor; + QColor foregroundWarningColor; + QColor foregroundErrorColor; + QColor clipColor; + QColor magnitudeColor; + QColor majorTickColor; + QColor minorTickColor; + qreal minimumLevel; + qreal warningLevel; + qreal errorLevel; + qreal clipLevel; + qreal minimumInputLevel; + qreal peakDecayRate; + qreal magnitudeIntegrationTime; + qreal peakHoldDuration; + qreal inputPeakHoldDuration; + + uint64_t lastRedrawTime = 0; public: - explicit VolumeMeter(QWidget *parent = 0); + explicit VolumeMeter(QWidget *parent = 0, + obs_volmeter_t *obs_volmeter = 0); ~VolumeMeter(); - void setLevels(float nmag, float npeak, float npeakHold); - QColor getBkColor() const; - void setBkColor(QColor c); - QColor getMagColor() const; - void setMagColor(QColor c); - QColor getPeakColor() const; - void setPeakColor(QColor c); - QColor getPeakHoldColor() const; - void setPeakHoldColor(QColor c); - QColor getClipColor1() const; - void setClipColor1(QColor c); - QColor getClipColor2() const; - void setClipColor2(QColor c); + void setLevels( + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float inputPeak[MAX_AUDIO_CHANNELS]); + + QColor getBackgroundNominalColor() const; + void setBackgroundNominalColor(QColor c); + QColor getBackgroundWarningColor() const; + void setBackgroundWarningColor(QColor c); + QColor getBackgroundErrorColor() const; + void setBackgroundErrorColor(QColor c); + QColor getForegroundNominalColor() const; + void setForegroundNominalColor(QColor c); + QColor getForegroundWarningColor() const; + void setForegroundWarningColor(QColor c); + QColor getForegroundErrorColor() const; + void setForegroundErrorColor(QColor c); + QColor getClipColor() const; + void setClipColor(QColor c); + QColor getMagnitudeColor() const; + void setMagnitudeColor(QColor c); + QColor getMajorTickColor() const; + void setMajorTickColor(QColor c); + QColor getMinorTickColor() const; + void setMinorTickColor(QColor c); + qreal getMinimumLevel() const; + void setMinimumLevel(qreal v); + qreal getWarningLevel() const; + void setWarningLevel(qreal v); + qreal getErrorLevel() const; + void setErrorLevel(qreal v); + qreal getClipLevel() const; + void setClipLevel(qreal v); + qreal getMinimumInputLevel() const; + void setMinimumInputLevel(qreal v); + qreal getPeakDecayRate() const; + void setPeakDecayRate(qreal v); + qreal getMagnitudeIntegrationTime() const; + void setMagnitudeIntegrationTime(qreal v); + qreal getPeakHoldDuration() const; + void setPeakHoldDuration(qreal v); + qreal getInputPeakHoldDuration() const; + void setInputPeakHoldDuration(qreal v); protected: void paintEvent(QPaintEvent *event); @@ -92,8 +225,10 @@ private: obs_volmeter_t *obs_volmeter; static void OBSVolumeChanged(void *param, float db); - static void OBSVolumeLevel(void *data, float level, float mag, - float peak, float muted); + static void OBSVolumeLevel(void *data, + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float inputPeak[MAX_AUDIO_CHANNELS]); static void OBSVolumeMuted(void *data, calldata_t *calldata); void EmitConfigClicked(); diff --git a/libobs/obs-audio-controls.c b/libobs/obs-audio-controls.c index 4abf8e872..bc67c7104 100644 --- a/libobs/obs-audio-controls.c +++ b/libobs/obs-audio-controls.c @@ -32,6 +32,8 @@ along with this program. If not, see . #pragma warning(disable : 4756) #endif +#define CLAMP(x, min, max) ((x) < min ? min : ((x) > max ? max : (x))) + typedef float (*obs_fader_conversion_t)(const float val); struct fader_cb { @@ -61,8 +63,6 @@ struct meter_cb { struct obs_volmeter { pthread_mutex_t mutex; - obs_fader_conversion_t pos_to_db; - obs_fader_conversion_t db_to_pos; obs_source_t *source; enum obs_fader_type type; float cur_db; @@ -70,20 +70,10 @@ struct obs_volmeter { pthread_mutex_t callback_mutex; DARRAY(struct meter_cb)callbacks; - unsigned int channels; unsigned int update_ms; - unsigned int update_frames; - unsigned int peakhold_ms; - unsigned int peakhold_frames; - unsigned int peakhold_count; - unsigned int ival_frames; - float ival_sum; - float ival_max; - - float vol_peak; - float vol_mag; - float vol_max; + float vol_magnitude[MAX_AUDIO_CHANNELS]; + float vol_peak[MAX_AUDIO_CHANNELS]; }; static float cubic_def_to_db(const float def) @@ -205,13 +195,14 @@ static void signal_volume_changed(struct obs_fader *fader, const float db) } static void signal_levels_updated(struct obs_volmeter *volmeter, - const float level, const float magnitude, const float peak, - bool muted) + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float input_peak[MAX_AUDIO_CHANNELS]) { pthread_mutex_lock(&volmeter->callback_mutex); for (size_t i = volmeter->callbacks.num; i > 0; i--) { struct meter_cb cb = volmeter->callbacks.array[i - 1]; - cb.callback(cb.param, level, magnitude, peak, muted); + cb.callback(cb.param, magnitude, peak, input_peak); } pthread_mutex_unlock(&volmeter->callback_mutex); } @@ -265,145 +256,84 @@ static void volmeter_source_destroyed(void *vptr, calldata_t *calldata) obs_volmeter_detach_source(volmeter); } -/* TODO: Separate for individual channels */ -static void volmeter_sum_and_max(float *data[MAX_AV_PLANES], size_t frames, - float *sum, float *max) -{ - float s = *sum; - float m = *max; - - for (size_t plane = 0; plane < MAX_AV_PLANES; plane++) { - if (!data[plane]) - break; - - for (float *c = data[plane]; c < data[plane] + frames; ++c) { - const float pow = *c * *c; - s += pow; - m = (m > pow) ? m : pow; - } - } - - *sum = s; - *max = m; -} - -/** - * @todo The IIR low pass filter has a different behavior depending on the - * update interval and sample rate, it should be replaced with something - * that is independent from both. - */ -static void volmeter_calc_ival_levels(obs_volmeter_t *volmeter) -{ - const unsigned int samples = volmeter->ival_frames * volmeter->channels; - const float alpha = 0.15f; - const float ival_max = sqrtf(volmeter->ival_max); - const float ival_rms = sqrtf(volmeter->ival_sum / (float)samples); - - if (ival_max > volmeter->vol_max) { - volmeter->vol_max = ival_max; - } else { - volmeter->vol_max = alpha * volmeter->vol_max + - (1.0f - alpha) * ival_max; - } - - if (volmeter->vol_max > volmeter->vol_peak || - volmeter->peakhold_count > volmeter->peakhold_frames) { - volmeter->vol_peak = volmeter->vol_max; - volmeter->peakhold_count = 0; - } else { - volmeter->peakhold_count += volmeter->ival_frames; - } - - volmeter->vol_mag = alpha * ival_rms + - volmeter->vol_mag * (1.0f - alpha); - - /* reset interval data */ - volmeter->ival_frames = 0; - volmeter->ival_sum = 0.0f; - volmeter->ival_max = 0.0f; -} - -static bool volmeter_process_audio_data(obs_volmeter_t *volmeter, +static void volmeter_process_audio_data(obs_volmeter_t *volmeter, const struct audio_data *data) { - bool updated = false; - size_t frames = 0; - size_t left = data->frames; - float *adata[MAX_AV_PLANES]; + int nr_samples = data->frames; + int channel_nr = 0; - for (size_t i = 0; i < MAX_AV_PLANES; i++) - adata[i] = (float*)data->data[i]; - - while (left) { - frames = (volmeter->ival_frames + left > - volmeter->update_frames) - ? volmeter->update_frames - volmeter->ival_frames - : left; - - volmeter_sum_and_max(adata, frames, &volmeter->ival_sum, - &volmeter->ival_max); - - volmeter->ival_frames += (unsigned int)frames; - left -= frames; - - for (size_t i = 0; i < MAX_AV_PLANES; i++) { - if (!adata[i]) - break; - adata[i] += frames; + for (size_t plane_nr = 0; plane_nr < MAX_AV_PLANES; plane_nr++) { + float *samples = (float *)data->data[plane_nr]; + if (!samples) { + // This plane does not contain data. + continue; } - /* break if we did not reach the end of the interval */ - if (volmeter->ival_frames != volmeter->update_frames) - break; + // For each plane calculate: + // * peak = the maximum-absolute of the sample values. + // * magnitude = root-mean-square of the sample values. + // A VU meter needs to integrate over 300ms, but this will + // be handled by the ballistics of the meter itself, + // reality. Which makes this calculation independent of + // sample rate or update rate. + float peak = 0.0; + float sum_of_squares = 0.0; + for (int sample_nr = 0; sample_nr < nr_samples; sample_nr++) { + float sample = samples[sample_nr]; - volmeter_calc_ival_levels(volmeter); - updated = true; + peak = fmaxf(peak, fabsf(sample)); + sum_of_squares += (sample * sample); + } + + volmeter->vol_magnitude[channel_nr] = sqrtf(sum_of_squares / + nr_samples); + volmeter->vol_peak[channel_nr] = peak; + channel_nr++; } - return updated; + // Clear audio channels that are not in use. + for (; channel_nr < MAX_AUDIO_CHANNELS; channel_nr++) { + volmeter->vol_magnitude[channel_nr] = 0.0; + volmeter->vol_peak[channel_nr] = 0.0; + } } static void volmeter_source_data_received(void *vptr, obs_source_t *source, const struct audio_data *data, bool muted) { struct obs_volmeter *volmeter = (struct obs_volmeter *) vptr; - bool updated = false; - float mul, level, mag, peak; + float mul; + float magnitude[MAX_AUDIO_CHANNELS]; + float peak[MAX_AUDIO_CHANNELS]; + float input_peak[MAX_AUDIO_CHANNELS]; pthread_mutex_lock(&volmeter->mutex); - updated = volmeter_process_audio_data(volmeter, data); + volmeter_process_audio_data(volmeter, data); - if (updated) { - mul = db_to_mul(volmeter->cur_db); - - level = volmeter->db_to_pos(mul_to_db(volmeter->vol_max * mul)); - mag = volmeter->db_to_pos(mul_to_db(volmeter->vol_mag * mul)); - peak = volmeter->db_to_pos( - mul_to_db(volmeter->vol_peak * mul)); + // Adjust magnitude/peak based on the volume level set by the user. + // And convert to dB. + mul = muted ? 0.0 : db_to_mul(volmeter->cur_db); + for (int channel_nr = 0; channel_nr < MAX_AUDIO_CHANNELS; + channel_nr++) { + magnitude[channel_nr] = mul_to_db( + volmeter->vol_magnitude[channel_nr] * mul); + peak[channel_nr] = mul_to_db( + volmeter->vol_peak[channel_nr] * mul); + input_peak[channel_nr] = mul_to_db( + volmeter->vol_peak[channel_nr]); } + // The input-peak is NOT adjusted with volume, so that the user + // can check the input-gain. + pthread_mutex_unlock(&volmeter->mutex); - if (updated) - signal_levels_updated(volmeter, level, mag, peak, muted); + signal_levels_updated(volmeter, magnitude, peak, input_peak); UNUSED_PARAMETER(source); } -static void volmeter_update_audio_settings(obs_volmeter_t *volmeter) -{ - audio_t *audio = obs_get_audio(); - const unsigned int sr = audio_output_get_sample_rate(audio); - uint32_t channels = (uint32_t)audio_output_get_channels(audio); - - pthread_mutex_lock(&volmeter->mutex); - volmeter->channels = channels; - volmeter->update_frames = volmeter->update_ms * sr / 1000; - volmeter->peakhold_frames = volmeter->peakhold_ms * sr / 1000; - pthread_mutex_unlock(&volmeter->mutex); -} - obs_fader_t *obs_fader_create(enum obs_fader_type type) { struct obs_fader *fader = bzalloc(sizeof(struct obs_fader)); @@ -634,28 +564,9 @@ obs_volmeter_t *obs_volmeter_create(enum obs_fader_type type) if (pthread_mutex_init(&volmeter->callback_mutex, NULL) != 0) goto fail; - /* set conversion functions */ - switch(type) { - case OBS_FADER_CUBIC: - volmeter->pos_to_db = cubic_def_to_db; - volmeter->db_to_pos = cubic_db_to_def; - break; - case OBS_FADER_IEC: - volmeter->pos_to_db = iec_def_to_db; - volmeter->db_to_pos = iec_db_to_def; - break; - case OBS_FADER_LOG: - volmeter->pos_to_db = log_def_to_db; - volmeter->db_to_pos = log_db_to_def; - break; - default: - goto fail; - break; - } volmeter->type = type; obs_volmeter_set_update_interval(volmeter, 50); - obs_volmeter_set_peak_hold(volmeter, 1500); return volmeter; fail: @@ -739,8 +650,6 @@ void obs_volmeter_set_update_interval(obs_volmeter_t *volmeter, pthread_mutex_lock(&volmeter->mutex); volmeter->update_ms = ms; pthread_mutex_unlock(&volmeter->mutex); - - volmeter_update_audio_settings(volmeter); } unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter) @@ -755,28 +664,26 @@ unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter) return interval; } -void obs_volmeter_set_peak_hold(obs_volmeter_t *volmeter, const unsigned int ms) +int obs_volmeter_get_nr_channels(obs_volmeter_t *volmeter) { - if (!volmeter) - return; + int source_nr_audio_channels; + int obs_nr_audio_channels; - pthread_mutex_lock(&volmeter->mutex); - volmeter->peakhold_ms = ms; - pthread_mutex_unlock(&volmeter->mutex); + if (volmeter->source) { + source_nr_audio_channels = get_audio_channels( + volmeter->source->sample_info.speakers); + } else { + source_nr_audio_channels = 1; + } - volmeter_update_audio_settings(volmeter); -} + struct obs_audio_info audio_info; + if (obs_get_audio_info(&audio_info)) { + obs_nr_audio_channels = get_audio_channels(audio_info.speakers); + } else { + obs_nr_audio_channels = 2; + } -unsigned int obs_volmeter_get_peak_hold(obs_volmeter_t *volmeter) -{ - if (!volmeter) - return 0; - - pthread_mutex_lock(&volmeter->mutex); - const unsigned int peakhold = volmeter->peakhold_ms; - pthread_mutex_unlock(&volmeter->mutex); - - return peakhold; + return CLAMP(source_nr_audio_channels, 1, obs_nr_audio_channels); } void obs_volmeter_add_callback(obs_volmeter_t *volmeter, @@ -805,26 +712,3 @@ void obs_volmeter_remove_callback(obs_volmeter_t *volmeter, pthread_mutex_unlock(&volmeter->callback_mutex); } -float obs_volmeter_get_cur_db(enum obs_fader_type type, const float def) -{ - float db; - - switch(type) { - case OBS_FADER_CUBIC: - db = cubic_def_to_db(def); - break; - case OBS_FADER_IEC: - db = iec_def_to_db(def); - break; - case OBS_FADER_LOG: - db = log_def_to_db(def); - break; - default: - goto fail; - break; - } - - return db; -fail: - return -INFINITY; -} diff --git a/libobs/obs-audio-controls.h b/libobs/obs-audio-controls.h index 2dd603dc8..158af7bf7 100644 --- a/libobs/obs-audio-controls.h +++ b/libobs/obs-audio-controls.h @@ -229,30 +229,21 @@ EXPORT void obs_volmeter_set_update_interval(obs_volmeter_t *volmeter, EXPORT unsigned int obs_volmeter_get_update_interval(obs_volmeter_t *volmeter); /** - * @brief Set the peak hold time for the volume meter + * @brief Get the number of channels which are configured for this source. * @param volmeter pointer to the volume meter object - * @param ms peak hold time in ms */ -EXPORT void obs_volmeter_set_peak_hold(obs_volmeter_t *volmeter, - const unsigned int ms); +EXPORT int obs_volmeter_get_nr_channels(obs_volmeter_t *volmeter); -/** - * @brief Get the peak hold time for the volume meter - * @param volmeter pointer to the volume meter object - * @return the peak hold time in ms - */ -EXPORT unsigned int obs_volmeter_get_peak_hold(obs_volmeter_t *volmeter); - -typedef void (*obs_volmeter_updated_t)(void *param, float level, - float magnitude, float peak, float muted); +typedef void (*obs_volmeter_updated_t)(void *param, + const float magnitude[MAX_AUDIO_CHANNELS], + const float peak[MAX_AUDIO_CHANNELS], + const float input_peak[MAX_AUDIO_CHANNELS]); EXPORT void obs_volmeter_add_callback(obs_volmeter_t *volmeter, obs_volmeter_updated_t callback, void *param); EXPORT void obs_volmeter_remove_callback(obs_volmeter_t *volmeter, obs_volmeter_updated_t callback, void *param); -EXPORT float obs_volmeter_get_cur_db(enum obs_fader_type type, const float def); - #ifdef __cplusplus } #endif