libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
audiometer.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_PROAV
13#include <cmath>
14#include <cstddef>
15#include <promeki/atomic.h>
16#include <promeki/namespace.h>
17#include <promeki/sharedptr.h>
18#include <promeki/uniqueptr.h>
19
20PROMEKI_NAMESPACE_BEGIN
21
46class AudioMeter {
47 PROMEKI_SHARED_BASE(AudioMeter)
48 public:
50 using Ptr = SharedPtr<AudioMeter, /*CopyOnWrite=*/false>;
51
52 virtual ~AudioMeter() = default;
53
61 virtual void process(const float *samples, size_t frames, size_t channels) = 0;
62
64 virtual void reset() = 0;
65};
66
99class AudioPeakRmsMeter : public AudioMeter {
100 PROMEKI_SHARED_DERIVED(AudioPeakRmsMeter)
101 public:
103 using Ptr = SharedPtr<AudioPeakRmsMeter, /*CopyOnWrite=*/false>;
104
106 explicit AudioPeakRmsMeter(size_t channels) { setChannels(channels); }
107
108 AudioPeakRmsMeter(const AudioPeakRmsMeter &) = delete;
109 AudioPeakRmsMeter &operator=(const AudioPeakRmsMeter &) = delete;
110
112 void setChannels(size_t channels) {
113 _channels = channels;
114 // Value-init form invokes each Atomic<float>'s
115 // explicit default constructor (T{} = 0.0f), so
116 // no follow-up zero-fill loop is needed.
117 _peak = MeterArray::createArrayValueInit(channels);
118 _rms = MeterArray::createArrayValueInit(channels);
119 }
120
122 size_t channels() const { return _channels; }
123
125 float peak(size_t ch) const {
126 if (ch >= _channels) return 0.0f;
127 return _peak[ch].load(MemoryOrder::Relaxed);
128 }
129
131 float rms(size_t ch) const {
132 if (ch >= _channels) return 0.0f;
133 return _rms[ch].load(MemoryOrder::Relaxed);
134 }
135
146 void setPeakDecay(float decay) { _peakDecay = decay; }
147
156 void setRmsWindow(size_t windowFrames) {
157 if (windowFrames == 0) windowFrames = 1;
158 _rmsAlpha = 1.0f / static_cast<float>(windowFrames);
159 }
160
162 void process(const float *samples, size_t frames, size_t channels) override {
163 if (channels > _channels) channels = _channels;
164 if (frames == 0 || channels == 0) return;
165
166 const float decay = _peakDecay;
167 const float alpha = _rmsAlpha;
168
169 for (size_t c = 0; c < channels; ++c) {
170 float peak = _peak[c].load(MemoryOrder::Relaxed) * decay;
171 // Convert held RMS back to mean-square for accumulation.
172 float held = _rms[c].load(MemoryOrder::Relaxed);
173 float msq = held * held;
174 for (size_t f = 0; f < frames; ++f) {
175 float s = samples[f * channels + c];
176 float a = std::fabs(s);
177 if (a > peak) peak = a;
178 msq = msq + alpha * (s * s - msq);
179 }
180 _peak[c].store(peak, MemoryOrder::Relaxed);
181 _rms[c].store(std::sqrt(msq < 0.0f ? 0.0f : msq), MemoryOrder::Relaxed);
182 }
183 }
184
186 void reset() override {
187 for (size_t i = 0; i < _channels; ++i) {
188 _peak[i].store(0.0f, MemoryOrder::Relaxed);
189 _rms[i].store(0.0f, MemoryOrder::Relaxed);
190 }
191 }
192
193 private:
194 // Atomic<float> is non-movable, so the per-channel
195 // arrays are held by UniquePtr<T[]> — List<T> and
196 // most other container wrappers require movable
197 // elements and would not compile here.
198 using MeterArray = promeki::UniquePtr<promeki::Atomic<float>[]>;
199
200 size_t _channels = 0;
201 MeterArray _peak;
202 MeterArray _rms;
203 float _peakDecay = 1.0f;
204 float _rmsAlpha = 1.0f / 4800.0f;
205};
206
207PROMEKI_NAMESPACE_END
208
209#endif // PROMEKI_ENABLE_PROAV