Skip to content

Commit 1090f63

Browse files
committed
add level meter + various tweaks
1 parent ac7ab1f commit 1090f63

File tree

7 files changed

+452
-173
lines changed

7 files changed

+452
-173
lines changed

data/locale/en-US.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ display_mode="Display Mode"
6262
curve="Curve"
6363
bars="Bars"
6464
stepped_bars="Stepped Bars"
65+
level_meter="Level Meter"
66+
stepped_level_meter="Stepped Level Meter"
67+
68+
rms_mode="RMS Mode"
69+
meter_buf="Meter Buffer"
6570

6671
bar_width="Bar Width"
6772
bar_gap="Bar Gap"

src/settings.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,11 @@ static inline bool p_equ(const char *s1, const char *s2) { return std::strcmp(s1
8686
#define P_CURVE "curve"
8787
#define P_BARS "bars"
8888
#define P_STEP_BARS "stepped_bars"
89+
#define P_LEVEL_METER "level_meter"
90+
#define P_STEPPED_METER "stepped_level_meter"
91+
92+
#define P_RMS_MODE "rms_mode"
93+
#define P_METER_BUF "meter_buf"
8994

9095
#define P_BAR_WIDTH "bar_width"
9196
#define P_BAR_GAP "bar_gap"

src/source.cpp

Lines changed: 325 additions & 143 deletions
Large diffs are not rendered by default.

src/source.hpp

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ enum class DisplayMode
6767
{
6868
CURVE,
6969
BAR,
70-
STEPPED_BAR
70+
STEPPED_BAR,
71+
METER,
72+
STEPPED_METER
7173
};
7274

7375
class WAVSource
@@ -88,16 +90,25 @@ class WAVSource
8890
circlebuf m_capturebufs[2]{};
8991
uint32_t m_capture_channels = 0; // audio input channels
9092
uint32_t m_output_channels = 0; // fft output channels (*not* display channels)
91-
bool m_output_bus_captured = false; // do we have an active audio output callback? (via audio_output_connect())
93+
bool m_output_bus_captured = false; // do we have an active audio output callback? (via audio_output_connect())
9294

9395
// 32-byte aligned buffers for FFT/AVX processing
9496
AVXBufR m_fft_input;
9597
AVXBufC m_fft_output;
9698
fftwf_plan m_fft_plan{};
9799
AVXBufR m_window_coefficients;
98100
AVXBufR m_tsmooth_buf[2]; // last frames magnitudes
99-
AVXBufR m_decibels[2]; // dBFS
100-
size_t m_fft_size = 0; // number of fft elements (not bytes, multiple of 16)
101+
AVXBufR m_decibels[2]; // dBFS, or audio sample buffer in meter mode
102+
size_t m_fft_size = 0; // number of fft elements, or audio samples in meter mode (not bytes, multiple of 16)
103+
// in meter mode m_fft_size is the size of of the circular buffer in samples
104+
105+
// meter mode
106+
size_t m_meter_pos[2] = { 0, 0 }; // circular buffer position (per channel)
107+
float m_meter_val[2] = { 0.0f, 0.0f }; // dBFS
108+
float m_meter_buf[2] = { 0.0f, 0.0f }; // EMA
109+
bool m_meter_rms = false; // RMS mode
110+
bool m_meter_mode = false; // either meter or stepped meter display mode is selected
111+
int m_meter_ms = 100; // milliseconds of audio data to buffer
101112

102113
// video fps
103114
double m_fps = 0.0;
@@ -116,9 +127,6 @@ class WAVSource
116127
int m_retries = 0;
117128
float m_next_retry = 0.0f;
118129

119-
// vertex buffer
120-
gs_vertbuffer_t *m_vbuf = nullptr;
121-
122130
// settings
123131
RenderMode m_render_mode = RenderMode::SOLID;
124132
FFTWindow m_window_func = FFTWindow::HANN;
@@ -160,18 +168,26 @@ class WAVSource
160168
// slope
161169
AVXBufR m_slope_modifiers;
162170

171+
// rounded caps
172+
float m_cap_radius = 0.0f;
173+
int m_cap_tris = 4; // number of triangles each cap is composed of (4 min)
174+
std::vector<vec2> m_cap_verts; // pre-rotated cap vertices (to be translated to final pos)
175+
163176
void get_settings(obs_data_t *settings);
164177

165178
void recapture_audio();
166179
void release_audio_capture();
167-
bool check_audio_capture(float seconds);
180+
bool check_audio_capture(float seconds); // check if capture is valid and retry if not
168181
void free_bufs();
169182

170183
void init_interp(unsigned int sz);
171184

172185
void render_curve(gs_effect_t *effect);
173186
void render_bars(gs_effect_t *effect);
174187

188+
virtual void tick_spectrum(float) = 0; // process audio data in frequency spectrum mode
189+
virtual void tick_meter(float); // process audio data in meter mode
190+
175191
// constants
176192
static const float DB_MIN;
177193
static constexpr auto RETRY_DELAY = 2.0f;
@@ -197,7 +213,7 @@ class WAVSource
197213

198214
// main callbacks
199215
virtual void update(obs_data_t *settings);
200-
virtual void tick(float seconds) = 0;
216+
virtual void tick(float seconds);
201217
virtual void render(gs_effect_t *effect);
202218

203219
void show();
@@ -224,7 +240,7 @@ class WAVSourceAVX2 : public WAVSource
224240
using WAVSource::WAVSource;
225241
~WAVSourceAVX2() override {}
226242

227-
void tick(float seconds) override;
243+
void tick_spectrum(float seconds) override;
228244
};
229245

230246
class WAVSourceAVX : public WAVSource
@@ -233,7 +249,7 @@ class WAVSourceAVX : public WAVSource
233249
using WAVSource::WAVSource;
234250
~WAVSourceAVX() override {}
235251

236-
void tick(float seconds) override;
252+
void tick_spectrum(float seconds) override;
237253
};
238254

239255
class WAVSourceSSE2 : public WAVSource
@@ -242,5 +258,6 @@ class WAVSourceSSE2 : public WAVSource
242258
using WAVSource::WAVSource;
243259
~WAVSourceSSE2() override {}
244260

245-
void tick(float seconds) override;
261+
void tick_spectrum(float seconds) override;
262+
void tick_meter(float seconds) override;
246263
};

src/source_avx.cpp

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
// adaptation of WAVSourceAVX2 to support CPUs without AVX2
2525
// see comments of WAVSourceAVX2
2626
DECORATE_AVX
27-
void WAVSourceAVX::tick(float seconds)
27+
void WAVSourceAVX::tick_spectrum(float seconds)
2828
{
29-
std::lock_guard lock(m_mtx);
29+
//std::lock_guard lock(m_mtx); // now locked in tick()
3030
if(!check_audio_capture(seconds))
3131
return;
3232

@@ -122,7 +122,7 @@ void WAVSourceAVX::tick(float seconds)
122122
// load 8 real/imaginary pairs and group the r/i components in the low/high halves
123123
// de-interleaving 256-bit float vectors is nigh impossible without AVX2, so we'll
124124
// use 128-bit vectors and merge them, but i question if this is better than a 128-bit loop
125-
const auto buf = (float*)&m_fft_output[i];
125+
const float *buf = &m_fft_output[i][0];
126126
auto chunk1 = _mm_load_ps(buf);
127127
auto chunk2 = _mm_load_ps(&buf[4]);
128128
auto rvec = _mm256_castps128_ps256(_mm_shuffle_ps(chunk1, chunk2, shuffle_mask_r)); // group octwords
@@ -141,9 +141,7 @@ void WAVSourceAVX::tick(float seconds)
141141
if(m_tsmoothing == TSmoothingMode::EXPONENTIAL)
142142
{
143143
if(m_fast_peaks)
144-
{
145144
_mm256_store_ps(&m_tsmooth_buf[channel][i], _mm256_max_ps(mag, _mm256_load_ps(&m_tsmooth_buf[channel][i])));
146-
}
147145

148146
mag = _mm256_fmadd_ps(g, _mm256_load_ps(&m_tsmooth_buf[channel][i]), _mm256_mul_ps(g2, mag));
149147
_mm256_store_ps(&m_tsmooth_buf[channel][i], mag);
@@ -168,7 +166,7 @@ void WAVSourceAVX::tick(float seconds)
168166
else if(m_capture_channels > 1)
169167
{
170168
for(size_t i = 0; i < outsz; ++i)
171-
m_decibels[0][i] = dbfs((m_decibels[0][i] + m_decibels[1][i]) / 2);
169+
m_decibels[0][i] = dbfs((m_decibels[0][i] + m_decibels[1][i]) * 0.5f);
172170
}
173171
else
174172
{

src/source_avx2.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
#include <cstring>
2323

2424
DECORATE_AVX2
25-
void WAVSourceAVX2::tick(float seconds)
25+
void WAVSourceAVX2::tick_spectrum(float seconds)
2626
{
27-
std::lock_guard lock(m_mtx);
27+
//std::lock_guard lock(m_mtx); // now locked in tick()
2828
if(!check_audio_capture(seconds))
2929
return;
3030

@@ -35,6 +35,7 @@ void WAVSourceAVX2::tick(float seconds)
3535
const auto outsz = m_fft_size / 2; // discard bins at nyquist and above
3636
constexpr auto step = sizeof(__m256) / sizeof(float);
3737

38+
// reset and stop processing when source is not being displayed
3839
if(!m_show)
3940
{
4041
if(m_last_silent)
@@ -124,7 +125,7 @@ void WAVSourceAVX2::tick(float seconds)
124125
{
125126
// this *should* be faster than 2x vgatherxxx instructions
126127
// load 8 real/imaginary pairs and group the r/i components in the low/high halves
127-
const auto buf = (float*)&m_fft_output[i];
128+
const float *buf = &m_fft_output[i][0]; // first element of complex (float[2])
128129
auto chunk1 = _mm256_permutevar8x32_ps(_mm256_load_ps(buf), shuffle_mask);
129130
auto chunk2 = _mm256_permutevar8x32_ps(_mm256_load_ps(&buf[step]), shuffle_mask);
130131

@@ -146,9 +147,7 @@ void WAVSourceAVX2::tick(float seconds)
146147
{
147148
// take new values immediately if larger
148149
if(m_fast_peaks)
149-
{
150150
_mm256_store_ps(&m_tsmooth_buf[channel][i], _mm256_max_ps(mag, _mm256_load_ps(&m_tsmooth_buf[channel][i])));
151-
}
152151

153152
// (gravity * oldval) + ((1 - gravity) * newval)
154153
mag = _mm256_fmadd_ps(g, _mm256_load_ps(&m_tsmooth_buf[channel][i]), _mm256_mul_ps(g2, mag));
@@ -176,7 +175,7 @@ void WAVSourceAVX2::tick(float seconds)
176175
else if(m_capture_channels > 1)
177176
{
178177
for(size_t i = 0; i < outsz; ++i)
179-
m_decibels[0][i] = dbfs((m_decibels[0][i] + m_decibels[1][i]) / 2);
178+
m_decibels[0][i] = dbfs((m_decibels[0][i] + m_decibels[1][i]) * 0.5f);
180179
}
181180
else
182181
{

src/source_sse2.cpp

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
// compatibility fallback using at most SSE2 instructions
2525
// see comments of WAVSourceAVX2
2626
DECORATE_SSE2
27-
void WAVSourceSSE2::tick(float seconds)
27+
void WAVSourceSSE2::tick_spectrum(float seconds)
2828
{
29-
std::lock_guard lock(m_mtx);
29+
//std::lock_guard lock(m_mtx); // now locked in tick()
3030
if(!check_audio_capture(seconds))
3131
return;
3232

@@ -120,7 +120,7 @@ void WAVSourceSSE2::tick(float seconds)
120120
for(size_t i = 0; i < outsz; i += step)
121121
{
122122
// load 4 real/imaginary pairs and pack the r/i components into separate vectors
123-
const auto buf = (float*)&m_fft_output[i];
123+
const float *buf = &m_fft_output[i][0];
124124
auto chunk1 = _mm_load_ps(buf);
125125
auto chunk2 = _mm_load_ps(&buf[4]);
126126
auto rvec = _mm_shuffle_ps(chunk1, chunk2, shuffle_mask_r);
@@ -135,9 +135,7 @@ void WAVSourceSSE2::tick(float seconds)
135135
if(m_tsmoothing == TSmoothingMode::EXPONENTIAL)
136136
{
137137
if(m_fast_peaks)
138-
{
139138
_mm_store_ps(&m_tsmooth_buf[channel][i], _mm_max_ps(mag, _mm_load_ps(&m_tsmooth_buf[channel][i])));
140-
}
141139

142140
mag = _mm_add_ps(_mm_mul_ps(g, _mm_load_ps(&m_tsmooth_buf[channel][i])), _mm_mul_ps(g2, mag));
143141
_mm_store_ps(&m_tsmooth_buf[channel][i], mag);
@@ -162,11 +160,86 @@ void WAVSourceSSE2::tick(float seconds)
162160
else if(m_capture_channels > 1)
163161
{
164162
for(size_t i = 0; i < outsz; ++i)
165-
m_decibels[0][i] = dbfs((m_decibels[0][i] + m_decibels[1][i]) / 2);
163+
m_decibels[0][i] = dbfs((m_decibels[0][i] + m_decibels[1][i]) * 0.5f);
166164
}
167165
else
168166
{
169167
for(size_t i = 0; i < outsz; ++i)
170168
m_decibels[0][i] = dbfs(m_decibels[0][i]);
171169
}
172170
}
171+
172+
void WAVSourceSSE2::tick_meter(float seconds)
173+
{
174+
if(!check_audio_capture(seconds))
175+
return;
176+
177+
if(m_capture_channels == 0)
178+
return;
179+
180+
const auto outsz = m_fft_size;
181+
182+
for(auto channel = 0u; channel < m_capture_channels; ++channel)
183+
{
184+
while(m_capturebufs[channel].size > 0)
185+
{
186+
auto consume = m_capturebufs[channel].size;
187+
auto max = (m_fft_size - m_meter_pos[channel]) * sizeof(float);
188+
if(consume >= max)
189+
{
190+
circlebuf_pop_front(&m_capturebufs[channel], &m_decibels[channel][m_meter_pos[channel]], max);
191+
m_meter_pos[channel] = 0;
192+
}
193+
else
194+
{
195+
circlebuf_pop_front(&m_capturebufs[channel], &m_decibels[channel][m_meter_pos[channel]], consume);
196+
m_meter_pos[channel] += consume / sizeof(float);
197+
}
198+
}
199+
}
200+
201+
if(!m_show)
202+
return;
203+
204+
for(auto channel = 0u; channel < m_capture_channels; ++channel)
205+
{
206+
if(m_meter_rms)
207+
{
208+
float sum = 0.0f;
209+
for(size_t i = 0; i < outsz; ++i)
210+
{
211+
auto val = m_decibels[channel][i];
212+
sum += val * val;
213+
}
214+
const auto g = m_gravity;
215+
const auto g2 = 1.0f - g;
216+
auto rms = std::sqrt(sum / m_fft_size);
217+
if(m_tsmoothing == TSmoothingMode::EXPONENTIAL)
218+
{
219+
if(!m_fast_peaks || (rms <= m_meter_buf[channel]))
220+
rms = (g * m_meter_buf[channel]) + (g2 * rms);
221+
}
222+
m_meter_buf[channel] = rms;
223+
m_meter_val[channel] = dbfs(rms);
224+
}
225+
else
226+
{
227+
const auto g = m_gravity;
228+
const auto g2 = 1.0f - g;
229+
float max = 0.0f;
230+
for(size_t i = 0; i < outsz; ++i)
231+
{
232+
auto val = std::abs(m_decibels[channel][i]);
233+
if(val > max)
234+
max = val;
235+
}
236+
if(m_tsmoothing == TSmoothingMode::EXPONENTIAL)
237+
{
238+
if(!m_fast_peaks || (max <= m_meter_buf[channel]))
239+
max = (g * m_meter_buf[channel]) + (g2 * max);
240+
}
241+
m_meter_buf[channel] = max;
242+
m_meter_val[channel] = dbfs(max);
243+
}
244+
}
245+
}

0 commit comments

Comments
 (0)