%{ ** Copyright (C) 2021 The Qt Company Ltd. ** SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 %} % Create mapping matrixes to convert ambisonic to different speaker layouts supported in Qt Multimedia % % This uses the ADT octave framework from https://bitbucket.org/ambidecodertoolbox/adt/src/master/ % to generate conversion matrixes between ambisonic formats and various speaker configurations % % We're generating two band conversion matrices with a cutoff frequency of 380 Herz. Filtering low % and high frequencies differently is important to get a decent spatial reproduction. For details % see the "Is my decoder Ambisonic?" paper (https://ambisonics.dreamhosters.com/BLaH3.pdf) % function adt_generate_qt() % assume speakers are 2 meters from listener. This should avoid near-field % effects and should work for most room setups radius = 2; [outfile,msg] = fopen("qambisonicdecoderdata_p.h",'w'); fprintf(outfile, "// Copyright (C) 2016 The Qt Company Ltd.\n"); fprintf(outfile, "//SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only\n"); fprintf(outfile, "#ifndef QAMBISONICDECODERDATA_P_H\n"); fprintf(outfile, "#define QAMBISONICDECODERDATA_P_H\n\n"); fprintf(outfile, "#include \n\n"); fprintf(outfile, "// W A R N I N G\n"); fprintf(outfile, "// -------------\n"); fprintf(outfile, "//\n"); fprintf(outfile, "// This file is not part of the Qt API. It exists purely as an\n"); fprintf(outfile, "// implementation detail. This header file may change from version to\n"); fprintf(outfile, "// version without notice, or even be removed.\n"); fprintf(outfile, "//\n"); fprintf(outfile, "// We mean it.\n"); fprintf(outfile, "//\n\n"); fprintf(outfile, "// This file is generated by the matlab/octave file adt_generate_qt.m\n"); fprintf(outfile, "// using the Ambisonic Decoder Toolbox (https://bitbucket.org/ambidecodertoolbox/adt/src/master/)\n"); fprintf(outfile, "\n\n"); fprintf(outfile, "QT_BEGIN_NAMESPACE\n\n"); % cover top/bottom for surround imag_speakers = [0,0,radius; 0,0,-radius]; % https://www.dolby.com/us/en/guide/surround-sound-speaker-setup/5-1-setup.html % Dolby 5.1 -- F: 22-30, S: 110-120, C in same plane as F S = ambi_spkr_array(... '5dot0', ... 'AER', 'DDM', ... 'L', [ 30, 0, radius], ... 'R', [ -30, 0, radius], ... 'C', [ 0, 0, radius], ... 'Ls', [ 110, 0, radius], ... 'Rs', [-110, 0, radius] ... ); createDecoders(S, imag_speakers, outfile); S.lfeRow = 4; S.name = "5dot1"; createDecoders(S, imag_speakers, outfile); % https://www.dolby.com/us/en/guide/surround-sound-speaker-setup/7-1-setup.html % Dolby 7.1 F: 22-30, S: 90-110, B: 135-150 S = ambi_spkr_array(... ... % array name '7dot0', ... ... % coordinate codes, unit codes ... % Azimuth, Elevation, Radius; Degrees, Degrees, Meters 'AER', 'DDM', ... ... % speaker name, [azimuth, elevation, radius] 'L', [ 30, 0, radius], ... 'R', [ -30, 0, radius], ... 'C', [ 0, 0, radius], ... 'Ls', [ 90, 0, radius], ... 'Rs', [ -90, 0, radius], ... 'Lb', [ 150, 0, radius], ... 'Rb', [-150, 0, radius] ... ); createDecoders(S, imag_speakers, outfile); S.lfeRow = 4; S.name = "7dot1"; createDecoders(S, imag_speakers, outfile); fprintf(outfile, "QT_END_NAMESPACE\n\n"); fprintf(outfile, "#endif\n\n"); fclose(outfile); end % remove rounding errors function m = trimMatrix(m) for i = 1:rows(m) for j = 1:columns(m) if (abs(m(i, j)) < 1e-4) m(i, j) = 0; endif endfor endfor end function writeLFERow(outfile, m, suffix) if (strcmp(suffix, 'hf')) fprintf(outfile, "0.0f, "); % no need for High frequency data on the LFE channel else fprintf(outfile, "0.5f, "); endif for i = 2:columns(m) fprintf(outfile, "0.0f, "); endfor fprintf(outfile, "// LFE\n"); end function [n, m] = getnm(l) % Computes spherical harmonic degree and order from Ambisonic Channel Number. n = floor(sqrt(l)); m = l-n.^2-n; end function channels = normalizeSN3D(channels) for i = 1:columns(channels) [n, m] = getnm(i-1); if (m == 0) factor = 1; else factor = sqrt(2 * factorial(n - abs(m)) / (factorial(n + abs(m)))); endif % There's an adjustment factor of 1.5 here, to match the volumes generated by the matrices % here with what we get from a simple stereo decoding. The factor is heuristically determined channels(i) *= factor*1.5; endfor end function writeMatrix(outfile, level, S, M, suffix) m = trimMatrix(M); hasLFE = isfield(S, "lfeRow"); r = rows(m); c = columns(m); if (hasLFE) r = r + 1; endif fprintf(outfile, "// Decoder matrix for %s, ambisonic level %d\n", S.name, level); fprintf(outfile, "static constexpr float decoderMatrix_%s_%d_%s[%d*%d] = {\n", S.name, level, suffix, r, c); for i = 1:rows(S.id) channels = normalizeSN3D(m(i, :)) fprintf(outfile, "%ff, ", channels); fprintf(outfile, "// %s\n", S.id(i, 1){1}); if (hasLFE && S.lfeRow == i + 1) writeLFERow(outfile, m, suffix); endif endfor fprintf(outfile, "};\n\n"); end function createOneDecoder(S, imag_speakers, outfile, level) ambi_order = ambi_channel_definitions_convention(level, 'ambix2011') [D,S,M,C] = ambi_run_allrad(S, ambi_order, imag_speakers, [S.name '_' int2str(level)], false, "amb", 1, 3); writeMatrix(outfile, level, S, M.lf, "lf"); m = ambi_apply_gamma(M.hf, D.hf_gains, C); writeMatrix(outfile, level, S, m, "hf"); end function createDecoders(S, imag_speakers, outfile) for level = [1:3] createOneDecoder(S, imag_speakers, outfile, level) endfor end