| 1 | // Copyright (C) 2022 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-3.0-only |
| 3 | #include "qambisonicdecoder_p.h" |
| 4 | |
| 5 | #include "qambisonicdecoderdata_p.h" |
| 6 | #include <cmath> |
| 7 | #include <qdebug.h> |
| 8 | |
| 9 | QT_BEGIN_NAMESPACE |
| 10 | |
| 11 | // Ambisonic decoding is described in detail in https://ambisonics.dreamhosters.com/BLaH3.pdf. |
| 12 | // We're using a phase matched band splitting filter to split the ambisonic signal into a low |
| 13 | // and high frequency component and apply matrix conversions to those components individually |
| 14 | // as described in the document. |
| 15 | // |
| 16 | // We are currently not using a near field compensation filter, something that could potentially |
| 17 | // improve sound quality further. |
| 18 | // |
| 19 | // For mono and stereo decoding, we use a simpler algorithm to avoid artificially dampening signals |
| 20 | // coming from the back, as we do not have any speakers in that direction and the calculations |
| 21 | // through matlab would give us audible 'holes'. |
| 22 | |
| 23 | struct QAmbisonicDecoderData |
| 24 | { |
| 25 | QAudioFormat::ChannelConfig config; |
| 26 | const float *lf[3]; |
| 27 | const float *hf[3]; |
| 28 | const float *reverb; |
| 29 | }; |
| 30 | |
| 31 | const float reverb_x_0[] = |
| 32 | { |
| 33 | 1.f, 0.f, // L |
| 34 | 0.f, 1.f, // R |
| 35 | .7f, .7f, // C |
| 36 | 1.f, 0.f, // Ls |
| 37 | 0.f, 1.f, // Rs |
| 38 | 1.f, 0.f, // Lb |
| 39 | 0.f, 1.f, // Rb |
| 40 | }; |
| 41 | |
| 42 | const float reverb_x_1[] = |
| 43 | { |
| 44 | 1.f, 0.f, // L |
| 45 | 0.f, 1.f, // R |
| 46 | .7f, .7f, // C |
| 47 | .0f, .0f, // LFE |
| 48 | 1.f, 0.f, // Ls |
| 49 | 0.f, 1.f, // Rs |
| 50 | 1.f, 0.f, // Lb |
| 51 | 0.f, 1.f, // Rb |
| 52 | }; |
| 53 | |
| 54 | static const QAmbisonicDecoderData decoderMap[] = |
| 55 | { |
| 56 | { .config: QAudioFormat::ChannelConfigSurround5Dot0, |
| 57 | .lf: { decoderMatrix_5dot0_1_lf, decoderMatrix_5dot0_2_lf, decoderMatrix_5dot0_3_lf }, |
| 58 | .hf: { decoderMatrix_5dot0_1_hf, decoderMatrix_5dot0_2_hf, decoderMatrix_5dot0_3_hf }, |
| 59 | .reverb: reverb_x_0 |
| 60 | }, |
| 61 | { .config: QAudioFormat::ChannelConfigSurround5Dot1, |
| 62 | .lf: { decoderMatrix_5dot1_1_lf, decoderMatrix_5dot1_2_lf, decoderMatrix_5dot1_3_lf }, |
| 63 | .hf: { decoderMatrix_5dot1_1_hf, decoderMatrix_5dot1_2_hf, decoderMatrix_5dot1_3_hf }, |
| 64 | .reverb: reverb_x_1 |
| 65 | }, |
| 66 | { .config: QAudioFormat::ChannelConfigSurround7Dot0, |
| 67 | .lf: { decoderMatrix_7dot0_1_lf, decoderMatrix_7dot0_2_lf, decoderMatrix_7dot0_3_lf }, |
| 68 | .hf: { decoderMatrix_7dot0_1_hf, decoderMatrix_7dot0_2_hf, decoderMatrix_7dot0_3_hf }, |
| 69 | .reverb: reverb_x_0 |
| 70 | }, |
| 71 | { .config: QAudioFormat::ChannelConfigSurround7Dot1, |
| 72 | .lf: { decoderMatrix_7dot1_1_lf, decoderMatrix_7dot1_2_lf, decoderMatrix_7dot1_3_lf }, |
| 73 | .hf: { decoderMatrix_7dot1_1_hf, decoderMatrix_7dot1_2_hf, decoderMatrix_7dot1_3_hf }, |
| 74 | .reverb: reverb_x_1 |
| 75 | } |
| 76 | }; |
| 77 | |
| 78 | // Implements a split second order IIR filter |
| 79 | // The audio data is split into a phase synced low and high frequency part |
| 80 | // This allows us to apply different factors to both parts for better sound |
| 81 | // localization when converting from ambisonic formats |
| 82 | // |
| 83 | // Details are described in https://ambisonics.dreamhosters.com/BLaH3.pdf, Appendix A.2. |
| 84 | class QAmbisonicDecoderFilter |
| 85 | { |
| 86 | public: |
| 87 | QAmbisonicDecoderFilter() = default; |
| 88 | void configure(float sampleRate, float cutoffFrequency = 380) |
| 89 | { |
| 90 | double k = tan(M_PI*cutoffFrequency/sampleRate); |
| 91 | a1 = float(2.*(k*k - 1.)/(k*k + 2*k + 1.)); |
| 92 | a2 = float((k*k - 2*k + 1.)/(k*k + 2*k + 1.)); |
| 93 | |
| 94 | b0_lf = float(k*k/(k*k + 2*k + 1)); |
| 95 | b1_lf = 2.f*b0_lf; |
| 96 | |
| 97 | b0_hf = float(1./(k*k + 2*k + 1)); |
| 98 | b1_hf = -2.f*b0_hf; |
| 99 | } |
| 100 | |
| 101 | struct Output |
| 102 | { |
| 103 | float lf; |
| 104 | float hf; |
| 105 | }; |
| 106 | |
| 107 | Output next(float x) |
| 108 | { |
| 109 | float r_lf = x*b0_lf + |
| 110 | prevX[0]*b1_lf + |
| 111 | prevX[1]*b0_lf - |
| 112 | prevR_lf[0]*a1 - |
| 113 | prevR_lf[1]*a2; |
| 114 | float r_hf = x*b0_hf + |
| 115 | prevX[0]*b1_hf + |
| 116 | prevX[1]*b0_hf - |
| 117 | prevR_hf[0]*a1 - |
| 118 | prevR_hf[1]*a2; |
| 119 | prevX[1] = prevX[0]; |
| 120 | prevX[0] = x; |
| 121 | prevR_lf[1] = prevR_lf[0]; |
| 122 | prevR_lf[0] = r_lf; |
| 123 | prevR_hf[1] = prevR_hf[0]; |
| 124 | prevR_hf[0] = r_hf; |
| 125 | return { .lf: r_lf, .hf: r_hf }; |
| 126 | } |
| 127 | |
| 128 | private: |
| 129 | float a1 = 0.; |
| 130 | float a2 = 0.; |
| 131 | |
| 132 | float b0_hf = 0.; |
| 133 | float b1_hf = 0.; |
| 134 | |
| 135 | float b0_lf = 0.; |
| 136 | float b1_lf = 0.; |
| 137 | |
| 138 | float prevX[2] = {}; |
| 139 | float prevR_lf[2] = {}; |
| 140 | float prevR_hf[2] = {}; |
| 141 | }; |
| 142 | |
| 143 | |
| 144 | QAmbisonicDecoder::QAmbisonicDecoder(AmbisonicLevel ambisonicLevel, const QAudioFormat &format) |
| 145 | : level(ambisonicLevel) |
| 146 | { |
| 147 | Q_ASSERT(level > 0 && level <= 3); |
| 148 | inputChannels = (level+1)*(level+1); |
| 149 | outputChannels = format.channelCount(); |
| 150 | |
| 151 | channelConfig = format.channelConfig(); |
| 152 | if (channelConfig == QAudioFormat::ChannelConfigUnknown) |
| 153 | channelConfig = format.defaultChannelConfigForChannelCount(channelCount: format.channelCount()); |
| 154 | |
| 155 | if (channelConfig == QAudioFormat::ChannelConfigMono || |
| 156 | channelConfig == QAudioFormat::ChannelConfigStereo || |
| 157 | channelConfig == QAudioFormat::ChannelConfig2Dot1 || |
| 158 | channelConfig == QAudioFormat::ChannelConfig3Dot0 || |
| 159 | channelConfig == QAudioFormat::ChannelConfig3Dot1) { |
| 160 | // these are non surround configs and handled manually to avoid |
| 161 | // audible holes for sounds coming from behing |
| 162 | // |
| 163 | // We use a simpler decoding process here, only taking first order |
| 164 | // ambisonics into account |
| 165 | // |
| 166 | // Left and right channels get 50% W and 50% X |
| 167 | // Center gets 50% W and 50% Y |
| 168 | // LFE gets 50% W |
| 169 | simpleDecoderFactors = new float[4*outputChannels]; |
| 170 | float *r = new float[2*outputChannels]; // reverb output is in stereo |
| 171 | float *f = simpleDecoderFactors; |
| 172 | reverbFactors = r; |
| 173 | if (channelConfig & QAudioFormat::channelConfig(channels: QAudioFormat::FrontLeft)) { |
| 174 | f[0] = 0.5f; f[1] = 0.5f; f[2] = 0.; f[3] = 0.f; |
| 175 | f += 4; |
| 176 | r[0] = 1.; r[1] = 0.; |
| 177 | r += 2; |
| 178 | } |
| 179 | if (channelConfig & QAudioFormat::channelConfig(channels: QAudioFormat::FrontRight)) { |
| 180 | f[0] = 0.5f; f[1] = -0.5f; f[2] = 0.; f[3] = 0.f; |
| 181 | f += 4; |
| 182 | r[0] = 0.; r[1] = 1.; |
| 183 | r += 2; |
| 184 | } |
| 185 | if (channelConfig & QAudioFormat::channelConfig(channels: QAudioFormat::FrontCenter)) { |
| 186 | f[0] = 0.5f; f[1] = -0.f; f[2] = 0.; f[3] = 0.5f; |
| 187 | f += 4; |
| 188 | r[0] = .5; r[1] = .5; |
| 189 | r += 2; |
| 190 | } |
| 191 | if (channelConfig & QAudioFormat::channelConfig(channels: QAudioFormat::LFE)) { |
| 192 | f[0] = 0.5f; f[1] = -0.f; f[2] = 0.; f[3] = 0.0f; |
| 193 | f += 4; |
| 194 | r[0] = 0.; r[1] = 0.; |
| 195 | r += 2; |
| 196 | } |
| 197 | Q_UNUSED(f); |
| 198 | Q_UNUSED(r); |
| 199 | Q_ASSERT((f - simpleDecoderFactors) == 4*outputChannels); |
| 200 | Q_ASSERT((r - reverbFactors) == 2*outputChannels); |
| 201 | |
| 202 | return; |
| 203 | } |
| 204 | |
| 205 | for (const auto &d : decoderMap) { |
| 206 | if (d.config == channelConfig) { |
| 207 | decoderData = &d; |
| 208 | reverbFactors = decoderData->reverb; |
| 209 | break; |
| 210 | } |
| 211 | } |
| 212 | if (!decoderData) { |
| 213 | // can't handle this, |
| 214 | outputChannels = 0; |
| 215 | return; |
| 216 | } |
| 217 | |
| 218 | filters = new QAmbisonicDecoderFilter[inputChannels]; |
| 219 | for (int i = 0; i < inputChannels; ++i) |
| 220 | filters[i].configure(sampleRate: format.sampleRate()); |
| 221 | } |
| 222 | |
| 223 | QAmbisonicDecoder::~QAmbisonicDecoder() |
| 224 | { |
| 225 | if (simpleDecoderFactors) { |
| 226 | delete[] simpleDecoderFactors; |
| 227 | delete[] reverbFactors; |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | void QAmbisonicDecoder::processBuffer(const float *input[], float *output, int nSamples) |
| 232 | { |
| 233 | float *o = output; |
| 234 | memset(s: o, c: 0, n: nSamples*outputChannels*sizeof(float)); |
| 235 | |
| 236 | if (simpleDecoderFactors) { |
| 237 | for (int i = 0; i < nSamples; ++i) { |
| 238 | for (int j = 0; j < 4; ++j) { |
| 239 | for (int k = 0; k < outputChannels; ++k) |
| 240 | o[k] += simpleDecoderFactors[k*4 + j]*input[j][i]; |
| 241 | } |
| 242 | o += outputChannels; |
| 243 | } |
| 244 | return; |
| 245 | } |
| 246 | |
| 247 | const float *matrix_hi = decoderData->hf[level - 1]; |
| 248 | const float *matrix_lo = decoderData->lf[level - 1]; |
| 249 | for (int i = 0; i < nSamples; ++i) { |
| 250 | QAmbisonicDecoderFilter::Output buf[maxAmbisonicChannels]; |
| 251 | for (int j = 0; j < inputChannels; ++j) |
| 252 | buf[j] = filters[j].next(x: input[j][i]); |
| 253 | for (int j = 0; j < inputChannels; ++j) { |
| 254 | for (int k = 0; k < outputChannels; ++k) |
| 255 | o[k] += matrix_lo[k*inputChannels + j]*buf[j].lf + matrix_hi[k*inputChannels + j]*buf[j].hf; |
| 256 | } |
| 257 | o += outputChannels; |
| 258 | } |
| 259 | } |
| 260 | |
| 261 | void QAmbisonicDecoder::processBuffer(const float *input[], short *output, int nSamples) |
| 262 | { |
| 263 | const float *reverb[] = { nullptr, nullptr }; |
| 264 | return processBufferWithReverb(input, reverb, output, nSamples); |
| 265 | } |
| 266 | |
| 267 | void QAmbisonicDecoder::processBufferWithReverb(const float *input[], const float *reverb[2], short *output, int nSamples) |
| 268 | { |
| 269 | if (simpleDecoderFactors) { |
| 270 | for (int i = 0; i < nSamples; ++i) { |
| 271 | float o[4] = {}; |
| 272 | for (int k = 0; k < outputChannels; ++k) { |
| 273 | for (int j = 0; j < 4; ++j) |
| 274 | o[k] += simpleDecoderFactors[k*4 + j]*input[j][i]; |
| 275 | } |
| 276 | if (reverb[0]) { |
| 277 | for (int k = 0; k < outputChannels; ++k) { |
| 278 | o[k] += reverb[0][i]*reverbFactors[2*k] + reverb[1][i]*reverbFactors[2*k+1]; |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | for (int k = 0; k < outputChannels; ++k) |
| 283 | output[k] = static_cast<short>(o[k]*32768.); |
| 284 | output += outputChannels; |
| 285 | } |
| 286 | return; |
| 287 | } |
| 288 | |
| 289 | // qDebug() << "XXX" << inputChannels << outputChannels; |
| 290 | const float *matrix_hi = decoderData->hf[level - 1]; |
| 291 | const float *matrix_lo = decoderData->lf[level - 1]; |
| 292 | for (int i = 0; i < nSamples; ++i) { |
| 293 | QAmbisonicDecoderFilter::Output buf[maxAmbisonicChannels]; |
| 294 | for (int j = 0; j < inputChannels; ++j) |
| 295 | buf[j] = filters[j].next(x: input[j][i]); |
| 296 | float o[32] = {}; // we can't support more than 32 channels from our API |
| 297 | for (int j = 0; j < inputChannels; ++j) { |
| 298 | for (int k = 0; k < outputChannels; ++k) |
| 299 | o[k] += matrix_lo[k*inputChannels + j]*buf[j].lf + matrix_hi[k*inputChannels + j]*buf[j].hf; |
| 300 | } |
| 301 | if (reverb[0]) { |
| 302 | for (int k = 0; k < outputChannels; ++k) { |
| 303 | o[k] += reverb[0][i]*reverbFactors[2*k] + reverb[1][i]*reverbFactors[2*k+1]; |
| 304 | } |
| 305 | } |
| 306 | for (int k = 0; k < outputChannels; ++k) |
| 307 | output[k] = static_cast<short>(o[k]*32768.); |
| 308 | output += outputChannels; |
| 309 | } |
| 310 | |
| 311 | } |
| 312 | |
| 313 | QT_END_NAMESPACE |
| 314 | |
| 315 | |