mirror of
https://git.suyu.dev/suyu/suyu
synced 2024-12-26 19:32:40 -06:00
296 lines
9.7 KiB
C++
296 lines
9.7 KiB
C++
// Copyright 2020 yuzu Emulator Project
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include "audio_core/behavior_info.h"
|
|
#include "audio_core/common.h"
|
|
#include "audio_core/effect_context.h"
|
|
#include "audio_core/mix_context.h"
|
|
#include "audio_core/splitter_context.h"
|
|
|
|
namespace AudioCore {
|
|
MixContext::MixContext() = default;
|
|
MixContext::~MixContext() = default;
|
|
|
|
void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count,
|
|
std::size_t effect_count) {
|
|
info_count = mix_count;
|
|
infos.resize(info_count);
|
|
auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
|
|
final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
|
|
sorted_info.reserve(infos.size());
|
|
for (auto& info : infos) {
|
|
sorted_info.push_back(&info);
|
|
}
|
|
|
|
for (auto& info : infos) {
|
|
info.SetEffectCount(effect_count);
|
|
}
|
|
|
|
// Only initialize our edge matrix and node states if splitters are supported
|
|
if (behavior_info.IsSplitterSupported()) {
|
|
node_states.Initialize(mix_count);
|
|
edge_matrix.Initialize(mix_count);
|
|
}
|
|
}
|
|
|
|
void MixContext::UpdateDistancesFromFinalMix() {
|
|
// Set all distances to be invalid
|
|
for (std::size_t i = 0; i < info_count; i++) {
|
|
GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
|
|
}
|
|
|
|
for (std::size_t i = 0; i < info_count; i++) {
|
|
auto& info = GetInfo(i);
|
|
auto& in_params = info.GetInParams();
|
|
// Populate our sorted info
|
|
sorted_info[i] = &info;
|
|
|
|
if (!in_params.in_use) {
|
|
continue;
|
|
}
|
|
|
|
auto mix_id = in_params.mix_id;
|
|
// Needs to be referenced out of scope
|
|
s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
|
|
for (; distance_to_final_mix < static_cast<s32>(info_count); distance_to_final_mix++) {
|
|
if (mix_id == AudioCommon::FINAL_MIX) {
|
|
// If we're at the final mix, we're done
|
|
break;
|
|
} else if (mix_id == AudioCommon::NO_MIX) {
|
|
// If we have no more mix ids, we're done
|
|
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
|
|
break;
|
|
} else {
|
|
const auto& dest_mix = GetInfo(mix_id);
|
|
const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
|
|
|
|
if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
|
|
// If our current mix isn't pointing to a final mix, follow through
|
|
mix_id = dest_mix.GetInParams().dest_mix_id;
|
|
} else {
|
|
// Our current mix + 1 = final distance
|
|
distance_to_final_mix = dest_mix_distance + 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we're out of range for our distance, mark it as no final mix
|
|
if (distance_to_final_mix >= static_cast<s32>(info_count)) {
|
|
distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
|
|
}
|
|
|
|
in_params.final_mix_distance = distance_to_final_mix;
|
|
}
|
|
}
|
|
|
|
void MixContext::CalcMixBufferOffset() {
|
|
s32 offset{};
|
|
for (std::size_t i = 0; i < info_count; i++) {
|
|
auto& info = GetSortedInfo(i);
|
|
auto& in_params = info.GetInParams();
|
|
if (in_params.in_use) {
|
|
// Only update if in use
|
|
in_params.buffer_offset = offset;
|
|
offset += in_params.buffer_count;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MixContext::SortInfo() {
|
|
// Get the distance to the final mix
|
|
UpdateDistancesFromFinalMix();
|
|
|
|
// Sort based on the distance to the final mix
|
|
std::sort(sorted_info.begin(), sorted_info.end(),
|
|
[](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
|
|
return lhs->GetInParams().final_mix_distance >
|
|
rhs->GetInParams().final_mix_distance;
|
|
});
|
|
|
|
// Calculate the mix buffer offset
|
|
CalcMixBufferOffset();
|
|
}
|
|
|
|
bool MixContext::TsortInfo(SplitterContext& splitter_context) {
|
|
// If we're not using mixes, just calculate the mix buffer offset
|
|
if (!splitter_context.UsingSplitter()) {
|
|
CalcMixBufferOffset();
|
|
return true;
|
|
}
|
|
// Sort our node states
|
|
if (!node_states.Tsort(edge_matrix)) {
|
|
return false;
|
|
}
|
|
|
|
// Get our sorted list
|
|
const auto sorted_list = node_states.GetIndexList();
|
|
std::size_t info_id{};
|
|
for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
|
|
// Set our sorted info
|
|
sorted_info[info_id++] = &GetInfo(*itr);
|
|
}
|
|
|
|
// Calculate the mix buffer offset
|
|
CalcMixBufferOffset();
|
|
return true;
|
|
}
|
|
|
|
std::size_t MixContext::GetCount() const {
|
|
return info_count;
|
|
}
|
|
|
|
ServerMixInfo& MixContext::GetInfo(std::size_t i) {
|
|
ASSERT(i < info_count);
|
|
return infos.at(i);
|
|
}
|
|
|
|
const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
|
|
ASSERT(i < info_count);
|
|
return infos.at(i);
|
|
}
|
|
|
|
ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
|
|
ASSERT(i < info_count);
|
|
return *sorted_info.at(i);
|
|
}
|
|
|
|
const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
|
|
ASSERT(i < info_count);
|
|
return *sorted_info.at(i);
|
|
}
|
|
|
|
ServerMixInfo& MixContext::GetFinalMixInfo() {
|
|
return infos.at(AudioCommon::FINAL_MIX);
|
|
}
|
|
|
|
const ServerMixInfo& MixContext::GetFinalMixInfo() const {
|
|
return infos.at(AudioCommon::FINAL_MIX);
|
|
}
|
|
|
|
EdgeMatrix& MixContext::GetEdgeMatrix() {
|
|
return edge_matrix;
|
|
}
|
|
|
|
const EdgeMatrix& MixContext::GetEdgeMatrix() const {
|
|
return edge_matrix;
|
|
}
|
|
|
|
ServerMixInfo::ServerMixInfo() {
|
|
Cleanup();
|
|
}
|
|
ServerMixInfo::~ServerMixInfo() = default;
|
|
|
|
const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
|
|
return in_params;
|
|
}
|
|
|
|
ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
|
|
return in_params;
|
|
}
|
|
|
|
bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
|
|
BehaviorInfo& behavior_info, SplitterContext& splitter_context,
|
|
EffectContext& effect_context) {
|
|
in_params.volume = mix_in.volume;
|
|
in_params.sample_rate = mix_in.sample_rate;
|
|
in_params.buffer_count = mix_in.buffer_count;
|
|
in_params.in_use = mix_in.in_use;
|
|
in_params.mix_id = mix_in.mix_id;
|
|
in_params.node_id = mix_in.node_id;
|
|
for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
|
|
std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
|
|
in_params.mix_volume[i].begin());
|
|
}
|
|
|
|
bool require_sort = false;
|
|
|
|
if (behavior_info.IsSplitterSupported()) {
|
|
require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
|
|
} else {
|
|
in_params.dest_mix_id = mix_in.dest_mix_id;
|
|
in_params.splitter_id = AudioCommon::NO_SPLITTER;
|
|
}
|
|
|
|
ResetEffectProcessingOrder();
|
|
const auto effect_count = effect_context.GetCount();
|
|
for (std::size_t i = 0; i < effect_count; i++) {
|
|
auto* effect_info = effect_context.GetInfo(i);
|
|
if (effect_info->GetMixID() == in_params.mix_id) {
|
|
effect_processing_order[effect_info->GetProcessingOrder()] = static_cast<s32>(i);
|
|
}
|
|
}
|
|
|
|
// TODO(ogniK): Update effect processing order
|
|
return require_sort;
|
|
}
|
|
|
|
bool ServerMixInfo::HasAnyConnection() const {
|
|
return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
|
|
in_params.mix_id != AudioCommon::NO_MIX;
|
|
}
|
|
|
|
void ServerMixInfo::Cleanup() {
|
|
in_params.volume = 0.0f;
|
|
in_params.sample_rate = 0;
|
|
in_params.buffer_count = 0;
|
|
in_params.in_use = false;
|
|
in_params.mix_id = AudioCommon::NO_MIX;
|
|
in_params.node_id = 0;
|
|
in_params.buffer_offset = 0;
|
|
in_params.dest_mix_id = AudioCommon::NO_MIX;
|
|
in_params.splitter_id = AudioCommon::NO_SPLITTER;
|
|
std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
|
|
}
|
|
|
|
void ServerMixInfo::SetEffectCount(std::size_t count) {
|
|
effect_processing_order.resize(count);
|
|
ResetEffectProcessingOrder();
|
|
}
|
|
|
|
void ServerMixInfo::ResetEffectProcessingOrder() {
|
|
for (auto& order : effect_processing_order) {
|
|
order = AudioCommon::NO_EFFECT_ORDER;
|
|
}
|
|
}
|
|
|
|
s32 ServerMixInfo::GetEffectOrder(std::size_t i) const {
|
|
return effect_processing_order.at(i);
|
|
}
|
|
|
|
bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
|
|
SplitterContext& splitter_context) {
|
|
// Mixes are identical
|
|
if (in_params.dest_mix_id == mix_in.dest_mix_id &&
|
|
in_params.splitter_id == mix_in.splitter_id &&
|
|
((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
|
|
!splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
|
|
return false;
|
|
}
|
|
// Remove current edges for mix id
|
|
edge_matrix.RemoveEdges(in_params.mix_id);
|
|
if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
|
|
// If we have a valid destination mix id, set our edge matrix
|
|
edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
|
|
} else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
|
|
// Recurse our splitter linked and set our edges
|
|
auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
|
|
const auto length = splitter_info.GetLength();
|
|
for (s32 i = 0; i < length; i++) {
|
|
const auto* splitter_destination =
|
|
splitter_context.GetDestinationData(mix_in.splitter_id, i);
|
|
if (splitter_destination == nullptr) {
|
|
continue;
|
|
}
|
|
if (splitter_destination->ValidMixId()) {
|
|
edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
|
|
}
|
|
}
|
|
}
|
|
in_params.dest_mix_id = mix_in.dest_mix_id;
|
|
in_params.splitter_id = mix_in.splitter_id;
|
|
return true;
|
|
}
|
|
|
|
} // namespace AudioCore
|