ebu.cpp (11468B)
1 /** 2 * KFR (https://www.kfrlib.com) 3 * Copyright (C) 2016-2023 Dan Cazarin 4 * See LICENSE.txt for details 5 */ 6 7 #include <kfr/dsp/ebu.hpp> 8 #include <kfr/dsp/oscillators.hpp> 9 10 CMT_PRAGMA_MSVC(warning(push)) 11 CMT_PRAGMA_MSVC(warning(disable : 4244)) 12 13 namespace kfr 14 { 15 inline namespace CMT_ARCH_NAME 16 { 17 18 struct TestFragment 19 { 20 float gain; // dB 21 float duration; // seconds 22 float frequency; // Hz 23 }; 24 25 struct TestFragmentMultichannel 26 { 27 float gain_L_R; // dB 28 float gain_C; // dB 29 float gain_Ls_Rs; // dB 30 float duration; // seconds 31 float frequency; // Hz 32 }; 33 34 template <typename T> 35 static void ebu_test_stereo(int sample_rate, const std::initializer_list<TestFragment>& fragments, T refM, 36 T refS, T refI, T refLRA) 37 { 38 ebu_r128<T> loudness(sample_rate, { Speaker::Left, Speaker::Right }); 39 40 size_t total_length = 0; 41 for (const TestFragment& f : fragments) 42 { 43 total_length += static_cast<size_t>(f.duration * sample_rate); 44 } 45 46 univector<T> left_right(total_length); 47 size_t pos = 0; 48 for (const TestFragment& f : fragments) 49 { 50 const size_t len = static_cast<size_t>(f.duration * sample_rate); 51 left_right.slice(pos, len) = dB_to_amp(f.gain) * sinenorm(phasor<float>(f.frequency, sample_rate)); 52 pos += len; 53 } 54 55 for (size_t i = 0; i < total_length / loudness.packet_size(); i++) 56 { 57 loudness.process_packet({ left_right.slice(i * loudness.packet_size(), loudness.packet_size()), 58 left_right.slice(i * loudness.packet_size(), loudness.packet_size()) }); 59 } 60 T M, S, I, RL, RH; 61 loudness.get_values(M, S, I, RL, RH); 62 if (!std::isnan(refM)) 63 { 64 testo::scope s(as_string("M = ", fmt<'f', -1, 2>(M))); 65 CHECK(std::abs(M - refM) < 0.05f); 66 } 67 if (!std::isnan(refS)) 68 { 69 testo::scope s(as_string("S = ", fmt<'f', -1, 2>(S))); 70 CHECK(std::abs(S - refS) < 0.05f); 71 } 72 if (!std::isnan(refI)) 73 { 74 testo::scope s(as_string("I = ", fmt<'f', -1, 2>(I))); 75 CHECK(std::abs(I - refI) < 0.05f); 76 } 77 if (!std::isnan(refLRA)) 78 { 79 testo::scope s(as_string("LRA = ", fmt<'f', -1, 2>((RH - RL)))); 80 CHECK(std::abs((RH - RL) - refLRA) < 0.05f); 81 } 82 } 83 84 template <typename T> 85 static void ebu_test_multichannel(int sample_rate, 86 const std::initializer_list<TestFragmentMultichannel>& fragments, T refM, 87 T refS, T refI, T refLRA) 88 { 89 ebu_r128<T> loudness(sample_rate, { Speaker::Left, Speaker::Right, Speaker::Center, Speaker::LeftSurround, 90 Speaker::RightSurround }); 91 92 size_t total_length = 0; 93 for (const TestFragmentMultichannel& f : fragments) 94 { 95 total_length += static_cast<size_t>(f.duration * sample_rate); 96 } 97 98 univector<T> left_right(total_length); 99 univector<T> center(total_length); 100 univector<T> surround(total_length); 101 size_t pos = 0; 102 for (const TestFragmentMultichannel& f : fragments) 103 { 104 const size_t len = static_cast<size_t>(f.duration * sample_rate); 105 left_right.slice(pos, len) = 106 dB_to_amp(f.gain_L_R) * sinenorm(phasor<float>(f.frequency, sample_rate)); 107 center.slice(pos, len) = dB_to_amp(f.gain_C) * sinenorm(phasor<float>(f.frequency, sample_rate)); 108 surround.slice(pos, len) = 109 dB_to_amp(f.gain_Ls_Rs) * sinenorm(phasor<float>(f.frequency, sample_rate)); 110 pos += len; 111 } 112 113 for (size_t i = 0; i < total_length / loudness.packet_size(); i++) 114 { 115 loudness.process_packet({ left_right.slice(i * loudness.packet_size(), loudness.packet_size()), 116 left_right.slice(i * loudness.packet_size(), loudness.packet_size()), 117 center.slice(i * loudness.packet_size(), loudness.packet_size()), 118 surround.slice(i * loudness.packet_size(), loudness.packet_size()), 119 surround.slice(i * loudness.packet_size(), loudness.packet_size()) }); 120 } 121 T M, S, I, RL, RH; 122 loudness.get_values(M, S, I, RL, RH); 123 if (!std::isnan(refM)) 124 { 125 testo::scope s(as_string("M = ", fmt<'f', -1, 2>(M))); 126 CHECK(std::abs(M - refM) < 0.1f); 127 } 128 if (!std::isnan(refS)) 129 { 130 testo::scope s(as_string("S = ", fmt<'f', -1, 2>(S))); 131 CHECK(std::abs(S - refS) < 0.1f); 132 } 133 if (!std::isnan(refI)) 134 { 135 testo::scope s(as_string("I = ", fmt<'f', -1, 2>(I))); 136 CHECK(std::abs(I - refI) < 0.1f); 137 } 138 if (!std::isnan(refLRA)) 139 { 140 testo::scope s(as_string("LRA = ", fmt<'f', -1, 2>((RH - RL)))); 141 CHECK(std::abs((RH - RL) - refLRA) < 0.1f); 142 } 143 } 144 145 TEST(ebu_stereo_1_and_2) 146 { 147 testo::matrix(named("type") = ctypes_t<float, double>{}, 148 named("sample_rate") = std::vector<int>{ 44100, 48000 }, 149 [](auto type, int sample_rate) 150 { 151 using T = typename decltype(type)::type; 152 153 ebu_test_stereo<T>(sample_rate, { { -23.f, 20.f, 1000.f } }, -23.f, -23.f, -23.f, NAN); 154 ebu_test_stereo<T>(sample_rate, { { -33.f, 20.f, 1000.f } }, -33.f, -33.f, -33.f, NAN); 155 }); 156 } 157 158 TEST(ebu_stereo_3_4_and_5) 159 { 160 testo::matrix( 161 named("type") = ctypes_t<float, double>{}, named("sample_rate") = std::vector<int>{ 44100, 48000 }, 162 [](auto type, int sample_rate) 163 { 164 using T = typename decltype(type)::type; 165 166 ebu_test_stereo<T>(sample_rate, 167 { { -36.f, 10.f, 1000.f }, { -23.f, 60.f, 1000.f }, { -36.f, 10.f, 1000.f } }, 168 NAN, NAN, -23.f, NAN); 169 ebu_test_stereo<T>(sample_rate, 170 { { -72.f, 10.f, 1000.f }, 171 { -36.f, 10.f, 1000.f }, 172 { -23.f, 60.f, 1000.f }, 173 { -36.f, 10.f, 1000.f }, 174 { -72.f, 10.f, 1000.f } }, 175 NAN, NAN, -23.f, NAN); 176 }); 177 } 178 179 TEST(ebu_multichannel_6) 180 { 181 testo::matrix(named("type") = ctypes_t<float, double>{}, 182 named("sample_rate") = std::vector<int>{ 44100, 48000 }, 183 [](auto type, int sample_rate) 184 { 185 using T = typename decltype(type)::type; 186 187 ebu_test_multichannel<T>(sample_rate, { { -28.f, -24.f, -30.f, 20.f, 1000.f } }, NAN, 188 NAN, -23.f, NAN); 189 }); 190 } 191 192 TEST(ebu_stereo_9) 193 { 194 testo::matrix(named("type") = ctypes_t<float, double>{}, 195 named("sample_rate") = std::vector<int>{ 44100, 48000 }, 196 [](auto type, int sample_rate) 197 { 198 using T = typename decltype(type)::type; 199 200 ebu_test_stereo<T>(sample_rate, 201 { { -20.f, 1.34f, 1000.f }, 202 { -30.f, 1.66f, 1000.f }, 203 { -20.f, 1.34f, 1000.f }, 204 { -30.f, 1.66f, 1000.f }, 205 { -20.f, 1.34f, 1000.f }, 206 { -30.f, 1.66f, 1000.f }, 207 { -20.f, 1.34f, 1000.f }, 208 { -30.f, 1.66f, 1000.f }, 209 { -20.f, 1.34f, 1000.f }, 210 { -30.f, 1.66f, 1000.f } }, 211 NAN, -23.f, NAN, NAN); 212 }); 213 } 214 215 TEST(ebu_stereo_12) 216 { 217 testo::matrix( 218 named("type") = ctypes_t<float, double>{}, named("sample_rate") = std::vector<int>{ 44100, 48000 }, 219 [](auto type, int sample_rate) 220 { 221 using T = typename decltype(type)::type; 222 223 ebu_test_stereo<T>(sample_rate, 224 { { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, 225 { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, 226 { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, 227 { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, 228 { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, 229 { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, 230 { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, 231 { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, 232 { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, 233 { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, 234 { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, 235 { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, 236 { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, 237 { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, 238 { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, 239 { -30.f, 0.22f, 1000.f }, { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f }, 240 { -20.f, 0.18f, 1000.f }, { -30.f, 0.22f, 1000.f } }, 241 -23.f, NAN, NAN, NAN); 242 }); 243 } 244 245 TEST(ebu_lra_1_2_3_and_4) 246 { 247 testo::matrix(named("type") = ctypes_t<float, double>{}, 248 named("sample_rate") = std::vector<int>{ 44100, 48000 }, 249 [](auto type, int sample_rate) 250 { 251 using T = typename decltype(type)::type; 252 253 ebu_test_stereo<T>(sample_rate, { { -20.f, 20.f, 1000.f }, { -30.f, 20.f, 1000.f } }, 254 NAN, NAN, NAN, 10.f); 255 256 ebu_test_stereo<T>(sample_rate, { { -20.f, 20.f, 1000.f }, { -15.f, 20.f, 1000.f } }, 257 NAN, NAN, NAN, 5.f); 258 259 ebu_test_stereo<T>(sample_rate, { { -40.f, 20.f, 1000.f }, { -20.f, 20.f, 1000.f } }, 260 NAN, NAN, NAN, 20.f); 261 262 ebu_test_stereo<T>(sample_rate, 263 { { -50.f, 20.f, 1000.f }, 264 { -35.f, 20.f, 1000.f }, 265 { -20.f, 20.f, 1000.f }, 266 { -35.f, 20.f, 1000.f }, 267 { -50.f, 20.f, 1000.f } }, 268 NAN, NAN, NAN, 15.f); 269 }); 270 } 271 } // namespace CMT_ARCH_NAME 272 273 } // namespace kfr 274 275 CMT_PRAGMA_MSVC(warning(pop))