// Copyright (C) 2025 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause #ifndef AUDIOLEVELMETER_H #define AUDIOLEVELMETER_H #include #include #include #include #include #include QT_BEGIN_NAMESPACE class QToolButton; class QLabel; QT_END_NAMESPACE using namespace std::chrono_literals; using std::chrono::milliseconds; // Constants used by AudioLevelMeter and MeterChannel static constexpr int WIDGET_WIDTH = 34; static constexpr int MAX_CHANNELS = 8; static constexpr auto PEAK_COLOR = "#1F9B5D"; static constexpr auto RMS_COLOR = "#28C878"; static constexpr milliseconds RMS_WINDOW = 400ms; static constexpr milliseconds PEAK_LABEL_HOLD_TIME = 2000ms; static constexpr milliseconds DECAY_EASE_IN_TIME = 160ms; static constexpr milliseconds UPDATE_INTERVAL = 16ms; // Assuming 60 Hz refresh rate. static constexpr float DB_DECAY_PER_SECOND = 20.0f; static constexpr float DB_DECAY_PER_UPDATE = DB_DECAY_PER_SECOND / (1000 / UPDATE_INTERVAL.count()); static constexpr float DB_MAX = 0.0f; static constexpr float DB_MIN = -60.0f; // A struct used by BufferAnalyzer to emit its results back to AudioLevelMeter struct BufferValues { BufferValues(int nChannels) : peaks(nChannels, 0.0f), squares(nChannels, 0.0f) {} std::vector peaks; std::vector squares; }; // A worker class analyzing incoming buffers on a separate worker thread class BufferAnalyzer : public QObject { Q_OBJECT public: void requestStop() { m_stopRequested.store(true); } public slots: void analyzeBuffer(const QAudioBuffer &buffer, int maxChannelsToAnalyze); signals: void valuesReady(const BufferValues &values); private: std::atomic m_stopRequested = false; }; // A custom QWidget representing an audio channel in the audio level meter. It serves // both as a model for the channels's peak and RMS values and as a view using the overrided // paintEvent() class MeterChannel : public QWidget { Q_OBJECT friend class AudioLevelMeter; private: explicit MeterChannel(QWidget *parent = nullptr); void paintEvent(QPaintEvent *event) override; void activate(); void deactivate(); void updatePeak(float peak); void updateRms(float sumOfSquaresForOneBuffer, milliseconds duration, int frameCount); void clearRmsData(); void decayPeak(); void decayRms(); float normalize(float dB); float m_peakDecayRate = 0.0f; float m_rmsDecayRate = 0.0f; float m_peak = DB_MIN; float m_rms = DB_MIN; float m_sumOfSquares = 0.0f; std::queue m_sumOfSquaresQueue{}; QBrush m_peakBrush{PEAK_COLOR}; QBrush m_rmsBrush{RMS_COLOR}; }; // The audio level meter“s parent widget class. It acts as a controller // for the MeterChannel widgets and the BufferAnalyzer worker. class AudioLevelMeter : public QWidget { Q_OBJECT public: explicit AudioLevelMeter(QWidget *parent = nullptr); ~AudioLevelMeter() { m_analyzerThread.requestInterruption(); m_bufferAnalyzer->requestStop(); m_analyzerThread.quit(); m_analyzerThread.wait(); }; signals: void newBuffer(const QAudioBuffer &buffer, int nChannels); public slots: void onAudioBufferReceived(const QAudioBuffer &buffer); void deactivate(); private slots: void toggleOnOff(); void updateValues(const BufferValues &values); void updateBars(); void resetPeakLabel(); private: void activate(); void updateChannelCount(int nCh); void updatePeakLabel(float peak); void clearAllRmsData(); bool m_isOn = true; bool m_isActive = false; QList m_channels{MAX_CHANNELS}; int m_channelCount = 0; milliseconds m_bufferDurationMs = 0ms; int m_frameCount = 0; float m_highestPeak = 0.0f; QTimer m_updateTimer; QTimer m_deactivationTimer; QTimer m_peakLabelHoldTimer; QLabel *m_peakLabel = nullptr; QToolButton *m_onOffButton = nullptr; BufferAnalyzer *m_bufferAnalyzer = nullptr; QThread m_analyzerThread; }; #endif // AUDIOLEVELMETER_H