blob: 9b79896b225ad9adaf4ecf0bacf979cefacf9c12 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#include "src/media/audio/audio_core/mixer/position_manager.h"
#include <iterator>
#include <fbl/algorithm.h>
#include <gtest/gtest.h>
namespace media::audio::mixer {
namespace {
constexpr int64_t kFracFrame = kOneFrame.raw_value();
constexpr int64_t kFracHalfFrame = kFracFrame / 2;
// Produce the frame pointer (in source format) for the first frame in the source buffer.
TEST(PositionManagerTest, FirstSourceFrame) {
auto source_chans = 2u;
mixer::PositionManager pos_mgr(source_chans, 2u, 1, 1);
int64_t source_frames = 5;
// Setting this to any non-nullptr value
void* source_void_ptr = static_cast<void*>(&source_frames);
auto source_offset = Fixed(3);
pos_mgr.SetSourceValues(source_void_ptr, source_frames, &source_offset);
uint8_t* expected_source_frame_u8 = static_cast<uint8_t*>(source_void_ptr);
int16_t* expected_source_frame_16 = static_cast<int16_t*>(source_void_ptr);
int32_t* expected_source_frame_32 = static_cast<int32_t*>(source_void_ptr);
float* expected_source_frame_float = static_cast<float*>(source_void_ptr);
EXPECT_EQ(pos_mgr.FirstSourceFrame<uint8_t>(), expected_source_frame_u8);
EXPECT_EQ(pos_mgr.FirstSourceFrame<int16_t>(), expected_source_frame_16);
EXPECT_EQ(pos_mgr.FirstSourceFrame<int32_t>(), expected_source_frame_32);
EXPECT_EQ(pos_mgr.FirstSourceFrame<float>(), expected_source_frame_float);
}
// Produce the frame pointer (in source format) for the last frame in the source buffer.
TEST(PositionManagerTest, LastSourceFrame) {
constexpr auto source_chans = 3u;
mixer::PositionManager pos_mgr(source_chans, 2u, 1, 1);
constexpr int64_t source_frames = 5;
float source[source_chans * source_frames];
const auto source_void = static_cast<void*>(source);
auto source_offset = Fixed(1);
pos_mgr.SetSourceValues(source_void, source_frames, &source_offset);
auto sample_num_of_last_frame = source_chans * (source_frames - 1);
uint8_t* expected_source_frame_u8 = static_cast<uint8_t*>(source_void) + sample_num_of_last_frame;
int16_t* expected_source_frame_16 = static_cast<int16_t*>(source_void) + sample_num_of_last_frame;
int32_t* expected_source_frame_32 = static_cast<int32_t*>(source_void) + sample_num_of_last_frame;
float* expected_source_frame_float = static_cast<float*>(source_void) + sample_num_of_last_frame;
EXPECT_EQ(pos_mgr.LastSourceFrame<uint8_t>(), expected_source_frame_u8);
EXPECT_EQ(pos_mgr.LastSourceFrame<int16_t>(), expected_source_frame_16);
EXPECT_EQ(pos_mgr.LastSourceFrame<int32_t>(), expected_source_frame_32);
EXPECT_EQ(pos_mgr.LastSourceFrame<float>(), expected_source_frame_float);
}
// Produce the frame pointer (in source format) corresponding with the current source offset
// This should take into account both source format container size and num source channels.
TEST(PositionManagerTest, CurrentSourceFrame) {
constexpr auto source_chans = 2u;
constexpr auto dest_chans = 1u;
mixer::PositionManager pos_mgr(source_chans, dest_chans, 1, kFracFrame);
constexpr int64_t source_frames = 2;
int16_t source[source_frames * source_chans];
auto source_void = static_cast<void*>(source);
auto source_start = 1;
auto source_offset = Fixed(source_start);
pos_mgr.SetSourceValues(source_void, source_frames, &source_offset);
auto expected_source_frame = source + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int16_t>(), expected_source_frame);
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
auto expected_source_frame_u8 =
reinterpret_cast<uint8_t*>(source_void) + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<uint8_t>(), expected_source_frame_u8);
auto expected_source_frame_32 =
reinterpret_cast<int32_t*>(source_void) + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int32_t>(), expected_source_frame_32);
auto expected_source_frame_float =
reinterpret_cast<float*>(source_void) + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<float>(), expected_source_frame_float);
source_start = 0;
source_offset = Fixed(source_start);
pos_mgr.SetSourceValues(source_void, source_frames, &source_offset);
expected_source_frame = source + (source_start * source_chans);
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int16_t>(), expected_source_frame);
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
}
// Produce the frame pointer (float*) corresponding with the current destination offset
// This should take into account the number of destination channels.
TEST(PositionManagerTest, CurrentDestFrame) {
constexpr auto source_chans = 1u;
constexpr auto dest_chans = 4u;
mixer::PositionManager pos_mgr(source_chans, dest_chans, 1, kFracFrame);
constexpr auto dest_frames = 2;
float dest[dest_frames * dest_chans];
int64_t dest_offset = 1;
auto expected_dest_frame = dest + (dest_offset * dest_chans);
pos_mgr.SetDestValues(dest, dest_frames, &dest_offset);
EXPECT_EQ(pos_mgr.CurrentDestFrame(), expected_dest_frame);
dest_offset = 0;
expected_dest_frame = dest + (dest_offset * dest_chans);
pos_mgr.SetDestValues(dest, dest_frames, &dest_offset);
EXPECT_EQ(pos_mgr.CurrentDestFrame(), expected_dest_frame);
}
// Write back the latest values of source offset, dest offset, and source modulo.
// This should overwrite existing values at those locations, and include effects of advances.
TEST(PositionManagerTest, UpdateOffsets) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracFrame);
float input;
void* source_void_ptr = static_cast<void*>(&input);
constexpr int64_t source_frames = 1;
auto source_offset = Fixed(0);
pos_mgr.SetSourceValues(source_void_ptr, source_frames, &source_offset);
float data;
float* dest = &data;
auto dest_frames = 1;
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, dest_frames, &dest_offset);
auto step_size = kFracFrame;
auto rate_modulo = 0ul;
auto denominator = 1ul;
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
source_offset = Fixed::FromRaw(27);
dest_offset = 42;
source_position_modulo = 72;
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(0));
EXPECT_EQ(dest_offset, 0);
EXPECT_EQ(source_position_modulo, 72ul);
// Now that rate_modulo and denominator are non-zero, source_position_modulo should be updated
rate_modulo = 1ul;
denominator = 2ul;
source_position_modulo = 0;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
source_offset = Fixed::FromRaw(27);
dest_offset = 42;
source_position_modulo = 72;
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(0));
EXPECT_EQ(dest_offset, 0);
EXPECT_EQ(source_position_modulo, 0ul);
}
TEST(PositionManagerTest, FrameCanBeMixed) {
constexpr auto source_chans = 1u;
constexpr auto dest_chans = 1u;
auto half = Fixed(ffl::FromRatio(1, 2)).raw_value();
mixer::PositionManager pos_mgr(source_chans, dest_chans, half + 1, half + 1);
int16_t source[2 * source_chans];
const auto source_void = static_cast<void*>(source);
Fixed source_offset = ffl::FromRatio(3, 2) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(source_void, std::size(source), &source_offset);
float dest[2u * dest_chans];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 2, &dest_offset);
EXPECT_TRUE(pos_mgr.FrameCanBeMixed());
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
source_offset += Fixed::FromRaw(1);
pos_mgr.SetSourceValues(source_void, std::size(source), &source_offset);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_TRUE(pos_mgr.SourceIsConsumed());
}
TEST(PositionManagerTest, AdvanceFrame_Basic) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracFrame);
uint8_t source[3];
auto source_offset = Fixed(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(kFracFrame, 0, 1, &source_position_modulo);
Fixed expected_source_offset = source_offset + Fixed(1);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_TRUE(pos_mgr.FrameCanBeMixed());
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
}
// When source_offset reaches source_frames, we can no longer mix a frame, and source is consumed.
TEST(PositionManagerTest, AdvanceFrame_SourceReachesEnd) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracFrame);
int32_t source[2];
auto source_offset = Fixed(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3u];
int64_t dest_offset = 2;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(kFracFrame, 0, 1, &source_position_modulo);
Fixed expected_source_offset = source_offset + Fixed(1);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_TRUE(pos_mgr.SourceIsConsumed());
}
// source_modulo starts just one shy of incrementing source_offset, and rate_modulo increments it
// This is the boundary case, exactly where source_modulo affects source_offset
TEST(PositionManagerTest, AdvanceFrame_SourceModuloReachesEnd) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracFrame);
int16_t source[3];
Fixed source_offset = Fixed(2) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
constexpr auto step_size = kFracFrame;
auto rate_modulo = 1ul;
auto denominator = 243ul;
auto source_position_modulo = 242ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
Fixed expected_source_offset = Fixed(2) - Fixed::FromRaw(1);
EXPECT_TRUE(pos_mgr.FrameCanBeMixed());
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int16_t>(), &source[1]);
EXPECT_EQ(pos_mgr.source_offset(), expected_source_offset);
expected_source_offset = Fixed(3);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_EQ(pos_mgr.source_offset(), received_source_offset);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_TRUE(pos_mgr.SourceIsConsumed());
EXPECT_EQ(received_source_offset, Fixed(3));
}
// source_modulo starts two shy of incrementing source_offset, and rate_modulo increments it by one.
// This is the boundary case, one less than where source_modulo affects source_offset
TEST(PositionManagerTest, AdvanceFrame_SourceModuloAlmostReachesEnd) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracFrame);
float source[3];
Fixed source_offset = Fixed(2) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
constexpr auto step_size = kFracFrame;
auto rate_modulo = 1ul;
auto denominator = 243ul;
auto source_position_modulo = 241ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
Fixed expected_source_offset = Fixed(2) - Fixed::FromRaw(1);
EXPECT_TRUE(pos_mgr.FrameCanBeMixed());
EXPECT_EQ(pos_mgr.CurrentSourceFrame<float>(), &source[1]);
EXPECT_EQ(pos_mgr.source_offset(), expected_source_offset);
expected_source_offset = source_offset + Fixed(1);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_EQ(pos_mgr.source_offset(), received_source_offset);
EXPECT_TRUE(pos_mgr.FrameCanBeMixed());
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
EXPECT_EQ(pos_mgr.CurrentSourceFrame<float>(), &source[2]);
EXPECT_EQ(received_source_offset, Fixed(3) - Fixed::FromRaw(1));
}
// When dest_offset reaches dest_frames, we can no longer mix a frame, but source is not consumed.
TEST(PositionManagerTest, AdvanceFrame_DestReachesEnd) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracFrame);
int16_t source[3];
auto source_offset = Fixed(1);
const Fixed expected_source_offset = source_offset + Fixed(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[2];
const int64_t dest_frames = std::size(dest);
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, dest_frames, &dest_offset);
constexpr auto step_size = kFracFrame;
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(step_size, 0, 1, &source_position_modulo);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_EQ(pos_mgr.source_offset(), received_source_offset);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
}
// Unity step_size (no rate_modulo) is the default if SetRateValues is never called.
TEST(PositionManagerTest, AdvanceFrame_NoRateValues) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracFrame);
int16_t source[3];
Fixed source_offset = Fixed(2) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[3];
int64_t dest_offset = 1;
pos_mgr.SetDestValues(dest, 3, &dest_offset);
Fixed expected_source_offset = Fixed(2) - Fixed::FromRaw(1);
EXPECT_TRUE(pos_mgr.FrameCanBeMixed());
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
EXPECT_EQ(pos_mgr.CurrentSourceFrame<int16_t>(), &source[1]);
EXPECT_EQ(pos_mgr.source_offset(), expected_source_offset);
expected_source_offset = Fixed(3) - Fixed::FromRaw(1);
auto received_source_offset = Fixed::FromRaw(pos_mgr.AdvanceFrame());
pos_mgr.UpdateOffsets();
EXPECT_EQ(received_source_offset, expected_source_offset);
EXPECT_EQ(pos_mgr.source_offset(), received_source_offset);
EXPECT_TRUE(pos_mgr.FrameCanBeMixed());
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
}
// AdvanceToEnd is limited by dest.
TEST(PositionManagerTest, AdvanceToEnd_Dest) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracFrame);
int16_t source[12];
Fixed source_offset = Fixed(1) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[5];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
constexpr auto step_size = kFracFrame * 2 - 1;
auto source_position_modulo = 1ul;
auto denominator = 2ul;
pos_mgr.SetRateValues(step_size, 0, denominator, &source_position_modulo);
auto num_source_frames_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frames_skipped, 10);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(11) - Fixed::FromRaw(6));
EXPECT_EQ(dest_offset, 5);
EXPECT_EQ(source_position_modulo, 1ul);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_FALSE(pos_mgr.SourceIsConsumed());
}
// AdvanceToEnd is limited by source, with no rate_modulo factor
TEST(PositionManagerTest, AdvanceToEnd_SourceBasic) {
mixer::PositionManager pos_mgr(1, 1, kFracHalfFrame + 1, kFracHalfFrame + 1);
int16_t source[6];
auto source_offset = Fixed(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[13];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(kFracHalfFrame, 0, 1, &source_position_modulo);
auto num_source_frames_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frames_skipped, 5);
Fixed expect_source_offset = Fixed(ffl::FromRatio(11, 2));
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, expect_source_offset)
<< std::hex << source_offset.raw_value() << " != " << expect_source_offset.raw_value();
EXPECT_EQ(dest_offset, 9);
EXPECT_EQ(source_position_modulo, 0u);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_TRUE(pos_mgr.SourceIsConsumed());
}
// AdvanceToEnd is limited by source; rate_modulo contributes EXACTLY what consumes source.
TEST(PositionManagerTest, AdvanceToEnd_SourceExactModulo) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracHalfFrame + 1);
int16_t source[11];
Fixed source_offset = Fixed(1) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[6];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
constexpr auto step_size = 2 * kFracFrame;
auto rate_modulo = 1ul;
auto denominator = 25ul;
auto source_position_modulo = 20ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
auto num_source_frames_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frames_skipped, 11);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(11));
EXPECT_EQ(dest_offset, 5);
EXPECT_EQ(source_position_modulo, 0ul);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_TRUE(pos_mgr.SourceIsConsumed());
}
// AdvanceToEnd is limited by source; source_position modulo flows beyond source but by < 1 frame.
TEST(PositionManagerTest, AdvanceToEnd_SourceExtraModulo) {
mixer::PositionManager pos_mgr(1, 1, 1, kFracHalfFrame + 1);
int16_t source[11];
Fixed source_offset = Fixed(1) - Fixed::FromRaw(1);
pos_mgr.SetSourceValues(static_cast<void*>(source), std::size(source), &source_offset);
float dest[6];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
constexpr auto step_size = kFracFrame * 2;
auto rate_modulo = 1ul;
auto denominator = 25ul;
auto source_position_modulo = 24ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
auto num_source_frames_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frames_skipped, 11);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(11));
EXPECT_EQ(dest_offset, 5);
EXPECT_EQ(source_position_modulo, 4ul);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_TRUE(pos_mgr.SourceIsConsumed());
}
// AdvanceToEnd is limited by source; step_size and filter lengths are high-magnitude.
TEST(PositionManagerTest, AdvanceToEndExtremeRatesAndWidths) {
mixer::PositionManager pos_mgr(1, 1, (336 << Fixed::Format::FractionalBits) + 1,
(336 << Fixed::Format::FractionalBits) + 1);
float dest[10];
int64_t dest_offset = 0;
pos_mgr.SetDestValues(dest, std::size(dest), &dest_offset);
auto step_size = (24 << Fixed::Format::FractionalBits);
auto rate_modulo = 0ul;
auto denominator = 1ul;
auto source_position_modulo = 0ul;
pos_mgr.SetRateValues(step_size, rate_modulo, denominator, &source_position_modulo);
int16_t source[360];
auto source_offset = Fixed::FromRaw(-1);
pos_mgr.SetSourceValues(static_cast<void*>(source), 336, &source_offset);
auto num_source_frames_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frames_skipped, 24);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset.raw_value(), (24 << Fixed::Format::FractionalBits) - 1);
EXPECT_EQ(dest_offset, 1);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_TRUE(pos_mgr.SourceIsConsumed());
// Now try starting from just one subframe further. This should not advance.
source_offset = Fixed(0);
pos_mgr.SetSourceValues(static_cast<void*>(source), 336, &source_offset);
num_source_frames_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frames_skipped, 0);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed(0));
EXPECT_EQ(dest_offset, 1);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_TRUE(pos_mgr.SourceIsConsumed());
// Back up position but subtract a frame of source supply: we should not advance.
source_offset = Fixed::FromRaw(-1);
pos_mgr.SetSourceValues(static_cast<void*>(source), 335, &source_offset);
num_source_frames_skipped = pos_mgr.AdvanceToEnd();
EXPECT_EQ(num_source_frames_skipped, 0);
pos_mgr.UpdateOffsets();
EXPECT_EQ(source_offset, Fixed::FromRaw(-1));
EXPECT_EQ(dest_offset, 1);
EXPECT_FALSE(pos_mgr.FrameCanBeMixed());
EXPECT_TRUE(pos_mgr.SourceIsConsumed());
}
} // namespace
} // namespace media::audio::mixer