projection: add ambisonic mapping matrices for 6th to 14th order - #492
Open
abhayap wants to merge 1 commit into
Open
projection: add ambisonic mapping matrices for 6th to 14th order#492abhayap wants to merge 1 commit into
abhayap wants to merge 1 commit into
Conversation
Channel mapping family 3 already accepts up to 227 channels, but the
built-in projection matrices stop at 5th order, so anything above that
fails in opus_projection_ambisonics_encoder_create(). Add the missing
tables for 6th to 14th order in a new src/mapping_matrix_hoa.c.
These are not built the way the 4th- and 5th-order tables are. Those
sample the spherical-harmonic matrix at Fliege-Maier cubature nodes and
invert it in the least-squares sense, which is fine to 9th order and then
degrades badly: cond(A) goes 3.8 at 9th order, 86 at 10th, 723 at 12th,
because those node sets are chosen to be good cubature rules rather than
good interpolation points, and the 121-point set is the first with a
negative weight. Conditioning is what the demixing step costs in SNR --
per-sector coding noise returns ||A||_F.||A^-1||_F/N times louder in the
ambisonic domain -- so extending that construction would give up 15 dB at
10th order and 32 dB at 12th.
Instead these sample at maximum-determinant (Fekete) nodes, which come in
sets of exactly (N+1)^2 points chosen to maximize |det Y|, and take the
polar factor of the result: the nearest matrix with orthonormal columns.
That is energy-preserving ambisonic decoding (Zotter, Pomberger &
Noisternig, Acta Acustica 98(1), 2012) at the critically sampled limit
L = (N+1)^2. At every order it gives cond(A) = 1, needs no gain field,
makes the demixing matrix the exact transpose of the mixing matrix, and
holds the Q15 round-trip error at the quantization floor instead of
letting it grow with order.
Also:
- Replace the per-order if/else chains in opus_projection_encoder.c
with mapping_matrix_get_ambisonic(), so adding an order is a table
entry rather than four more branches in two places.
- Drop the 65004-octet cap in mapping_matrix_get_size(). That is the
most a mapping matrix can be and still fit an Ogg OpusHead, which is
a container limit for the muxer to enforce, not an allocator limit;
it excludes only 13th and 14th order (78 KB and 103 KB of cells).
The 255-channel bound stays.
- Extend tests/test_opus_projection.c: check that each built-in pair
inverts itself to within Q15 (46-64 dB, on a diffuse field), and that
a synthetic ambisonic signal -- four tonal sources encoded as plane
waves in ACN/SN3D -- survives encode and decode at every order, with
and without the non-diegetic channels (~27 dB at 64 kbit/s per
channel). The codec figure averages two sets of source directions,
because how sparsely four sources happen to land on the sectors moves
it by 1-2 dB either way, which is more than the matrices differ by.
The synthesis is deterministic and self-contained; no test vectors are
needed.
Tables generated with AmbisonicArrays.jl.
abhayap
force-pushed
the
hoa-projection-orders-6-14
branch
from
August 17, 2026 22:49
8cedc68 to
7b24ed3
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Channel mapping family 3 already accepts up to 227 channels, but the built-in projection matrices stop at 5th order, so anything above that fails in opus_projection_ambisonics_encoder_create(). Add the missing tables for 6th to 14th order in a new src/mapping_matrix_hoa.c.
These are not built the way the 4th- and 5th-order tables are. Those sample the spherical-harmonic matrix at Fliege-Maier cubature nodes and invert it in the least-squares sense, which is fine to 9th order and then degrades badly: cond(A) goes 3.8 at 9th order, 86 at 10th, 723 at 12th, because those node sets are chosen to be good cubature rules rather than good interpolation points, and the 121-point set is the first with a negative weight. Conditioning is what the demixing step costs in SNR -- per-sector coding noise returns ||A||_F.||A^-1||_F/N times louder in the ambisonic domain -- so extending that construction would give up 15 dB at 10th order and 32 dB at 12th.
Instead these sample at maximum-determinant (Fekete) nodes, which come in sets of exactly (N+1)^2 points chosen to maximize |det Y|, and take the polar factor of the result: the nearest matrix with orthonormal columns. That is energy-preserving ambisonic decoding (Zotter, Pomberger & Noisternig, Acta Acustica 98(1), 2012) at the critically sampled limit L = (N+1)^2. At every order it gives cond(A) = 1, needs no gain field, makes the demixing matrix the exact transpose of the mixing matrix, and holds the Q15 round-trip error at the quantization floor instead of letting it grow with order.
Also:
Replace the per-order if/else chains in opus_projection_encoder.c with mapping_matrix_get_ambisonic(), so adding an order is a table entry rather than four more branches in two places.
Drop the 65004-octet cap in mapping_matrix_get_size(). That is the most a mapping matrix can be and still fit an Ogg OpusHead, which is a container limit for the muxer to enforce, not an allocator limit; it excludes only 13th and 14th order (78 KB and 103 KB of cells). The 255-channel bound stays.
Extend tests/test_opus_projection.c: check that each built-in pair inverts itself to within Q15 (46-64 dB, on a diffuse field), and that a synthetic ambisonic signal -- four tonal sources encoded as plane waves in ACN/SN3D -- survives encode and decode at every order, with and without the non-diegetic channels (~27 dB at 64 kbit/s per channel). The codec figure averages two sets of source directions, because how sparsely four sources happen to land on the sectors moves it by 1-2 dB either way, which is more than the matrices differ by. The synthesis is deterministic and self-contained; no test vectors are needed.