From 458da8a94877677f086f06cdeecf959ec4283a33 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Sat, 16 Jul 2022 23:48:45 +0100 Subject: Project Andio --- .../renderer/splitter/splitter_context.cpp | 217 +++++++++++++++++++++ .../renderer/splitter/splitter_context.h | 189 ++++++++++++++++++ .../splitter/splitter_destinations_data.cpp | 87 +++++++++ .../renderer/splitter/splitter_destinations_data.h | 135 +++++++++++++ src/audio_core/renderer/splitter/splitter_info.cpp | 79 ++++++++ src/audio_core/renderer/splitter/splitter_info.h | 107 ++++++++++ 6 files changed, 814 insertions(+) create mode 100644 src/audio_core/renderer/splitter/splitter_context.cpp create mode 100644 src/audio_core/renderer/splitter/splitter_context.h create mode 100644 src/audio_core/renderer/splitter/splitter_destinations_data.cpp create mode 100644 src/audio_core/renderer/splitter/splitter_destinations_data.h create mode 100644 src/audio_core/renderer/splitter/splitter_info.cpp create mode 100644 src/audio_core/renderer/splitter/splitter_info.h (limited to 'src/audio_core/renderer/splitter') diff --git a/src/audio_core/renderer/splitter/splitter_context.cpp b/src/audio_core/renderer/splitter/splitter_context.cpp new file mode 100644 index 000000000..7a23ba43f --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_context.cpp @@ -0,0 +1,217 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/common/audio_renderer_parameter.h" +#include "audio_core/common/workbuffer_allocator.h" +#include "audio_core/renderer/behavior/behavior_info.h" +#include "audio_core/renderer/splitter/splitter_context.h" +#include "common/alignment.h" + +namespace AudioCore::AudioRenderer { + +SplitterDestinationData* SplitterContext::GetDesintationData(const s32 splitter_id, + const s32 destination_id) { + return splitter_infos[splitter_id].GetData(destination_id); +} + +SplitterInfo& SplitterContext::GetInfo(const s32 splitter_id) { + return splitter_infos[splitter_id]; +} + +u32 SplitterContext::GetDataCount() const { + return destinations_count; +} + +u32 SplitterContext::GetInfoCount() const { + return info_count; +} + +SplitterDestinationData& SplitterContext::GetData(const u32 index) { + return splitter_destinations[index]; +} + +void SplitterContext::Setup(std::span splitter_infos_, const u32 splitter_info_count_, + SplitterDestinationData* splitter_destinations_, + const u32 destination_count_, const bool splitter_bug_fixed_) { + splitter_infos = splitter_infos_; + info_count = splitter_info_count_; + splitter_destinations = splitter_destinations_; + destinations_count = destination_count_; + splitter_bug_fixed = splitter_bug_fixed_; +} + +bool SplitterContext::UsingSplitter() const { + return splitter_infos.size() > 0 && info_count > 0 && splitter_destinations != nullptr && + destinations_count > 0; +} + +void SplitterContext::ClearAllNewConnectionFlag() { + for (s32 i = 0; i < info_count; i++) { + splitter_infos[i].SetNewConnectionFlag(); + } +} + +bool SplitterContext::Initialize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params, + WorkbufferAllocator& allocator) { + if (behavior.IsSplitterSupported() && params.splitter_infos > 0 && + params.splitter_destinations > 0) { + splitter_infos = allocator.Allocate(params.splitter_infos, 0x10); + + for (u32 i = 0; i < params.splitter_infos; i++) { + std::construct_at(&splitter_infos[i], static_cast(i)); + } + + if (splitter_infos.size() == 0) { + splitter_infos = {}; + return false; + } + + splitter_destinations = + allocator.Allocate(params.splitter_destinations, 0x10).data(); + + for (s32 i = 0; i < params.splitter_destinations; i++) { + std::construct_at(&splitter_destinations[i], i); + } + + if (params.splitter_destinations <= 0) { + splitter_infos = {}; + splitter_destinations = nullptr; + return false; + } + + Setup(splitter_infos, params.splitter_infos, splitter_destinations, + params.splitter_destinations, behavior.IsSplitterBugFixed()); + } + return true; +} + +bool SplitterContext::Update(const u8* input, u32& consumed_size) { + auto in_params{reinterpret_cast(input)}; + + if (destinations_count == 0 || info_count == 0) { + consumed_size = 0; + return true; + } + + if (in_params->magic != GetSplitterInParamHeaderMagic()) { + consumed_size = 0; + return false; + } + + for (auto& splitter_info : splitter_infos) { + splitter_info.ClearNewConnectionFlag(); + } + + u32 offset{sizeof(InParameterHeader)}; + offset = UpdateInfo(input, offset, in_params->info_count); + offset = UpdateData(input, offset, in_params->destination_count); + + consumed_size = Common::AlignUp(offset, 0x10); + return true; +} + +u32 SplitterContext::UpdateInfo(const u8* input, u32 offset, const u32 splitter_count) { + for (u32 i = 0; i < splitter_count; i++) { + auto info_header{reinterpret_cast(input + offset)}; + + if (info_header->magic != GetSplitterInfoMagic()) { + continue; + } + + if (info_header->id < 0 || info_header->id > info_count) { + break; + } + + auto& info{splitter_infos[info_header->id]}; + RecomposeDestination(info, info_header); + + offset += info.Update(info_header); + } + + return offset; +} + +u32 SplitterContext::UpdateData(const u8* input, u32 offset, const u32 count) { + for (u32 i = 0; i < count; i++) { + auto data_header{ + reinterpret_cast(input + offset)}; + + if (data_header->magic != GetSplitterSendDataMagic()) { + continue; + } + + if (data_header->id < 0 || data_header->id > destinations_count) { + continue; + } + + splitter_destinations[data_header->id].Update(*data_header); + offset += sizeof(SplitterDestinationData::InParameter); + } + + return offset; +} + +void SplitterContext::UpdateInternalState() { + for (s32 i = 0; i < info_count; i++) { + splitter_infos[i].UpdateInternalState(); + } +} + +void SplitterContext::RecomposeDestination(SplitterInfo& out_info, + const SplitterInfo::InParameter* info_header) { + auto destination{out_info.GetData(0)}; + while (destination != nullptr) { + auto dest{destination->GetNext()}; + destination->SetNext(nullptr); + destination = dest; + } + out_info.SetDestinations(nullptr); + + auto dest_count{info_header->destination_count}; + if (!splitter_bug_fixed) { + dest_count = std::min(dest_count, GetDestCountPerInfoForCompat()); + } + + if (dest_count == 0) { + return; + } + + std::span destination_ids{reinterpret_cast(&info_header[1]), dest_count}; + + auto head{&splitter_destinations[destination_ids[0]]}; + auto current_destination{head}; + for (u32 i = 1; i < dest_count; i++) { + auto next_destination{&splitter_destinations[destination_ids[i]]}; + current_destination->SetNext(next_destination); + current_destination = next_destination; + } + + out_info.SetDestinations(head); + out_info.SetDestinationCount(dest_count); +} + +u32 SplitterContext::GetDestCountPerInfoForCompat() const { + if (info_count <= 0) { + return 0; + } + return static_cast(destinations_count / info_count); +} + +u64 SplitterContext::CalcWorkBufferSize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params) { + u64 size{0}; + if (!behavior.IsSplitterSupported()) { + return size; + } + + size += params.splitter_destinations * sizeof(SplitterDestinationData) + + params.splitter_infos * sizeof(SplitterInfo); + + if (behavior.IsSplitterBugFixed()) { + size += Common::AlignUp(params.splitter_destinations * sizeof(u32), 0x10); + } + return size; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_context.h b/src/audio_core/renderer/splitter/splitter_context.h new file mode 100644 index 000000000..cfd092b4f --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_context.h @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "audio_core/renderer/splitter/splitter_destinations_data.h" +#include "audio_core/renderer/splitter/splitter_info.h" +#include "common/common_types.h" + +namespace AudioCore { +struct AudioRendererParameterInternal; +class WorkbufferAllocator; + +namespace AudioRenderer { +class BehaviorInfo; + +/** + * The splitter allows much more control over how sound is mixed together. + * Previously, one mix can only connect to one other, and you may need + * more mixes (and duplicate processing) to achieve the same result. + * With the splitter, many-to-one and one-to-many mixing is possible. + * This was added in revision 2. + * Had a bug with incorrect numbers of destinations, fixed in revision 5. + */ +class SplitterContext { + struct InParameterHeader { + /* 0x00 */ u32 magic; // 'SNDH' + /* 0x04 */ s32 info_count; + /* 0x08 */ s32 destination_count; + /* 0x0C */ char unk0C[0x14]; + }; + static_assert(sizeof(InParameterHeader) == 0x20, + "SplitterContext::InParameterHeader has the wrong size!"); + +public: + /** + * Get a destination mix from the given splitter and destination index. + * + * @param splitter_id - Splitter index to get from. + * @param destination_id - Destination index within the splitter. + * @return Pointer to the found destination. May be nullptr. + */ + SplitterDestinationData* GetDesintationData(s32 splitter_id, s32 destination_id); + + /** + * Get a splitter from the given index. + * + * @param index - Index of the desired splitter. + * @return Splitter requested. + */ + SplitterInfo& GetInfo(s32 index); + + /** + * Get the total number of splitter destinations. + * + * @return Number of destiantions. + */ + u32 GetDataCount() const; + + /** + * Get the total number of splitters. + * + * @return Number of splitters. + */ + u32 GetInfoCount() const; + + /** + * Get a specific global destination. + * + * @param index - Index of the desired destination. + * @return The requested destination. + */ + SplitterDestinationData& GetData(u32 index); + + /** + * Check if the splitter is in use. + * + * @return True if any splitter or destination is in use, otherwise false. + */ + bool UsingSplitter() const; + + /** + * Mark all splitters as having new connections. + */ + void ClearAllNewConnectionFlag(); + + /** + * Initialize the context. + * + * @param behavior - Used to check for splitter support. + * @param params - Input parameters. + * @param allocator - Allocator used to allocate workbuffer memory. + */ + bool Initialize(const BehaviorInfo& behavior, const AudioRendererParameterInternal& params, + WorkbufferAllocator& allocator); + + /** + * Update the context. + * + * @param input - Input buffer with the new info, + * expected to point to a InParameterHeader. + * @param consumed_size - Output with the number of bytes consumed from input. + */ + bool Update(const u8* input, u32& consumed_size); + + /** + * Update the splitters. + * + * @param input - Input buffer with the new info. + * @param offset - Current offset within the input buffer, + * input + offset should point to a SplitterInfo::InParameter. + * @param splitter_count - Number of splitters in the input buffer. + * @return Number of bytes consumed in input. + */ + u32 UpdateInfo(const u8* input, u32 offset, u32 splitter_count); + + /** + * Update the splitters. + * + * @param input - Input buffer with the new info. + * @param offset - Current offset within the input buffer, + * input + offset should point to a + * SplitterDestinationData::InParameter. + * @param destination_count - Number of destinations in the input buffer. + * @return Number of bytes consumed in input. + */ + u32 UpdateData(const u8* input, u32 offset, u32 destination_count); + + /** + * Update the state of all destinations in all splitters. + */ + void UpdateInternalState(); + + /** + * Replace the given splitter's destinations with new ones. + * + * @param out_info - Splitter to recompose. + * @param info_header - Input parameters containing new destination ids. + */ + void RecomposeDestination(SplitterInfo& out_info, const SplitterInfo::InParameter* info_header); + + /** + * Old calculation for destinations, this is the thing the splitter bug fixes. + * Left for compatibility, and now min'd with the actual count to not bug. + * + * @return Number of splitter destinations. + */ + u32 GetDestCountPerInfoForCompat() const; + + /** + * Calculate the size of the required workbuffer for splitters and destinations. + * + * @param behavior - Used to check splitter features. + * @param params - Input parameters with splitter/destination counts. + * @return Required buffer size. + */ + static u64 CalcWorkBufferSize(const BehaviorInfo& behavior, + const AudioRendererParameterInternal& params); + +private: + /** + * Setup the context. + * + * @param splitter_infos - Workbuffer for splitters. + * @param splitter_info_count - Number of splitters in the workbuffer. + * @param splitter_destinations - Workbuffer for splitter destinations. + * @param destination_count - Number of destinations in the workbuffer. + * @param splitter_bug_fixed - Is the splitter bug fixed? + */ + void Setup(std::span splitter_infos, u32 splitter_info_count, + SplitterDestinationData* splitter_destinations, u32 destination_count, + bool splitter_bug_fixed); + + /// Workbuffer for splitters + std::span splitter_infos{}; + /// Number of splitters in buffer + s32 info_count{}; + /// Workbuffer for destinations + SplitterDestinationData* splitter_destinations{}; + /// Number of destinations in buffer + s32 destinations_count{}; + /// Is the splitter bug fixed? + bool splitter_bug_fixed{}; +}; + +} // namespace AudioRenderer +} // namespace AudioCore diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.cpp b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp new file mode 100644 index 000000000..b27d44896 --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.cpp @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/splitter/splitter_destinations_data.h" + +namespace AudioCore::AudioRenderer { + +SplitterDestinationData::SplitterDestinationData(const s32 id_) : id{id_} {} + +void SplitterDestinationData::ClearMixVolume() { + mix_volumes.fill(0.0f); + prev_mix_volumes.fill(0.0f); +} + +s32 SplitterDestinationData::GetId() const { + return id; +} + +bool SplitterDestinationData::IsConfigured() const { + return in_use && destination_id != UnusedMixId; +} + +s32 SplitterDestinationData::GetMixId() const { + return destination_id; +} + +f32 SplitterDestinationData::GetMixVolume(const u32 index) const { + if (index >= mix_volumes.size()) { + LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolume Invalid index {}", index); + return 0.0f; + } + return mix_volumes[index]; +} + +std::span SplitterDestinationData::GetMixVolume() { + return mix_volumes; +} + +f32 SplitterDestinationData::GetMixVolumePrev(const u32 index) const { + if (index >= prev_mix_volumes.size()) { + LOG_ERROR(Service_Audio, "SplitterDestinationData::GetMixVolumePrev Invalid index {}", + index); + return 0.0f; + } + return prev_mix_volumes[index]; +} + +std::span SplitterDestinationData::GetMixVolumePrev() { + return prev_mix_volumes; +} + +void SplitterDestinationData::Update(const InParameter& params) { + if (params.id != id || params.magic != GetSplitterSendDataMagic()) { + return; + } + + destination_id = params.mix_id; + mix_volumes = params.mix_volumes; + + if (!in_use && params.in_use) { + prev_mix_volumes = mix_volumes; + need_update = false; + } + + in_use = params.in_use; +} + +void SplitterDestinationData::MarkAsNeedToUpdateInternalState() { + need_update = true; +} + +void SplitterDestinationData::UpdateInternalState() { + if (in_use && need_update) { + prev_mix_volumes = mix_volumes; + } + need_update = false; +} + +SplitterDestinationData* SplitterDestinationData::GetNext() const { + return next; +} + +void SplitterDestinationData::SetNext(SplitterDestinationData* next_) { + next = next_; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_destinations_data.h b/src/audio_core/renderer/splitter/splitter_destinations_data.h new file mode 100644 index 000000000..bd3d55748 --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_destinations_data.h @@ -0,0 +1,135 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#include "audio_core/common/common.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents a mixing node, can be connected to a previous and next destination forming a chain + * that a certain mix buffer will pass through to output. + */ +class SplitterDestinationData { +public: + struct InParameter { + /* 0x00 */ u32 magic; // 'SNDD' + /* 0x04 */ s32 id; + /* 0x08 */ std::array mix_volumes; + /* 0x68 */ u32 mix_id; + /* 0x6C */ bool in_use; + }; + static_assert(sizeof(InParameter) == 0x70, + "SplitterDestinationData::InParameter has the wrong size!"); + + SplitterDestinationData(s32 id); + + /** + * Reset the mix volumes for this destination. + */ + void ClearMixVolume(); + + /** + * Get the id of this destination. + * + * @return Id for this destination. + */ + s32 GetId() const; + + /** + * Check if this destination is correctly configured. + * + * @return True if configured, otherwise false. + */ + bool IsConfigured() const; + + /** + * Get the mix id for this destination. + * + * @return Mix id for this destination. + */ + s32 GetMixId() const; + + /** + * Get the current mix volume of a given index in this destination. + * + * @param index - Mix buffer index to get the volume for. + * @return Current volume of the specified mix. + */ + f32 GetMixVolume(u32 index) const; + + /** + * Get the current mix volumes for all mix buffers in this destination. + * + * @return Span of current mix buffer volumes. + */ + std::span GetMixVolume(); + + /** + * Get the previous mix volume of a given index in this destination. + * + * @param index - Mix buffer index to get the volume for. + * @return Previous volume of the specified mix. + */ + f32 GetMixVolumePrev(u32 index) const; + + /** + * Get the previous mix volumes for all mix buffers in this destination. + * + * @return Span of previous mix buffer volumes. + */ + std::span GetMixVolumePrev(); + + /** + * Update this destination. + * + * @param params - Inpout parameters to update the destination. + */ + void Update(const InParameter& params); + + /** + * Mark this destination as needing its volumes updated. + */ + void MarkAsNeedToUpdateInternalState(); + + /** + * Copy current volumes to previous if an update is required. + */ + void UpdateInternalState(); + + /** + * Get the next destination in the mix chain. + * + * @return The next splitter destination, may be nullptr if this is the last in the chain. + */ + SplitterDestinationData* GetNext() const; + + /** + * Set the next destination in the mix chain. + * + * @param next - Destination this one is to be connected to. + */ + void SetNext(SplitterDestinationData* next); + +private: + /// Id of this destination + const s32 id; + /// Mix id this destination represents + s32 destination_id{UnusedMixId}; + /// Current mix volumes + std::array mix_volumes{0.0f}; + /// Previous mix volumes + std::array prev_mix_volumes{0.0f}; + /// Next destination in the mix chain + SplitterDestinationData* next{}; + /// Is this destiantion in use? + bool in_use{}; + /// Does this destiantion need its volumes updated? + bool need_update{}; +}; + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_info.cpp b/src/audio_core/renderer/splitter/splitter_info.cpp new file mode 100644 index 000000000..1aee6720b --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_info.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "audio_core/renderer/splitter/splitter_info.h" + +namespace AudioCore::AudioRenderer { + +SplitterInfo::SplitterInfo(const s32 id_) : id{id_} {} + +void SplitterInfo::InitializeInfos(SplitterInfo* splitters, const u32 count) { + if (splitters == nullptr) { + return; + } + + for (u32 i = 0; i < count; i++) { + auto& splitter{splitters[i]}; + splitter.destinations = nullptr; + splitter.destination_count = 0; + splitter.has_new_connection = true; + } +} + +u32 SplitterInfo::Update(const InParameter* params) { + if (params->id != id) { + return 0; + } + sample_rate = params->sample_rate; + has_new_connection = true; + return static_cast((sizeof(InParameter) + 3 * sizeof(s32)) + + params->destination_count * sizeof(s32)); +} + +SplitterDestinationData* SplitterInfo::GetData(const u32 destination_id) { + auto out_destination{destinations}; + u32 i{0}; + while (i < destination_id) { + if (out_destination == nullptr) { + break; + } + out_destination = out_destination->GetNext(); + i++; + } + + return out_destination; +} + +u32 SplitterInfo::GetDestinationCount() const { + return destination_count; +} + +void SplitterInfo::SetDestinationCount(const u32 count) { + destination_count = count; +} + +bool SplitterInfo::HasNewConnection() const { + return has_new_connection; +} + +void SplitterInfo::ClearNewConnectionFlag() { + has_new_connection = false; +} + +void SplitterInfo::SetNewConnectionFlag() { + has_new_connection = true; +} + +void SplitterInfo::UpdateInternalState() { + auto destination{destinations}; + while (destination != nullptr) { + destination->UpdateInternalState(); + destination = destination->GetNext(); + } +} + +void SplitterInfo::SetDestinations(SplitterDestinationData* destinations_) { + destinations = destinations_; +} + +} // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/splitter/splitter_info.h b/src/audio_core/renderer/splitter/splitter_info.h new file mode 100644 index 000000000..d1d75064c --- /dev/null +++ b/src/audio_core/renderer/splitter/splitter_info.h @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "audio_core/renderer/splitter/splitter_destinations_data.h" +#include "common/common_types.h" + +namespace AudioCore::AudioRenderer { +/** + * Represents a splitter, wraps multiple output destinations to split an input mix into. + */ +class SplitterInfo { +public: + struct InParameter { + /* 0x00 */ u32 magic; // 'SNDI' + /* 0x04 */ s32 id; + /* 0x08 */ u32 sample_rate; + /* 0x0C */ u32 destination_count; + }; + static_assert(sizeof(InParameter) == 0x10, "SplitterInfo::InParameter has the wrong size!"); + + explicit SplitterInfo(s32 id); + + /** + * Initialize the given splitters. + * + * @param splitters - Splitters to initialize. + * @param count - Number of splitters given. + */ + static void InitializeInfos(SplitterInfo* splitters, u32 count); + + /** + * Update this splitter. + * + * @param params - Input parameters to update with. + * @return The size in bytes of this splitter. + */ + u32 Update(const InParameter* params); + + /** + * Get a destination in this splitter. + * + * @param id - Destination id to get. + * @return Pointer to the destination, may be nullptr. + */ + SplitterDestinationData* GetData(u32 id); + + /** + * Get the number of destinations in this splitter. + * + * @return The number of destiantions. + */ + u32 GetDestinationCount() const; + + /** + * Set the number of destinations in this splitter. + * + * @param count - The new number of destiantions. + */ + void SetDestinationCount(u32 count); + + /** + * Check if the splitter has a new connection. + * + * @return True if there is a new connection, otherwise false. + */ + bool HasNewConnection() const; + + /** + * Reset the new connection flag. + */ + void ClearNewConnectionFlag(); + + /** + * Mark as having a new connection. + */ + void SetNewConnectionFlag(); + + /** + * Update the state of all destinations. + */ + void UpdateInternalState(); + + /** + * Set this splitter's destinations. + * + * @param destinations - The new destination list for this splitter. + */ + void SetDestinations(SplitterDestinationData* destinations); + +private: + /// Id of this splitter + s32 id; + /// Sample rate of this splitter + u32 sample_rate{}; + /// Number of destinations in this splitter + u32 destination_count{}; + /// Does this splitter have a new connection? + bool has_new_connection{true}; + /// Pointer to the destinations of this splitter + SplitterDestinationData* destinations{}; + /// Number of channels this splitter manages + u32 channel_count{}; +}; + +} // namespace AudioCore::AudioRenderer -- cgit v1.2.3