mirror of
https://github.com/yuzu-emu/yuzu-android.git
synced 2025-06-25 14:08:13 -05:00
Project Andio
This commit is contained in:
141
src/audio_core/renderer/mix/mix_context.cpp
Normal file
141
src/audio_core/renderer/mix/mix_context.cpp
Normal file
@ -0,0 +1,141 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <ranges>
|
||||
|
||||
#include "audio_core/renderer/mix/mix_context.h"
|
||||
#include "audio_core/renderer/splitter/splitter_context.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
void MixContext::Initialize(std::span<MixInfo*> sorted_mix_infos_, std::span<MixInfo> mix_infos_,
|
||||
const u32 count_, std::span<s32> effect_process_order_buffer_,
|
||||
const u32 effect_count_, std::span<u8> node_states_workbuffer,
|
||||
const u64 node_buffer_size, std::span<u8> edge_matrix_workbuffer,
|
||||
const u64 edge_matrix_size) {
|
||||
count = count_;
|
||||
sorted_mix_infos = sorted_mix_infos_;
|
||||
mix_infos = mix_infos_;
|
||||
effect_process_order_buffer = effect_process_order_buffer_;
|
||||
effect_count = effect_count_;
|
||||
|
||||
if (node_states_workbuffer.size() > 0 && edge_matrix_workbuffer.size() > 0) {
|
||||
node_states.Initialize(node_states_workbuffer, node_buffer_size, count);
|
||||
edge_matrix.Initialize(edge_matrix_workbuffer, edge_matrix_size, count);
|
||||
}
|
||||
|
||||
for (s32 i = 0; i < count; i++) {
|
||||
sorted_mix_infos[i] = &mix_infos[i];
|
||||
}
|
||||
}
|
||||
|
||||
MixInfo* MixContext::GetSortedInfo(const s32 index) {
|
||||
return sorted_mix_infos[index];
|
||||
}
|
||||
|
||||
void MixContext::SetSortedInfo(const s32 index, MixInfo& mix_info) {
|
||||
sorted_mix_infos[index] = &mix_info;
|
||||
}
|
||||
|
||||
MixInfo* MixContext::GetInfo(const s32 index) {
|
||||
return &mix_infos[index];
|
||||
}
|
||||
|
||||
MixInfo* MixContext::GetFinalMixInfo() {
|
||||
return &mix_infos[0];
|
||||
}
|
||||
|
||||
s32 MixContext::GetCount() const {
|
||||
return count;
|
||||
}
|
||||
|
||||
void MixContext::UpdateDistancesFromFinalMix() {
|
||||
for (s32 i = 0; i < count; i++) {
|
||||
mix_infos[i].distance_from_final_mix = InvalidDistanceFromFinalMix;
|
||||
}
|
||||
|
||||
for (s32 i = 0; i < count; i++) {
|
||||
auto& mix_info{mix_infos[i]};
|
||||
sorted_mix_infos[i] = &mix_info;
|
||||
|
||||
if (!mix_info.in_use) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto mix_id{mix_info.mix_id};
|
||||
auto distance_to_final_mix{FinalMixId};
|
||||
|
||||
while (distance_to_final_mix < count) {
|
||||
if (mix_id == FinalMixId) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mix_id == UnusedMixId) {
|
||||
distance_to_final_mix = InvalidDistanceFromFinalMix;
|
||||
break;
|
||||
}
|
||||
|
||||
auto distance_from_final_mix{mix_infos[mix_id].distance_from_final_mix};
|
||||
if (distance_from_final_mix != InvalidDistanceFromFinalMix) {
|
||||
distance_to_final_mix = distance_from_final_mix + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
distance_to_final_mix++;
|
||||
mix_id = mix_infos[mix_id].dst_mix_id;
|
||||
}
|
||||
|
||||
if (distance_to_final_mix >= count) {
|
||||
distance_to_final_mix = InvalidDistanceFromFinalMix;
|
||||
}
|
||||
mix_info.distance_from_final_mix = distance_to_final_mix;
|
||||
}
|
||||
}
|
||||
|
||||
void MixContext::SortInfo() {
|
||||
UpdateDistancesFromFinalMix();
|
||||
|
||||
std::ranges::sort(sorted_mix_infos, [](const MixInfo* lhs, const MixInfo* rhs) {
|
||||
return lhs->distance_from_final_mix > rhs->distance_from_final_mix;
|
||||
});
|
||||
|
||||
CalcMixBufferOffset();
|
||||
}
|
||||
|
||||
void MixContext::CalcMixBufferOffset() {
|
||||
s16 offset{0};
|
||||
for (s32 i = 0; i < count; i++) {
|
||||
auto mix_info{sorted_mix_infos[i]};
|
||||
if (mix_info->in_use) {
|
||||
const auto buffer_count{mix_info->buffer_count};
|
||||
mix_info->buffer_offset = offset;
|
||||
offset += buffer_count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MixContext::TSortInfo(const SplitterContext& splitter_context) {
|
||||
if (!splitter_context.UsingSplitter()) {
|
||||
CalcMixBufferOffset();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!node_states.Tsort(edge_matrix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<s32> sorted_results{node_states.GetSortedResuls()};
|
||||
const auto result_size{std::min(count, static_cast<s32>(sorted_results.size()))};
|
||||
for (s32 i = 0; i < result_size; i++) {
|
||||
sorted_mix_infos[i] = &mix_infos[sorted_results[i]];
|
||||
}
|
||||
|
||||
CalcMixBufferOffset();
|
||||
return true;
|
||||
}
|
||||
|
||||
EdgeMatrix& MixContext::GetEdgeMatrix() {
|
||||
return edge_matrix;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
124
src/audio_core/renderer/mix/mix_context.h
Normal file
124
src/audio_core/renderer/mix/mix_context.h
Normal file
@ -0,0 +1,124 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/renderer/mix/mix_info.h"
|
||||
#include "audio_core/renderer/nodes/edge_matrix.h"
|
||||
#include "audio_core/renderer/nodes/node_states.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
class SplitterContext;
|
||||
|
||||
/*
|
||||
* Manages mixing states, sorting and building a node graph to describe a mix order.
|
||||
*/
|
||||
class MixContext {
|
||||
public:
|
||||
/**
|
||||
* Initialize the mix context.
|
||||
*
|
||||
* @param sorted_mix_infos - Buffer for the sorted mix infos.
|
||||
* @param mix_infos - Buffer for the mix infos.
|
||||
* @param effect_process_order_buffer - Buffer for the effect process orders.
|
||||
* @param effect_count - Number of effects in the buffer.
|
||||
* @param node_states_workbuffer - Buffer for node states.
|
||||
* @param node_buffer_size - Size of the node states buffer.
|
||||
* @param edge_matrix_workbuffer - Buffer for edge matrix.
|
||||
* @param edge_matrix_size - Size of the edge matrix buffer.
|
||||
*/
|
||||
void Initialize(std::span<MixInfo*> sorted_mix_infos, std::span<MixInfo> mix_infos, u32 count_,
|
||||
std::span<s32> effect_process_order_buffer, u32 effect_count,
|
||||
std::span<u8> node_states_workbuffer, u64 node_buffer_size,
|
||||
std::span<u8> edge_matrix_workbuffer, u64 edge_matrix_size);
|
||||
|
||||
/**
|
||||
* Get a sorted mix at the given index.
|
||||
*
|
||||
* @param index - Index of sorted mix.
|
||||
* @return The sorted mix.
|
||||
*/
|
||||
MixInfo* GetSortedInfo(s32 index);
|
||||
|
||||
/**
|
||||
* Set the sorted info at the given index.
|
||||
*
|
||||
* @param index - Index of sorted mix.
|
||||
* @param mix_info - The new mix for this index.
|
||||
*/
|
||||
void SetSortedInfo(s32 index, MixInfo& mix_info);
|
||||
|
||||
/**
|
||||
* Get a mix at the given index.
|
||||
*
|
||||
* @param index - Index of mix.
|
||||
* @return The mix.
|
||||
*/
|
||||
MixInfo* GetInfo(s32 index);
|
||||
|
||||
/**
|
||||
* Get the final mix.
|
||||
*
|
||||
* @return The final mix.
|
||||
*/
|
||||
MixInfo* GetFinalMixInfo();
|
||||
|
||||
/**
|
||||
* Get the current number of mixes.
|
||||
*
|
||||
* @return The number of active mixes.
|
||||
*/
|
||||
s32 GetCount() const;
|
||||
|
||||
/**
|
||||
* Update all of the mixes' distance from the final mix.
|
||||
* Needs to be called after altering the mix graph.
|
||||
*/
|
||||
void UpdateDistancesFromFinalMix();
|
||||
|
||||
/**
|
||||
* Non-splitter sort, sorts the sorted mixes based on their distance from the final mix.
|
||||
*/
|
||||
void SortInfo();
|
||||
|
||||
/**
|
||||
* Re-calculate the mix buffer offsets for each mix after altering the mix.
|
||||
*/
|
||||
void CalcMixBufferOffset();
|
||||
|
||||
/**
|
||||
* Splitter sort, traverse the splitter node graph and sort the sorted mixes from results.
|
||||
*
|
||||
* @param splitter_context - Splitter context for the sort.
|
||||
* @return True if the sort was successful, othewise false.
|
||||
*/
|
||||
bool TSortInfo(const SplitterContext& splitter_context);
|
||||
|
||||
/**
|
||||
* Get the edge matrix used for the mix graph.
|
||||
*
|
||||
* @return The edge matrix used.
|
||||
*/
|
||||
EdgeMatrix& GetEdgeMatrix();
|
||||
|
||||
private:
|
||||
/// Array of sorted mixes
|
||||
std::span<MixInfo*> sorted_mix_infos{};
|
||||
/// Array of mixes
|
||||
std::span<MixInfo> mix_infos{};
|
||||
/// Number of active mixes
|
||||
s32 count{};
|
||||
/// Array of effect process orderings
|
||||
std::span<s32> effect_process_order_buffer{};
|
||||
/// Number of effects in the process ordering buffer
|
||||
u64 effect_count{};
|
||||
/// Node states used in splitter sort
|
||||
NodeStates node_states{};
|
||||
/// Edge matrix for connected nodes used in splitter sort
|
||||
EdgeMatrix edge_matrix{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
120
src/audio_core/renderer/mix/mix_info.cpp
Normal file
120
src/audio_core/renderer/mix/mix_info.cpp
Normal file
@ -0,0 +1,120 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/behavior/behavior_info.h"
|
||||
#include "audio_core/renderer/effect/effect_context.h"
|
||||
#include "audio_core/renderer/mix/mix_info.h"
|
||||
#include "audio_core/renderer/nodes/edge_matrix.h"
|
||||
#include "audio_core/renderer/splitter/splitter_context.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
|
||||
MixInfo::MixInfo(std::span<s32> effect_order_buffer_, s32 effect_count_, BehaviorInfo& behavior)
|
||||
: effect_order_buffer{effect_order_buffer_}, effect_count{effect_count_},
|
||||
long_size_pre_delay_supported{behavior.IsLongSizePreDelaySupported()} {
|
||||
ClearEffectProcessingOrder();
|
||||
}
|
||||
|
||||
void MixInfo::Cleanup() {
|
||||
mix_id = UnusedMixId;
|
||||
dst_mix_id = UnusedMixId;
|
||||
dst_splitter_id = UnusedSplitterId;
|
||||
}
|
||||
|
||||
void MixInfo::ClearEffectProcessingOrder() {
|
||||
for (s32 i = 0; i < effect_count; i++) {
|
||||
effect_order_buffer[i] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
bool MixInfo::Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
|
||||
EffectContext& effect_context, SplitterContext& splitter_context,
|
||||
const BehaviorInfo& behavior) {
|
||||
volume = in_params.volume;
|
||||
sample_rate = in_params.sample_rate;
|
||||
buffer_count = static_cast<s16>(in_params.buffer_count);
|
||||
in_use = in_params.in_use;
|
||||
mix_id = in_params.mix_id;
|
||||
node_id = in_params.node_id;
|
||||
mix_volumes = in_params.mix_volumes;
|
||||
|
||||
bool sort_required{false};
|
||||
if (behavior.IsSplitterSupported()) {
|
||||
sort_required = UpdateConnection(edge_matrix, in_params, splitter_context);
|
||||
} else {
|
||||
if (dst_mix_id != in_params.dest_mix_id) {
|
||||
dst_mix_id = in_params.dest_mix_id;
|
||||
sort_required = true;
|
||||
}
|
||||
dst_splitter_id = UnusedSplitterId;
|
||||
}
|
||||
|
||||
ClearEffectProcessingOrder();
|
||||
|
||||
// Check all effects, and set their order if they belong to this mix.
|
||||
const auto count{effect_context.GetCount()};
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
const auto& info{effect_context.GetInfo(i)};
|
||||
if (mix_id == info.GetMixId()) {
|
||||
const auto processing_order{info.GetProcessingOrder()};
|
||||
if (processing_order > effect_count) {
|
||||
break;
|
||||
}
|
||||
effect_order_buffer[processing_order] = i;
|
||||
}
|
||||
}
|
||||
|
||||
return sort_required;
|
||||
}
|
||||
|
||||
bool MixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
|
||||
SplitterContext& splitter_context) {
|
||||
auto has_new_connection{false};
|
||||
if (dst_splitter_id != UnusedSplitterId) {
|
||||
auto& splitter_info{splitter_context.GetInfo(dst_splitter_id)};
|
||||
has_new_connection = splitter_info.HasNewConnection();
|
||||
}
|
||||
|
||||
// Check if this mix matches the input parameters.
|
||||
// If everything is the same, don't bother updating.
|
||||
if (dst_mix_id == in_params.dest_mix_id && dst_splitter_id == in_params.dest_splitter_id &&
|
||||
!has_new_connection) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset the mix in the graph, as we're about to update it.
|
||||
edge_matrix.RemoveEdges(mix_id);
|
||||
|
||||
if (in_params.dest_mix_id == UnusedMixId) {
|
||||
if (in_params.dest_splitter_id != UnusedSplitterId) {
|
||||
// If the splitter is used, connect this mix to each active destination.
|
||||
auto& splitter_info{splitter_context.GetInfo(in_params.dest_splitter_id)};
|
||||
auto const destination_count{splitter_info.GetDestinationCount()};
|
||||
|
||||
for (u32 i = 0; i < destination_count; i++) {
|
||||
auto destination{
|
||||
splitter_context.GetDesintationData(in_params.dest_splitter_id, i)};
|
||||
|
||||
if (destination) {
|
||||
const auto destination_id{destination->GetMixId()};
|
||||
if (destination_id != UnusedMixId) {
|
||||
edge_matrix.Connect(mix_id, destination_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the splitter is not used, only connect this mix to its destination.
|
||||
edge_matrix.Connect(mix_id, in_params.dest_mix_id);
|
||||
}
|
||||
|
||||
dst_mix_id = in_params.dest_mix_id;
|
||||
dst_splitter_id = in_params.dest_splitter_id;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MixInfo::HasAnyConnection() const {
|
||||
return dst_mix_id != UnusedMixId || dst_splitter_id != UnusedSplitterId;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
124
src/audio_core/renderer/mix/mix_info.h
Normal file
124
src/audio_core/renderer/mix/mix_info.h
Normal file
@ -0,0 +1,124 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::AudioRenderer {
|
||||
class EdgeMatrix;
|
||||
class SplitterContext;
|
||||
class EffectContext;
|
||||
class BehaviorInfo;
|
||||
|
||||
/**
|
||||
* A single mix, which may feed through other mixes in a chain until reaching the final output mix.
|
||||
*/
|
||||
class MixInfo {
|
||||
public:
|
||||
struct InParameter {
|
||||
/* 0x000 */ f32 volume;
|
||||
/* 0x004 */ u32 sample_rate;
|
||||
/* 0x008 */ u32 buffer_count;
|
||||
/* 0x00C */ bool in_use;
|
||||
/* 0x00D */ bool is_dirty;
|
||||
/* 0x010 */ s32 mix_id;
|
||||
/* 0x014 */ u32 effect_count;
|
||||
/* 0x018 */ s32 node_id;
|
||||
/* 0x01C */ char unk01C[0x8];
|
||||
/* 0x024 */ std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes;
|
||||
/* 0x924 */ s32 dest_mix_id;
|
||||
/* 0x928 */ s32 dest_splitter_id;
|
||||
/* 0x92C */ char unk92C[0x4];
|
||||
};
|
||||
static_assert(sizeof(InParameter) == 0x930, "MixInfo::InParameter has the wrong size!");
|
||||
|
||||
struct InDirtyParameter {
|
||||
/* 0x00 */ u32 magic;
|
||||
/* 0x04 */ s32 count;
|
||||
/* 0x08 */ char unk08[0x18];
|
||||
};
|
||||
static_assert(sizeof(InDirtyParameter) == 0x20,
|
||||
"MixInfo::InDirtyParameter has the wrong size!");
|
||||
|
||||
MixInfo(std::span<s32> effect_order_buffer, s32 effect_count, BehaviorInfo& behavior);
|
||||
|
||||
/**
|
||||
* Clean up the mix, resetting it to a default state.
|
||||
*/
|
||||
void Cleanup();
|
||||
|
||||
/**
|
||||
* Clear the effect process order for all effects in this mix.
|
||||
*/
|
||||
void ClearEffectProcessingOrder();
|
||||
|
||||
/**
|
||||
* Update the mix according to the given parameters.
|
||||
*
|
||||
* @param edge_matrix - Updated with new splitter node connections, if supported.
|
||||
* @param in_params - Input parameters.
|
||||
* @param effect_context - Used to update the effect orderings.
|
||||
* @param splitter_context - Used to update the mix graph if supported.
|
||||
* @param behavior - Used for checking which features are supported.
|
||||
* @return True if the mix was updated and a sort is required, otherwise false.
|
||||
*/
|
||||
bool Update(EdgeMatrix& edge_matrix, const InParameter& in_params,
|
||||
EffectContext& effect_context, SplitterContext& splitter_context,
|
||||
const BehaviorInfo& behavior);
|
||||
|
||||
/**
|
||||
* Update the mix's connection in the node graph according to the given parameters.
|
||||
*
|
||||
* @param edge_matrix - Updated with new splitter node connections, if supported.
|
||||
* @param in_params - Input parameters.
|
||||
* @param splitter_context - Used to update the mix graph if supported.
|
||||
* @return True if the mix was updated and a sort is required, otherwise false.
|
||||
*/
|
||||
bool UpdateConnection(EdgeMatrix& edge_matrix, const InParameter& in_params,
|
||||
SplitterContext& splitter_context);
|
||||
|
||||
/**
|
||||
* Check if this mix is connected to any other.
|
||||
*
|
||||
* @return True if the mix has a connection, otherwise false.
|
||||
*/
|
||||
bool HasAnyConnection() const;
|
||||
|
||||
/// Volume of this mix
|
||||
f32 volume{};
|
||||
/// Sample rate of this mix
|
||||
u32 sample_rate{};
|
||||
/// Number of buffers in this mix
|
||||
s16 buffer_count{};
|
||||
/// Is this mix in use?
|
||||
bool in_use{};
|
||||
/// Is this mix enabled?
|
||||
bool enabled{};
|
||||
/// Id of this mix
|
||||
s32 mix_id{UnusedMixId};
|
||||
/// Node id of this mix
|
||||
s32 node_id{};
|
||||
/// Buffer offset for this mix
|
||||
s16 buffer_offset{};
|
||||
/// Distance to the final mix
|
||||
s32 distance_from_final_mix{InvalidDistanceFromFinalMix};
|
||||
/// Array of effect orderings of all effects in this mix
|
||||
std::span<s32> effect_order_buffer;
|
||||
/// Number of effects in this mix
|
||||
const s32 effect_count;
|
||||
/// Id for next mix in the chain
|
||||
s32 dst_mix_id{UnusedMixId};
|
||||
/// Mixing volumes for this mix used when this mix is chained with another
|
||||
std::array<std::array<f32, MaxMixBuffers>, MaxMixBuffers> mix_volumes{};
|
||||
/// Id for next mix in the graph when splitter is used
|
||||
s32 dst_splitter_id{UnusedSplitterId};
|
||||
/// Is a longer pre-delay time supported for the reverb effect?
|
||||
const bool long_size_pre_delay_supported;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioRenderer
|
Reference in New Issue
Block a user