RavEngine
Loading...
Searching...
No Matches
ambisonic_codec_impl.h
1/*
2Copyright 2018 Google Inc. All Rights Reserved.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS-IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17#ifndef RESONANCE_AUDIO_AMBISONICS_AMBISONIC_CODEC_IMPL_H_
18#define RESONANCE_AUDIO_AMBISONICS_AMBISONIC_CODEC_IMPL_H_
19
20#include <cmath>
21#include <vector>
22
23#include "Eigen/Dense"
24#include "ambisonics/ambisonic_codec.h"
25#include "ambisonics/associated_legendre_polynomials_generator.h"
26#include "ambisonics/utils.h"
27#include "base/audio_buffer.h"
28#include "base/constants_and_types.h"
29#include "base/logging.h"
30#include "base/spherical_angle.h"
31#include "utils/pseudoinverse.h"
32
33namespace vraudio {
34// An encoder/decoder for ambisonic sound fields. It supports variable ambisonic
35// order, ACN channel sequencing and SN3D normalization.
36//
37// @tparam NumAngles Used to fix the number of angles to be encoded/decoded at
38// compile-time; use |Eigen::Dynamic| to indicate run-time variability.
39// @tparam NumSphericalHarmonics Used to fix the number of spherical harmonic
40// components at compile time; use |Eigen::Dynamic| to indicate run-time
41// variability.
42template <int NumAngles = Eigen::Dynamic,
43 int NumSphericalHarmonics = Eigen::Dynamic>
45 public:
46 // Spherical harmonics encoder matrix.
48 // Spherical harmonics decoder matrix.
50 // Spherical harmonics encoding of a frame or collection of frames (i.e., a
51 // vector of spherical harmonics).
53 // Decoded sequence of values for each angle / mono frame (i.e., a vector with
54 // a decoded mono frame for each angle).
56
57 // Creates a codec with the given |ambisonic_order| and spherical |angles| to
58 // compute encoder/decoder matrices.
59 AmbisonicCodecImpl(int ambisonic_order,
60 const std::vector<SphericalAngle>& angles);
61
62 // Implements |AmbisonicCodec|.
63 void EncodeBuffer(const AudioBuffer& input, AudioBuffer* output) override;
64 void DecodeBuffer(const AudioBuffer& input, AudioBuffer* output) override;
65 int ambisonic_order() const override;
66 size_t num_angles() const override;
67 size_t num_spherical_harmonics() const override;
68 const std::vector<SphericalAngle>& angles() const override;
69 void set_angles(const std::vector<SphericalAngle>& angles) override;
70
71 // Encodes the given vector.
72
73 void Encode(const Eigen::Ref<const Eigen::MatrixXf> decoded_vector,
74 Eigen::Ref<Eigen::MatrixXf> encoded_vector);
75
76 // Decodes the given vector.
77
78 void Decode(const Eigen::Ref<const Eigen::MatrixXf> encoded_vector,
79 Eigen::Ref<Eigen::MatrixXf> decoded_vector);
80
81 // Gets the ambisonic sound field encoder matrix.
82 const Eigen::Ref<const Eigen::MatrixXf> GetEncoderMatrix();
83
84 // Gets the ambisonic sound field decoder matrix.
85 const Eigen::Ref<const Eigen::MatrixXf> GetDecoderMatrix();
86
87 // Necessary due to Eigen's alignment requirements on some platforms.
88 EIGEN_MAKE_ALIGNED_OPERATOR_NEW
89
90 private:
91 // Returns the unnormalized spherical harmonic
92 // Y_degree^order(azimuth, elevation).
93 float UnnormalizedSphericalHarmonic(int degree, int order,
94 const SphericalAngle& angle) const;
95
96 // The maximum-ordered ambisonic sound field handled by this codec. In the
97 // case of a periphonic codec, this is the order of the ambisonic sound field.
98 const int ambisonic_order_;
99 // Spherical angles used to compute spherical harmonics. For example, for a
100 // decoder, virtual loudspeaker positions; for an encoder, the position(s) of
101 // virtual sources relative to the listener.
102 std::vector<SphericalAngle> angles_;
103 // Current spherical harmonics encoder matrix if encoder_matrix_invalid_ is
104 // false.
105 EncoderMatrix encoder_matrix_;
106 // True if encoder_matrix_ needs to be recomputed.
107 bool encoder_matrix_invalid_;
108 // Current spherical harmonics decoder matrix if encoder_matrix_invalid_ is
109 // false.
110 DecoderMatrix decoder_matrix_;
111 // True if decoder_matrix_ needs to be recomputed.
112 bool decoder_matrix_invalid_;
113 // The associated Legendre polynomial generator for this codec.
115 // Temporary storage for associated Legendre polynomials generated.
116 std::vector<float> associated_legendre_polynomials_temp_;
117};
118
119template <int NumAngles, int NumSphericalHarmonics>
121 int ambisonic_order, const std::vector<SphericalAngle>& angles)
122 : ambisonic_order_(ambisonic_order),
123 alp_generator_(ambisonic_order, false, false) {
124 DCHECK_GE(ambisonic_order_, 0);
125 set_angles(angles);
126}
127
128template <int NumAngles, int NumSphericalHarmonics>
129void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::Encode(
130 const Eigen::Ref<const Eigen::MatrixXf> decoded_vector,
131 Eigen::Ref<Eigen::MatrixXf> encoded_vector) {
132 encoded_vector.noalias() = GetEncoderMatrix() * decoded_vector;
133}
134
135template <int NumAngles, int NumSphericalHarmonics>
136void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::Decode(
137 const Eigen::Ref<const Eigen::MatrixXf> encoded_vector,
138 Eigen::Ref<Eigen::MatrixXf> decoded_vector) {
139 decoded_vector.noalias() = GetDecoderMatrix() * encoded_vector;
140}
141
142template <int NumAngles, int NumSphericalHarmonics>
143void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::EncodeBuffer(
144 const AudioBuffer& input, AudioBuffer* output) {
145 CHECK(output);
146 CHECK_EQ(input.num_channels(), num_angles());
147 CHECK_EQ(output->num_channels(), num_spherical_harmonics());
148 CHECK_EQ(input.num_frames(), output->num_frames());
149
153 unencoded_buffer(&input[0][0], num_angles(), output->GetChannelStride());
154
158 encoded_buffer(&(*output)[0][0], num_spherical_harmonics(),
159 input.GetChannelStride());
160
161 encoded_buffer.noalias() = GetEncoderMatrix() * unencoded_buffer;
162}
163
164template <int NumAngles, int NumSphericalHarmonics>
165void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::DecodeBuffer(
166 const AudioBuffer& input, AudioBuffer* output) {
167 CHECK(output);
168 CHECK_EQ(input.num_channels(), num_spherical_harmonics());
169 CHECK_EQ(output->num_channels(), num_angles());
170 CHECK_EQ(input.num_frames(), output->num_frames());
171
175 encoded_buffer(&input[0][0], num_spherical_harmonics(),
176 input.GetChannelStride());
177
181 decoded_buffer(&(*output)[0][0], num_angles(),
182 output->GetChannelStride());
183
184 decoded_buffer.noalias() = GetDecoderMatrix() * encoded_buffer;
185}
186
187template <int NumAngles, int NumSphericalHarmonics>
189AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::GetEncoderMatrix() {
190 if (encoder_matrix_invalid_) {
191 encoder_matrix_ = EncoderMatrix(
192 GetNumPeriphonicComponents(ambisonic_order_), angles_.size());
193 for (int col = 0; col < encoder_matrix_.cols(); col++) {
194 const SphericalAngle& angle = angles_[col];
195 associated_legendre_polynomials_temp_ =
196 alp_generator_.Generate(std::sin(angle.elevation()));
197 // Compute the actual spherical harmonics using the generated polynomials.
198 for (int degree = 0; degree <= ambisonic_order_; degree++) {
199 for (int order = -degree; order <= degree; order++) {
200 const int row = AcnSequence(degree, order);
201 if (row == -1) {
202 // Skip this spherical harmonic.
203 continue;
204 }
205 encoder_matrix_(row, col) =
206 Sn3dNormalization(degree, order) *
207 UnnormalizedSphericalHarmonic(degree, order, angle);
208 }
209 }
210 }
211 encoder_matrix_invalid_ = false;
212 }
213 return encoder_matrix_;
214}
215
216template <int NumAngles, int NumSphericalHarmonics>
218AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::GetDecoderMatrix() {
219 if (decoder_matrix_invalid_) {
220 decoder_matrix_ = Pseudoinverse<Eigen::MatrixXf>(GetEncoderMatrix());
221 // Condition number of the encoding/decoding matrices. We use the fact that
222 // the decoding matrix is already a (pseudo)-inverse of the encoding matrix.
223 const float condition_number =
224 static_cast<float>(GetEncoderMatrix().norm() * decoder_matrix_.norm());
225 const float num_rows = static_cast<float>(GetEncoderMatrix().rows());
226 const float num_cols = static_cast<float>(GetEncoderMatrix().cols());
227 if (condition_number >
228 1.0f / (std::max(num_rows, num_cols) * kEpsilonFloat)) {
229 LOG(WARNING) << "Ambisonic decoding matrix is ill-conditioned. Results "
230 << "may be inaccurate.";
231 }
232 decoder_matrix_invalid_ = false;
233 }
234 return decoder_matrix_;
235}
236
237template <int NumAngles, int NumSphericalHarmonics>
238int AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::ambisonic_order()
239 const {
240 return ambisonic_order_;
241}
242
243template <int NumAngles, int NumSphericalHarmonics>
244size_t AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::num_angles()
245 const {
246 return angles_.size();
247}
248
249template <int NumAngles, int NumSphericalHarmonics>
250size_t AmbisonicCodecImpl<
251 NumAngles, NumSphericalHarmonics>::num_spherical_harmonics() const {
252 // Return the worst-case scenario (the number of coefficients for a
253 // periphonic sound field).
254 return GetNumPeriphonicComponents(ambisonic_order_);
255}
256
257template <int NumAngles, int NumSphericalHarmonics>
258const std::vector<SphericalAngle>&
259AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::angles() const {
260 return angles_;
261}
262
263template <int NumAngles, int NumSphericalHarmonics>
264void AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::set_angles(
265 const std::vector<SphericalAngle>& angles) {
266 CHECK_GT(angles.size(), 0);
267
268 angles_ = angles;
269 encoder_matrix_invalid_ = decoder_matrix_invalid_ = true;
270}
271
272template <int NumAngles, int NumSphericalHarmonics>
273float AmbisonicCodecImpl<NumAngles, NumSphericalHarmonics>::
274 UnnormalizedSphericalHarmonic(int degree, int order,
275 const SphericalAngle& angle) const {
276 const float last_term =
277 (order >= 0) ? std::cos(static_cast<float>(order) * angle.azimuth())
278 : std::sin(static_cast<float>(-order) * angle.azimuth());
279 return associated_legendre_polynomials_temp_[alp_generator_.GetIndex(
280 degree, std::abs(order))] *
281 last_term;
282}
283
284// Codec for a single source.
285template <int NumSphericalHarmonics = Eigen::Dynamic>
286using MonoAmbisonicCodec = AmbisonicCodecImpl<1, NumSphericalHarmonics>;
287
288// Codec for a N-speaker first-order periphonic setup.
289template <int NumAngles>
290using FirstOrderPeriphonicAmbisonicCodec =
291 AmbisonicCodecImpl<NumAngles, GetNumPeriphonicComponentsStatic<1>::value>;
292
293} // namespace vraudio
294
295#endif // RESONANCE_AUDIO_AMBISONICS_AMBISONIC_CODEC_IMPL_H_
A matrix or vector expression mapping an existing array of data.
Definition Map.h:96
The matrix class, also used for vectors and row-vectors.
Definition Matrix.h:180
A matrix or vector expression mapping an existing expression.
Definition Ref.h:283
Definition ambisonic_codec_impl.h:44
Definition ambisonic_codec.h:28
Definition associated_legendre_polynomials_generator.h:26
Definition audio_buffer.h:78
Definition spherical_angle.h:25
GLM_FUNC_QUALIFIER vec< L, T, Q > sin(vec< L, T, Q > const &v)
Definition func_trigonometric.inl:41
@ Aligned
Definition Constants.h:240
@ RowMajor
Definition Constants.h:321
GLM_FUNC_DECL T angle(qua< T, Q > const &x)
Definition quaternion_trigonometric.inl:6
GLM_FUNC_DECL genType::row_type row(genType const &m, length_t index)
Definition matrix_access.inl:23
const int Dynamic
Definition Constants.h:22