Loading...
Searching...
No Matches
Loudness.hpp
1#pragma once
2#include <Process/Monitoring/MonitorLink.hpp>
3
4#include <Effect/EffectLayer.hpp>
5
6#include <ossia/audio/meters.hpp>
7#include <Process/Monitoring/Monitor.hpp>
8
9#include <QColor>
10#include <QString>
11
12#include <Ui/Monitor/ChannelLayout.hpp>
13#include <Ui/Monitor/LayerHelpers.hpp>
14#include <ebur128.h>
15#include <halp/audio.hpp>
16#include <halp/controls.hpp>
17#include <halp/meta.hpp>
18
19#include <array>
20#include <atomic>
21#include <cmath>
22#include <functional>
23#include <memory>
24#include <vector>
25
26namespace Process
27{
28class ControlInlet;
29}
30
31namespace Ui::Loudness
32{
33enum class Mode
34{
35 Numbers,
36 History,
37 Histogram,
38 Radar
39};
40inline constexpr std::string_view mode_names[]{"Numbers", "History", "Histogram", "Radar"};
41
42enum class Standard
43{
44 EbuR128,
45 AtscA85,
46 Streaming,
47 Podcast,
48 Custom
49};
50inline constexpr std::string_view standard_names[]{
51 "EBU R128 (-23)", "ATSC A/85 (-24)", "Streaming (-14)", "Podcast (-16)", "Custom (Target)"};
52
53struct Target
54{
55 double lufs;
56 double tolerance;
57 double max_true_peak;
58};
59
60inline Target targetFor(Standard s, double custom)
61{
62 switch(s)
63 {
64 case Standard::EbuR128:
65 return {-23., 1., -1.};
66 case Standard::AtscA85:
67 return {-24., 2., -2.};
68 case Standard::Streaming:
69 return {-14., 1., -1.};
70 case Standard::Podcast:
71 return {-16., 1., -1.};
72 default:
73 return {custom, 1., -1.};
74 }
75}
76
77enum class Scale
78{
79 Ebu9,
80 Ebu18
81};
82inline constexpr std::string_view scale_names[]{"+9 scale", "+18 scale"};
83
84static constexpr int max_channels = 16;
86static constexpr double histogram_low = -70.;
87static constexpr double histogram_step = 0.5;
88static constexpr int histogram_bins = 150;
89
91{
93 uint64_t sequence{};
94 double momentary{-HUGE_VAL};
95 double short_term{-HUGE_VAL};
96 double integrated{-HUGE_VAL};
97 double range{};
98 double true_peak{-HUGE_VAL};
100 double duration{};
101 bool running{};
102 std::array<uint32_t, histogram_bins> histogram{};
103};
104
107inline ebur128_state* makeState(double rate)
108{
109 return ebur128_init(
110 max_channels, (unsigned long)rate,
111 EBUR128_MODE_M | EBUR128_MODE_S | EBUR128_MODE_I | EBUR128_MODE_LRA
112 | EBUR128_MODE_HISTOGRAM);
113}
114
123{
124 static constexpr int slots = 2;
125
127 std::atomic_bool reset{};
128 std::array<std::atomic<ebur128_state*>, slots> spare{};
129 std::array<std::atomic<ebur128_state*>, slots> retired{};
131 std::atomic<double> rate{48000.};
133 std::atomic_bool recycling{};
134
137 void recycle()
138 {
139 for(auto& slot : retired)
140 if(auto* old = slot.exchange(nullptr))
141 ebur128_destroy(&old);
142 for(auto& slot : spare)
143 {
144 if(slot.load())
145 continue;
146 auto* fresh = makeState(rate.load());
147 ebur128_state* expected = nullptr;
148 if(!slot.compare_exchange_strong(expected, fresh))
149 ebur128_destroy(&fresh);
150 }
151 recycling = false;
152 }
153
156 int freeRetiredSlot() const noexcept
157 {
158 for(int i = 0; i < slots; i++)
159 if(!retired[i].load())
160 return i;
161 return -1;
162 }
163
169 ebur128_state* takeSpare(unsigned long samplerate) noexcept
170 {
171 for(auto& slot : spare)
172 {
173 ebur128_state* st = slot.exchange(nullptr);
174 if(!st)
175 continue;
176 if(st->samplerate == samplerate)
177 return st;
178 if(const int r = freeRetiredSlot(); r >= 0)
179 retired[r].store(st);
180 else
181 slot.store(st);
182 }
183 return nullptr;
184 }
185
186 ~LoudnessMonitor() override
187 {
188 for(auto* states : {&spare, &retired})
189 for(auto& slot : *states)
190 if(auto* st = slot.exchange(nullptr))
191 ebur128_destroy(&st);
192 }
193};
194
195inline double lufsToDb(double v)
196{
197 return std::isfinite(v) ? v : -200.;
198}
199
200struct Layer;
201
202struct Node
203{
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, "")
209 halp_meta(
210 description,
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);
216
217 struct
218 {
219 halp::dynamic_audio_bus<"Audio", double> audio;
220 struct : halp::combobox_t<"Mode", Mode>
221 {
222 struct range
223 {
224 std::string_view values[4]{mode_names[0], mode_names[1], mode_names[2], mode_names[3]};
225 Mode init{Mode::Numbers};
226 };
227 } mode;
228 struct : halp::combobox_t<"Standard", Standard>
229 {
230 halp_meta(description, "What the readings are judged against: the Target, or the target and tolerance of a standard")
231 struct range
232 {
233 std::string_view values[5]{
234 standard_names[0], standard_names[1], standard_names[2], standard_names[3],
235 standard_names[4]};
236 Standard init{Standard::Custom};
237 };
238 } standard;
239 struct : halp::spinbox_f32<"Target", halp::range{-40., 0., -23.}>
240 {
241 halp_meta(description, "Target in LUFS, when Standard is Custom; a named standard sets its own")
242 } custom;
243 struct : halp::combobox_t<"Scale", Scale>
244 {
245 halp_meta(description, "EBU Tech 3341 display range around the target")
246 struct range
247 {
248 std::string_view values[2]{scale_names[0], scale_names[1]};
249 Scale init{Scale::Ebu9};
250 };
251 } scale;
252 struct : halp::toggle<"Relative">
253 {
254 halp_meta(description, "Show LU relative to the target instead of LUFS")
255 } relative;
256 struct : halp::toggle_t<bool, "Running", halp::toggle_setup{.init = true}>
257 {
258 halp_meta(description, "Integrate; off pauses the integrated loudness and range")
259 } running;
260 halp::impulse_button<"Reset"> reset;
261 struct : halp::toggle_t<bool, "Follow transport", halp::toggle_setup{.init = true}>
262 {
263 halp_meta(description, "Start a new integration whenever playback starts")
264 } follow;
265 struct : halp::combobox_t<"Layout", Ui::Monitor::ChannelLayout>
266 {
267 halp_meta(description, "Speaker layout: the LFE is left out and surrounds weigh 1.5 dB more")
268 struct range
269 {
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};
276 };
277 } layout;
278 } inputs;
279
280 struct
281 {
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>
286 {
287 halp_meta(description, "Loudness range (LRA), in LU")
288 } range;
289 struct : halp::val_port<"True peak", float>
290 {
291 halp_meta(description, "Highest true peak of any channel since the last reset, in dBTP")
292 } true_peak;
293 } outputs;
294
295 std::shared_ptr<LoudnessMonitor> monitor = std::make_shared<LoudnessMonitor>();
296
297 struct worker_t
298 {
299 std::function<void(std::shared_ptr<LoudnessMonitor>)> request;
300 static void work(const std::shared_ptr<LoudnessMonitor>& m) { m->recycle(); }
301 } worker;
302
304 {
305 void operator()(ebur128_state* st) const noexcept { ebur128_destroy(&st); }
306 };
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{};
317 double rate{};
318 double since_readings{};
319 bool restart{};
322 Readings readings;
323
327 void prepare(halp::setup s)
328 {
329 const double r = s.rate > 0 ? s.rate : 48000.;
330 if(r == rate && state)
331 return;
332 rate = r;
333 state.reset(makeState(rate));
334 for(auto& t : tp)
335 t.prepare(rate);
336 tp_max.fill(0.);
337 monitor->rate = rate;
338 for(auto& slot : monitor->spare)
339 if(auto* old = slot.exchange(nullptr))
340 ebur128_destroy(&old);
341 monitor->recycle();
342 mapped_channels = -1;
343 readings = {};
344 }
345
347 void start()
348 {
349 if(inputs.follow)
350 restart = true;
351 }
352
353 void map_channels(int nch)
354 {
355 const auto layout = inputs.layout.value;
356 if(nch == mapped_channels && layout == mapped_layout)
357 return;
358 for(int c = 0; c < max_channels; c++)
359 {
360 int type = EBUR128_UNUSED;
361 if(c < nch)
362 {
363 const double w = Ui::Monitor::loudnessWeight(layout, nch, c);
364 type = w == 0. ? EBUR128_UNUSED
365 : w > 1. ? EBUR128_LEFT_SURROUND
366 : EBUR128_LEFT;
367 // A single channel counts as if heard from both speakers
368 if(nch == 1)
369 type = EBUR128_DUAL_MONO;
370 }
371 ebur128_set_channel(state.get(), c, type);
372 }
373 mapped_channels = nch;
374 mapped_layout = layout;
375 }
376
377 void request_recycle()
378 {
379 if(worker.request && !monitor->recycling.exchange(true))
380 worker.request(monitor);
381 }
382
384 bool clear() noexcept
385 {
386 const int slot = monitor->freeRetiredSlot();
387 if(slot < 0)
388 return false;
389 ebur128_state* fresh = monitor->takeSpare((unsigned long)rate);
390 if(!fresh)
391 return false;
392 monitor->retired[slot].store(state.release());
393 state.reset(fresh);
394 for(auto& t : tp)
395 t.history.fill(0.);
396 tp_max.fill(0.);
397 mapped_channels = -1;
398 readings = {};
399 return true;
400 }
401
402 void operator()(int frames)
403 {
404 if(!state || frames <= 0)
405 return;
406 const int nch = std::min(inputs.audio.channels, max_channels);
407 if(nch == 0)
408 return;
409
410 if(restart || inputs.reset || monitor->reset.exchange(false, std::memory_order_relaxed))
411 {
412 restart = false;
413 reset_pending = true;
414 }
415 if(reset_pending)
416 {
417 const bool done = clear();
418 request_recycle();
419 if(!done)
420 return;
421 reset_pending = false;
422 }
423 map_channels(nch);
424
425 const bool running = inputs.running.value;
426 readings.running = running;
427 if(running)
428 {
429 for(int done = 0; done < frames;)
430 {
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++)
434 {
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];
438 }
439 ebur128_add_frames_double(state.get(), interleaved.data(), n);
440 done += n;
441 }
442 for(int c = 0; c < nch; c++)
443 {
444 tp[c].process(inputs.audio[c], frames);
445 tp_max[c] = std::max(tp_max[c], tp[c].block_peak);
446 }
447 readings.duration += frames / rate;
448 }
449
450 since_readings += frames;
451 if(since_readings < rate / 10.)
452 return;
453 since_readings = 0;
454
455 auto* st = state.get();
456 double v{};
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)
464 readings.range = v;
465 double peak = 0;
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)
470 {
471 const int bin = int((readings.short_term - histogram_low) / histogram_step);
472 if(bin >= 0 && bin < histogram_bins)
473 readings.histogram[bin]++;
474 }
475 readings.sequence++;
476 if(monitor->watched())
477 monitor->readings.produce(readings);
478
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);
484 }
485};
486
488{
489 struct Point
490 {
491 float momentary, short_term;
492 };
493
494 Layer(
495 const Process::ProcessModel& process, const Process::Context& doc,
496 QGraphicsItem* parent);
497
498 void mousePressEvent(QGraphicsSceneMouseEvent* ev) override;
499 void paint_impl(QPainter* p) const override;
500
501private:
502 Mode mode() const;
503 Target target() const;
504 bool relative() const;
505 std::pair<double, double> range() const;
506 QString format(double lufs) const;
507 QColor zone(double lufs) const;
508 QRectF resetRect() const;
509
510 void paintNumbers(QPainter* p, const QRectF& r) const;
511 void paintHistory(QPainter* p, const QRectF& area) const;
512 void paintHistogram(QPainter* p, const QRectF& area) const;
513 void paintRadar(QPainter* p, const QRectF& area) const;
514
515 const Process::ControlInlet* m_mode{};
516 const Process::ControlInlet* m_standard{};
517 const Process::ControlInlet* m_custom{};
518 const Process::ControlInlet* m_scale{};
519 const Process::ControlInlet* m_relative{};
520 Process::MonitorLink* m_link{};
521 Ui::Monitor::DoubleClick m_doubleClick;
522 Readings m_now;
524 std::vector<Point> m_history = std::vector<Point>(6000);
525 int m_history_pos{};
526 int m_history_count{};
527};
528}
Definition Port.hpp:247
Definition EffectLayer.hpp:17
Definition Monitor.hpp:59
The Process class.
Definition score-lib-process/Process/Process.hpp:82
Definition LayerHelpers.hpp:103
Base classes and tools to implement processes and layers.
Definition JSONVisitor.hpp:1115
double loudnessWeight(ChannelLayout l, int channels, int c) noexcept
Definition ChannelLayout.hpp:113
Definition ProcessContext.hpp:12
Definition Monitor.hpp:27
Definition Loudness.hpp:490
Definition Loudness.hpp:488
Definition Loudness.hpp:123
std::atomic< double > rate
The rate spares are built for.
Definition Loudness.hpp:131
int freeRetiredSlot() const noexcept
Definition Loudness.hpp:156
std::atomic_bool recycling
Set when a recycle() is requested, cleared once it is done.
Definition Loudness.hpp:133
ebur128_state * takeSpare(unsigned long samplerate) noexcept
Definition Loudness.hpp:169
void recycle()
Definition Loudness.hpp:137
Definition Loudness.hpp:304
Definition Loudness.hpp:298
Definition Loudness.hpp:203
bool clear() noexcept
Starts over with a spare state; false when none is ready yet.
Definition Loudness.hpp:384
void start()
Playback started: a new programme.
Definition Loudness.hpp:347
void prepare(halp::setup s)
Definition Loudness.hpp:327
bool reset_pending
A reset waits for a spare state: until one is ready, nothing is measured.
Definition Loudness.hpp:321
std::array< double, max_channels > tp_max
Highest true peak of each channel since the last reset, linear.
Definition Loudness.hpp:314
std::unique_ptr< ebur128_state, state_deleter > state
Definition Loudness.hpp:309
Definition Loudness.hpp:91
uint64_t sequence
Increments with every new set of readings, ten per second.
Definition Loudness.hpp:93
double duration
Seconds integrated so far.
Definition Loudness.hpp:100
Definition Loudness.hpp:54
Definition MIDISync.hpp:149