127 halp_meta(name,
"Audio meter")
128 halp_meta(c_name,
"VUMeter")
129 halp_meta(category,
"Monitoring/Audio")
130 halp_meta(author,
"ossia score")
131 halp_meta(manual_url,
"")
134 "Multichannel level meter: sample peak and RMS, true peak, VU, IEC "
135 "programme meters (DIN, Nordic, BBC, EBU), K-System, surround and bit "
137 halp_meta(uuid,
"0d0a3152-8ee9-4472-8a97-457b8bd6e56a")
138 halp_flag(fully_custom_item);
143 halp::dynamic_audio_bus<
"Audio",
double> audio;
145 struct : halp::combobox_t<
"Mode", Mode>
147 halp_meta(description,
"Which meter: each follows its own standard's ballistics and scale.")
150 std::string_view values[std::size(mode_names)]{
151 mode_names[0], mode_names[1], mode_names[2], mode_names[3],
152 mode_names[4], mode_names[5], mode_names[6], mode_names[7],
153 mode_names[8], mode_names[9], mode_names[10], mode_names[11]};
154 Mode init{Mode::PeakRms};
158 struct : halp::spinbox_f32<
"Reference", halp::range{-40., 0., -18.}>
162 "Alignment level in dBFS: what reads 0 VU, TEST on Nordic and EBU "
163 "meters, 4 on BBC meters and -9 on DIN meters.")
166 struct : halp::spinbox_f32<
"Floor", halp::range{-140., -6., -60.}>
168 halp_meta(description,
"Bottom of the dBFS scale")
170 struct : halp::spinbox_f32<
"Ceiling", halp::range{-6., 24., 0.}>
172 halp_meta(description,
"Top of the dBFS scale")
175 struct : halp::spinbox_f32<
"Hold", halp::range{0., 30., 1.5}>
177 halp_meta(description,
"Seconds a peak stays marked before falling")
179 struct : halp::spinbox_f32<
"Fall", halp::range{1., 60., 20.}>
181 halp_meta(description,
"Fall rate of sample and true peak bars, in dB/s")
184 halp::impulse_button<
"Reset clips"> reset;
186 struct : halp::combobox_t<
"Layout", Ui::Monitor::ChannelLayout>
188 halp_meta(description,
"Speaker layout: channel names and surround positions")
191 std::string_view values[7]{
192 Ui::Monitor::channel_layout_names[0], Ui::Monitor::channel_layout_names[1],
193 Ui::Monitor::channel_layout_names[2], Ui::Monitor::channel_layout_names[3],
194 Ui::Monitor::channel_layout_names[4], Ui::Monitor::channel_layout_names[5],
195 Ui::Monitor::channel_layout_names[6]};
196 Ui::Monitor::ChannelLayout init{Ui::Monitor::ChannelLayout::Auto};
204 struct : halp::val_port<
"Levels", std::optional<std::vector<float>>>
206 halp_meta(description,
"Level of each channel on the current scale, about 30 times a second")
208 struct : halp::val_port<
"Clip",
bool>
210 halp_meta(description,
"Whether any channel went over full scale since the last reset")
214 std::shared_ptr<MeterMonitor> monitor = std::make_shared<MeterMonitor>();
218 ossia::meters::sample_peak peak;
219 ossia::meters::rms rms;
220 ossia::meters::vu vu;
221 ossia::meters::ppm ppm;
222 ossia::meters::true_peak tp;
223 ossia::meters::peak_hold hold;
224 ossia::meters::correlation ring_corr;
229 std::vector<Channel> channels = std::vector<Channel>(max_channels);
233 ossia::meters::true_peak tp_proto;
236 Mode current{Mode::PeakRms};
237 float current_hold{-1}, current_fall{-1};
240 double since_output{};
247 const double r = s.rate > 0 ? s.rate : 48000.;
251 ppm_type_i.prepare(rate, ossia::meters::ppm::type_i);
252 ppm_type_ii.prepare(rate, ossia::meters::ppm::type_ii);
253 tp_proto.prepare(rate);
257 void reset_followers() noexcept
259 const auto& ppm_proto
260 = (current == Mode::PpmDin || current == Mode::PpmNordic) ?
ppm_type_i
262 const bool k = current == Mode::K20 || current == Mode::K14 || current == Mode::K12;
263 for(
auto& c : channels)
265 c.rms.prepare(rate, k ? 0.6 : 0.3, k);
270 c.ring_corr.prepare(rate, 0.3);
284 const double fall = ossia::meters::fall_coefficient(inputs.fall.value, rate);
285 for(
auto& c : channels)
288 c.hold.hold_frames = inputs.hold.value * rate;
289 c.hold.fall_per_frame = fall;
292 current_hold = inputs.hold.value;
293 current_fall = inputs.fall.value;
296 void operator()(
int frames)
298 const int nch = std::min(inputs.audio.channels, max_channels);
299 if(nch == 0 || frames <= 0)
302 if(inputs.mode.value != current)
304 current = inputs.mode.value;
307 else if(inputs.hold.value != current_hold || inputs.fall.value != current_fall)
311 if(inputs.reset || monitor->reset_clips.exchange(
false, std::memory_order_relaxed))
314 const double dc_coef = 1. - std::exp(-frames / (1. * rate));
316 for(
int c = 0; c < nch; c++)
318 auto& ch = channels[c];
319 const double* in = inputs.audio[c];
320 ch.peak.process(in, frames);
321 double block_peak = 0;
322 for(
int i = 0; i < frames; i++)
323 block_peak = std::max(block_peak, std::abs(in[i]));
325 float level{}, secondary{};
326 double clip_level = block_peak;
331 ch.rms.process(in, frames);
332 level = ch.peak.level;
333 secondary = ch.rms.level();
334 ch.hold.feed(block_peak, frames);
337 ch.tp.process(in, frames);
338 ch.tp_level = std::max(ch.tp.block_peak, ch.tp_level * std::pow(tp_fall, frames));
340 secondary = ch.peak.level;
341 clip_level = ch.tp.block_peak;
342 ch.hold.feed(ch.tp.block_peak, frames);
345 ch.vu.process(in, frames);
346 level = ch.vu.level();
347 ch.hold.feed(level, frames);
350 case Mode::PpmNordic:
353 ch.ppm.process(in, frames);
354 level = ch.ppm.level();
355 ch.hold.feed(level, frames);
360 ch.rms.process(in, frames);
361 level = ch.rms.level();
362 secondary = ch.peak.level;
363 ch.hold.feed(block_peak, frames);
366 level = ch.peak.level;
369 for(
int i = 0; i < frames; i++)
372 const auto q = (int64_t)std::llround(std::clamp(in[i], -1., 1.) * 8388607.);
373 bits |= uint32_t(q < 0 ? -q : q);
376 ch.dc += dc_coef * (sum / frames - ch.dc);
377 ch.hold.feed(block_peak, frames);
382 clipped |= (uint64_t(1) << c);
384 frame.level[c] = level;
385 frame.secondary[c] = secondary;
386 frame.hold[c] = ch.hold.value;
387 frame.bits[c] = ch.bits;
391 if(current == Mode::Surround)
393 const auto r = Ui::Monitor::ring(inputs.layout.value, nch);
394 for(
int i = 0; i < r.size; i++)
396 const int a = r.channel[i], b = r.channel[(i + 1) % r.size];
397 auto& corr = channels[a].ring_corr;
398 corr.process(inputs.audio[a], inputs.audio[b], frames);
399 frame.ring_correlation[i] = corr.value();
403 frame.channels = nch;
404 frame.mode = current;
405 frame.clipped = clipped;
406 if(monitor->watched())
407 monitor->frames.produce(frame);
409 outputs.clip.value = clipped != 0;
413 since_output += frames;
414 if(since_output >= rate / 30.)
417 auto& out = outputs.levels.value.emplace(nch);
418 for(
int c = 0; c < nch; c++)
419 out[c] =
float(toScale(current, frame.level[c], inputs.reference.value));