Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,25 @@ AmbisonicEncoder::AmbisonicEncoder(size_t number_of_input_channels,
encoding_matrix_ =
Eigen::MatrixXf::Zero(static_cast<int>(number_of_output_channels_),
static_cast<int>(number_of_input_channels_));

// Size the working storage of `ProcessPlanarAudioData()`.
associated_legendre_polynomials_.reserve(alp_generator_.GetNumValues());
sh_coeffs_.assign(number_of_output_channels_, 0.0f);
column_start_.assign(
number_of_input_channels_,
Eigen::VectorXf::Zero(static_cast<int>(number_of_output_channels_)));
column_end_.assign(
number_of_input_channels_,
Eigen::VectorXf::Zero(static_cast<int>(number_of_output_channels_)));
needs_interpolation_.assign(number_of_input_channels_, false);
encoding_frame_ =
Eigen::MatrixXf::Zero(static_cast<int>(number_of_output_channels_),
static_cast<int>(number_of_input_channels_));
interpolated_column_ =
Eigen::VectorXf::Zero(static_cast<int>(number_of_output_channels_));

// `SetSource()` adds at most one entry per input channel.
sources_.reserve(number_of_input_channels_);
}

void AmbisonicEncoder::SetSource(size_t input_channel, float gain,
Expand Down Expand Up @@ -131,17 +150,10 @@ void AmbisonicEncoder::ProcessPlanarAudioData(const AudioBuffer& input_buffer,

// Compute a per-frame encoding matrix (output x input) by linearly
// interpolating each source's parameters across the block. This avoids
// clicks when object positions change quickly.
Eigen::MatrixXf last_encoding =
Eigen::MatrixXf::Zero(static_cast<int>(number_of_output_channels_),
static_cast<int>(number_of_input_channels_));

// Precompute start and target encoding columns for each input channel.
std::vector<Eigen::VectorXf> column_start(number_of_input_channels_);
std::vector<Eigen::VectorXf> column_end(number_of_input_channels_);

// Track which input channels actually need per-frame interpolation.
std::vector<bool> needs_interpolation(number_of_input_channels_, false);
// clicks when object positions change quickly. The start and target
// encoding columns of each input channel go into `column_start_` and
// `column_end_`, and `needs_interpolation_` tracks which input channels
// actually need per-frame interpolation.

// Helper lambda to compare two sets of source parameters for equality.
auto params_equal = [&](const SingleSourceParams& a,
Expand Down Expand Up @@ -169,10 +181,9 @@ void AmbisonicEncoder::ProcessPlanarAudioData(const AudioBuffer& input_buffer,

// For each input channel, compute the encoding matrix column at the start.
for (size_t in_ch = 0; in_ch < number_of_input_channels_; ++in_ch) {
column_start[in_ch] =
Eigen::VectorXf::Zero(static_cast<int>(number_of_output_channels_));
column_end[in_ch] =
Eigen::VectorXf::Zero(static_cast<int>(number_of_output_channels_));
column_start_[in_ch].setZero();
column_end_[in_ch].setZero();
needs_interpolation_[in_ch] = false;

auto it = sources_.find(in_ch);
if (it == sources_.end()) {
Expand All @@ -189,39 +200,39 @@ void AmbisonicEncoder::ProcessPlanarAudioData(const AudioBuffer& input_buffer,
}

// Compute spherical harmonic coefficients for this direction.
std::vector<float> sh_coeffs(number_of_output_channels_);
GetShCoeffs(p.azimuth, p.elevation, ambisonic_order_, sh_coeffs);
std::fill(sh_coeffs_.begin(), sh_coeffs_.end(), 0.0f);
GetShCoeffs(p.azimuth, p.elevation, ambisonic_order_, sh_coeffs_);

// Apply SH damping per-degree if needed. Use normalized_head_radius
// = 0.1f (i.e. damping applies for distance < 0.1).
ApplyWithinHeadSHCoefficientsDamping(p.distance, 0.1f, sh_coeffs);
ApplyWithinHeadSHCoefficientsDamping(p.distance, 0.1f, sh_coeffs_);

// Scale by gain and write into the Eigen vector.
for (size_t out_ch = 0; out_ch < number_of_output_channels_; ++out_ch) {
col(static_cast<int>(out_ch)) = sh_coeffs.at(out_ch) * p.gain;
col(static_cast<int>(out_ch)) = sh_coeffs_.at(out_ch) * p.gain;
}
};

// Always compute the target column.
fill_column(source_properties.target, column_end[in_ch]);
fill_column(source_properties.target, column_end_[in_ch]);

// Decide whether this source needs interpolation across frames.
if (!params_equal(source_properties.current, source_properties.target)) {
// Only compute the start/current column if parameters differ.
fill_column(source_properties.current, column_start[in_ch]);
needs_interpolation[in_ch] = true;
fill_column(source_properties.current, column_start_[in_ch]);
needs_interpolation_[in_ch] = true;
} else {
// Parameters equal: reuse the target column as the start.
column_start[in_ch] = column_end[in_ch];
needs_interpolation[in_ch] = false;
column_start_[in_ch] = column_end_[in_ch];
needs_interpolation_[in_ch] = false;
}
}

// If no channel actually needs interpolation for this block build a single
// static encoding matrix and reuse it for all frames.
bool any_needs_interpolation = false;
for (size_t i = 0; i < number_of_input_channels_; ++i) {
if (needs_interpolation[i]) {
if (needs_interpolation_[i]) {
any_needs_interpolation = true;
break;
}
Expand All @@ -230,28 +241,26 @@ void AmbisonicEncoder::ProcessPlanarAudioData(const AudioBuffer& input_buffer,
if (!any_needs_interpolation) {
// Interpolation disabled: build one static encoding matrix for the
// whole block from the (already computed) target columns and reuse it
// for every frame. This avoids per-frame interpolation and copies.
Eigen::MatrixXf static_encoding(
static_cast<int>(number_of_output_channels_),
static_cast<int>(number_of_input_channels_));
static_encoding.setZero();
// for every frame. This avoids per-frame interpolation and copies. It is
// built in `encoding_matrix_`, which keeps it after the block.
encoding_matrix_.setZero();
for (size_t in_ch = 0; in_ch < number_of_input_channels_; ++in_ch) {
if (column_end[in_ch].isZero(0)) {
if (column_end_[in_ch].isZero(0)) {
continue;
}
for (int out_ch = 0;
out_ch < static_cast<int>(number_of_output_channels_); ++out_ch) {
static_encoding(out_ch, static_cast<int>(in_ch)) =
column_end[in_ch](out_ch);
encoding_matrix_(out_ch, static_cast<int>(in_ch)) =
column_end_[in_ch](out_ch);
}
}

// The output buffer does not alias the matrix or the input, so the
// product is written in place rather than through a temporary.
for (size_t frame = 0; frame < num_frames; ++frame) {
encoded_buffer.col(static_cast<int>(frame)) =
static_encoding * unencoded_buffer.col(static_cast<int>(frame));
encoded_buffer.col(static_cast<int>(frame)).noalias() =
encoding_matrix_ * unencoded_buffer.col(static_cast<int>(frame));
}

last_encoding = static_encoding;
} else {
// Some channels need interpolation: perform per-frame interpolation so
// parameters ramp smoothly across the block.
Expand All @@ -262,45 +271,50 @@ void AmbisonicEncoder::ProcessPlanarAudioData(const AudioBuffer& input_buffer,
alpha = static_cast<float>(frame) / static_cast<float>(num_frames - 1);
}

Eigen::MatrixXf encoding_frame(
static_cast<int>(number_of_output_channels_),
static_cast<int>(number_of_input_channels_));
encoding_frame.setZero();
encoding_frame_.setZero();

// Build encoding_frame column-by-column for each input/source by
// Build encoding_frame_ column-by-column for each input/source by
// interpolating the precomputed start/end columns.
for (size_t in_ch = 0; in_ch < number_of_input_channels_; ++in_ch) {
// If both start and end are zeros, skip.
if (column_start[in_ch].isZero(0) && column_end[in_ch].isZero(0)) {
if (column_start_[in_ch].isZero(0) && column_end_[in_ch].isZero(0)) {
continue;
}

// Interpolate column values directly in coefficient space only if
// this channel needs interpolation. Otherwise, use the target column
// directly to avoid per-frame arithmetic.
Eigen::VectorXf col;
if (needs_interpolation[in_ch]) {
col = column_start[in_ch] +
alpha * (column_end[in_ch] - column_start[in_ch]);
if (needs_interpolation_[in_ch]) {
interpolated_column_ =
column_start_[in_ch] +
alpha * (column_end_[in_ch] - column_start_[in_ch]);
} else {
col = column_end[in_ch];
interpolated_column_ = column_end_[in_ch];
}

// Copy into the encoding frame column.
for (int out_ch = 0;
out_ch < static_cast<int>(number_of_output_channels_); ++out_ch) {
encoding_frame(out_ch, static_cast<int>(in_ch)) = col(out_ch);
encoding_frame_(out_ch, static_cast<int>(in_ch)) =
interpolated_column_(out_ch);
}
}

// Multiply the per-frame encoding matrix with the single-frame input
// column to produce the encoded output for this frame index.
// Note: use .col() with the actual frame index (frame) mapping into the
// row-major mapped buffers.
encoded_buffer.col(static_cast<int>(frame)) =
encoding_frame * unencoded_buffer.col(static_cast<int>(frame));
// row-major mapped buffers. The output buffer does not alias the
// matrix or the input, so the product is written in place.
encoded_buffer.col(static_cast<int>(frame)).noalias() =
encoding_frame_ * unencoded_buffer.col(static_cast<int>(frame));
}

last_encoding = encoding_frame; // keep for later update
// Keep the last frame's matrix (zero for an empty block) so code that
// expects a static encoding matrix still has a reasonable value.
if (num_frames > 0) {
encoding_matrix_ = encoding_frame_;
} else {
encoding_matrix_.setZero();
}
}

Expand All @@ -310,10 +324,6 @@ void AmbisonicEncoder::ProcessPlanarAudioData(const AudioBuffer& input_buffer,
kv.second.current = kv.second.target;
}

// Update the stored encoding matrix to the last frame's matrix so code that
// expects a static encoding matrix still has a reasonable value.
encoding_matrix_ = last_encoding;

// Audio has now been rendered; subsequent `SetSource()` updates ramp from
// the previously audible parameters.
has_processed_ = true;
Expand All @@ -325,8 +335,8 @@ void AmbisonicEncoder::GetShCoeffs(float azimuth, float elevation,
const float azimuth_rad = azimuth * kRadiansFromDegrees;
const float elevation_rad = elevation * kRadiansFromDegrees;

std::vector<float> associated_legendre_polynomials_temp_ =
alp_generator_.Generate(std::sin(elevation_rad));
alp_generator_.Generate(std::sin(elevation_rad),
&associated_legendre_polynomials_);
// Compute the actual spherical harmonics using the generated polynomials.
for (int degree = 0; degree <= ambisonic_order; degree++) {
for (int order = -degree; order <= degree; order++) {
Expand All @@ -339,11 +349,10 @@ void AmbisonicEncoder::GetShCoeffs(float azimuth, float elevation,
const float angle = static_cast<float>(order) * azimuth_rad;
const float last_term = (order >= 0) ? std::cos(angle) : std::sin(-angle);

coeffs.at(row) =
Sn3dNormalization(degree, order) *
associated_legendre_polynomials_temp_[alp_generator_.GetIndex(
degree, std::abs(order))] *
last_term;
coeffs.at(row) = Sn3dNormalization(degree, order) *
associated_legendre_polynomials_[alp_generator_.GetIndex(
degree, std::abs(order))] *
last_term;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class AmbisonicEncoder {
void RemoveSource(size_t input_channel);

/*!\brief Processing callback for planar audio data (AudioBuffer).
*
* The output is written in place frame by frame, so `output_buffer` must
* not be `input_buffer`.
*
* \param input_buffer Input buffer of samples.
* \param output_buffer Output buffer of processed samples.
Expand Down Expand Up @@ -137,6 +140,16 @@ class AmbisonicEncoder {

AssociatedLegendrePolynomialsGenerator alp_generator_;
Eigen::MatrixXf encoding_matrix_;

// Working storage of `ProcessPlanarAudioData()` and `GetShCoeffs()`, sized
// in the constructor so that processing does not allocate.
std::vector<float> associated_legendre_polynomials_;
std::vector<float> sh_coeffs_;
std::vector<Eigen::VectorXf> column_start_;
std::vector<Eigen::VectorXf> column_end_;
std::vector<bool> needs_interpolation_;
Eigen::MatrixXf encoding_frame_;
Eigen::VectorXf interpolated_column_;
};

} // namespace obr
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ AssociatedLegendrePolynomialsGenerator::AssociatedLegendrePolynomialsGenerator(
std::vector<float> AssociatedLegendrePolynomialsGenerator::Generate(
float x) const {
std::vector<float> values(GetNumValues());
Generate(x, &values);
return values;
}

void AssociatedLegendrePolynomialsGenerator::Generate(
float x, std::vector<float>* values_out) const {
ABSL_DCHECK_NE(values_out, nullptr);
// Assigning a size within the capacity does not allocate.
values_out->assign(GetNumValues(), 0.0f);
std::vector<float>& values = *values_out;

// Bases for the recurrence relations.
values[GetIndex(0, 0)] = ComputeValue(0, 0, x, values);
Expand Down Expand Up @@ -78,7 +88,6 @@ std::vector<float> AssociatedLegendrePolynomialsGenerator::Generate(
}
}
}
return values;
}

size_t AssociatedLegendrePolynomialsGenerator::GetNumValues() const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ class AssociatedLegendrePolynomialsGenerator {
*/
std::vector<float> Generate(float x) const;

/*!\brief Generates the associated Legendre polynomials for the given `x`
* into `values_out`, which is resized to `GetNumValues()` and does
* not allocate when its capacity is at least that.
*
* \param x Abscissa (the polynomials' variable).
* \param values_out Output vector of computed sequence values.
*/
void Generate(float x, std::vector<float>* values_out) const;

/*!\brief Gets the produced number of associated Legendre polynomials.
*
* \return Number of associated Legendre polynomials this generator
Expand Down
30 changes: 13 additions & 17 deletions src/renderer/obr/obr_capi/obr/obr/peak_limiter/peak_limiter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <vector>

#include "absl/log/absl_check.h"
#include "obr/audio_buffer/audio_buffer.h"
Expand All @@ -36,30 +35,27 @@ void PeakLimiter::Process(const AudioBuffer& input, AudioBuffer* output) {
const size_t num_channels = input.num_channels();
const size_t num_frames = input.num_frames();

// Get a vector of maximum sample values across all channels.
std::vector<float> max_samples(num_frames, 0.0);
for (const auto& channel : input) {
for (size_t frame = 0; frame < num_frames; ++frame) {
max_samples[frame] = std::max(max_samples[frame], abs(channel[frame]));
// One pass per frame, so the limiter needs no per-call storage: the
// maximum sample value across all channels, the limiter envelope at that
// frame, then the envelope applied to every channel. Each frame reads its
// input before writing its output, so `input` and `output` may be the
// same buffer.
for (size_t frame = 0; frame < num_frames; ++frame) {
float max_sample = 0.0f;
for (size_t channel = 0; channel < num_channels; ++channel) {
max_sample = std::max(max_sample, abs(input[channel][frame]));
}
}

// Get the limiter envelope.
std::vector<float> limiter_env(num_frames, 0.0);
for (size_t frame = 0; frame < num_frames; ++frame) {
double max_req_gain = GetMaximumRequiredGain(max_samples[frame]);
const double max_req_gain = GetMaximumRequiredGain(max_sample);
if (max_req_gain < env_) {
env_ = max_req_gain;
} else {
env_ = release_time_constant_ * (env_ - max_req_gain) + max_req_gain;
}
limiter_env[frame] = static_cast<float>(env_);
}
const float gain = static_cast<float>(env_);

// Apply the limiter envelope to the input buffer.
for (size_t channel = 0; channel < num_channels; ++channel) {
for (size_t frame = 0; frame < num_frames; ++frame) {
(*output)[channel][frame] = input[channel][frame] * limiter_env[frame];
for (size_t channel = 0; channel < num_channels; ++channel) {
(*output)[channel][frame] = input[channel][frame] * gain;
}
}
}
Expand Down
Loading
Loading