204 halp_meta(name,
"Loudness")
205 halp_meta(c_name,
"LoudnessMeter")
206 halp_meta(category,
"Monitoring/Audio")
207 halp_meta(author,
"ossia score, libebur128 (Jan Kokemüller)")
208 halp_meta(manual_url,
"")
211 "Loudness per ITU-R BS.1770 and EBU R128: momentary, short-term and "
212 "integrated loudness, loudness range and true peak")
213 halp_meta(uuid,
"a39302dc-502f-479a-b849-8eb0ea4cc7e3")
214 halp_flag(fully_custom_item);
219 halp::dynamic_audio_bus<
"Audio",
double> audio;
220 struct : halp::combobox_t<
"Mode", Mode>
224 std::string_view values[4]{mode_names[0], mode_names[1], mode_names[2], mode_names[3]};
225 Mode init{Mode::Numbers};
228 struct : halp::combobox_t<
"Standard", Standard>
230 halp_meta(description,
"What the readings are judged against: the Target, or the target and tolerance of a standard")
233 std::string_view values[5]{
234 standard_names[0], standard_names[1], standard_names[2], standard_names[3],
236 Standard init{Standard::Custom};
239 struct : halp::spinbox_f32<
"Target", halp::range{-40., 0., -23.}>
241 halp_meta(description,
"Target in LUFS, when Standard is Custom; a named standard sets its own")
243 struct : halp::combobox_t<
"Scale", Scale>
245 halp_meta(description,
"EBU Tech 3341 display range around the target")
248 std::string_view values[2]{scale_names[0], scale_names[1]};
249 Scale init{Scale::Ebu9};
252 struct : halp::toggle<
"Relative">
254 halp_meta(description,
"Show LU relative to the target instead of LUFS")
256 struct : halp::toggle_t<bool,
"Running", halp::toggle_setup{.init =
true}>
258 halp_meta(description,
"Integrate; off pauses the integrated loudness and range")
260 halp::impulse_button<
"Reset"> reset;
261 struct : halp::toggle_t<bool,
"Follow transport", halp::toggle_setup{.init =
true}>
263 halp_meta(description,
"Start a new integration whenever playback starts")
265 struct : halp::combobox_t<
"Layout", Ui::Monitor::ChannelLayout>
267 halp_meta(description,
"Speaker layout: the LFE is left out and surrounds weigh 1.5 dB more")
270 std::string_view values[7]{
271 Ui::Monitor::channel_layout_names[0], Ui::Monitor::channel_layout_names[1],
272 Ui::Monitor::channel_layout_names[2], Ui::Monitor::channel_layout_names[3],
273 Ui::Monitor::channel_layout_names[4], Ui::Monitor::channel_layout_names[5],
274 Ui::Monitor::channel_layout_names[6]};
275 Ui::Monitor::ChannelLayout init{Ui::Monitor::ChannelLayout::Auto};
282 halp::val_port<
"Momentary",
float> momentary;
283 halp::val_port<
"Short-term",
float> short_term;
284 halp::val_port<
"Integrated",
float> integrated;
285 struct : halp::val_port<
"Range",
float>
287 halp_meta(description,
"Loudness range (LRA), in LU")
289 struct : halp::val_port<
"True peak",
float>
291 halp_meta(description,
"Highest true peak of any channel since the last reset, in dBTP")
295 std::shared_ptr<LoudnessMonitor> monitor = std::make_shared<LoudnessMonitor>();
299 std::function<void(std::shared_ptr<LoudnessMonitor>)> request;
300 static void work(
const std::shared_ptr<LoudnessMonitor>& m) { m->recycle(); }
305 void operator()(ebur128_state* st)
const noexcept { ebur128_destroy(&st); }
309 std::unique_ptr<ebur128_state, state_deleter>
state;
310 static constexpr int block_frames = 512;
311 std::vector<double> interleaved = std::vector<double>(std::size_t(block_frames) * max_channels);
312 std::array<ossia::meters::true_peak, max_channels> tp;
314 std::array<double, max_channels>
tp_max{};
315 int mapped_channels{-1};
316 Ui::Monitor::ChannelLayout mapped_layout{};
318 double since_readings{};
329 const double r = s.rate > 0 ? s.rate : 48000.;
330 if(r == rate &&
state)
333 state.reset(makeState(rate));
337 monitor->rate = rate;
338 for(
auto& slot : monitor->spare)
339 if(
auto* old = slot.exchange(
nullptr))
340 ebur128_destroy(&old);
342 mapped_channels = -1;
353 void map_channels(
int nch)
355 const auto layout = inputs.layout.value;
356 if(nch == mapped_channels && layout == mapped_layout)
358 for(
int c = 0; c < max_channels; c++)
360 int type = EBUR128_UNUSED;
364 type = w == 0. ? EBUR128_UNUSED
365 : w > 1. ? EBUR128_LEFT_SURROUND
369 type = EBUR128_DUAL_MONO;
371 ebur128_set_channel(
state.get(), c, type);
373 mapped_channels = nch;
374 mapped_layout = layout;
377 void request_recycle()
379 if(worker.request && !monitor->recycling.exchange(
true))
380 worker.request(monitor);
386 const int slot = monitor->freeRetiredSlot();
389 ebur128_state* fresh = monitor->takeSpare((
unsigned long)rate);
392 monitor->retired[slot].store(
state.release());
397 mapped_channels = -1;
402 void operator()(
int frames)
404 if(!
state || frames <= 0)
406 const int nch = std::min(inputs.audio.channels, max_channels);
410 if(restart || inputs.reset || monitor->reset.exchange(
false, std::memory_order_relaxed))
417 const bool done =
clear();
425 const bool running = inputs.running.value;
426 readings.running = running;
429 for(
int done = 0; done < frames;)
431 const int n = std::min(frames - done, block_frames);
432 std::fill_n(interleaved.begin(), std::size_t(n) * max_channels, 0.);
433 for(
int c = 0; c < nch; c++)
435 const double* in = inputs.audio[c] + done;
436 for(
int i = 0; i < n; i++)
437 interleaved[std::size_t(i) * max_channels + c] = in[i];
439 ebur128_add_frames_double(
state.get(), interleaved.data(), n);
442 for(
int c = 0; c < nch; c++)
444 tp[c].process(inputs.audio[c], frames);
450 since_readings += frames;
451 if(since_readings < rate / 10.)
455 auto* st =
state.get();
457 if(ebur128_loudness_momentary(st, &v) == EBUR128_SUCCESS)
458 readings.momentary = v;
459 if(ebur128_loudness_shortterm(st, &v) == EBUR128_SUCCESS)
460 readings.short_term = v;
461 if(ebur128_loudness_global(st, &v) == EBUR128_SUCCESS)
462 readings.integrated = v;
463 if(ebur128_loudness_range(st, &v) == EBUR128_SUCCESS)
466 for(
int c = 0; c < nch; c++)
467 peak = std::max(peak,
tp_max[c]);
468 readings.true_peak = ossia::detail::LinearGainToDecibels(std::max(peak, 1e-10));
469 if(running && std::isfinite(readings.short_term) && readings.short_term > histogram_low)
471 const int bin = int((readings.short_term - histogram_low) / histogram_step);
472 if(bin >= 0 && bin < histogram_bins)
473 readings.histogram[bin]++;
476 if(monitor->watched())
477 monitor->readings.produce(readings);
479 outputs.momentary.value = float(lufsToDb(readings.momentary));
480 outputs.short_term.value = float(lufsToDb(readings.short_term));
481 outputs.integrated.value = float(lufsToDb(readings.integrated));
482 outputs.range.value = float(readings.range);
483 outputs.true_peak.value = float(readings.true_peak);
Definition ProcessContext.hpp:12