mirror of
https://git.eden-emu.dev/archive/citron
synced 2026-03-28 04:19:33 -04:00
feat: REV15 audio renderer + HID fix (v0.8.0)
Complete SDK15 audio implementation with native float biquads. Fixes TotK 1.4.2 and BotW 1.8.2 boot loops. - Add REV15 float biquad filter support - Implement VoiceInParameterV2 (0x188 bytes) - Add SplitterDestinationV2b with biquad filters - Fix HID sampling number (double state value) - Add AudioSnoopManager and AudioSystemManager - Implement FinalOutputRecorder system - Add FFT and loudness calculator (ITU-R BS.1770) - Add full Limiter effect Resolves boot loops and controller detection in SDK20 games. Signed-off-by: Zephyron <zephyron@citron-emu.org>
This commit is contained in:
67
src/audio_core/renderer/effect/limiter.cpp
Normal file
67
src/audio_core/renderer/effect/limiter.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/renderer/effect/limiter.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
void LimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const EffectInfoBase::InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion1*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion1));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void LimiterInfo::Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const EffectInfoBase::InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper) {
|
||||
auto in_specific{reinterpret_cast<const ParameterVersion2*>(in_params.specific.data())};
|
||||
auto params{reinterpret_cast<ParameterVersion2*>(parameter.data())};
|
||||
|
||||
std::memcpy(params, in_specific, sizeof(ParameterVersion2));
|
||||
mix_id = in_params.mix_id;
|
||||
process_order = in_params.process_order;
|
||||
enabled = in_params.enabled;
|
||||
|
||||
error_info.error_code = ResultSuccess;
|
||||
error_info.address = CpuAddr(0);
|
||||
}
|
||||
|
||||
void LimiterInfo::UpdateForCommandGeneration() {
|
||||
if (enabled) {
|
||||
usage_state = UsageState::Enabled;
|
||||
} else {
|
||||
usage_state = UsageState::Disabled;
|
||||
}
|
||||
|
||||
auto params{reinterpret_cast<ParameterVersion1*>(parameter.data())};
|
||||
params->state = ParameterState::Updated;
|
||||
}
|
||||
|
||||
void LimiterInfo::InitializeResultState(EffectResultState& result_state) {
|
||||
auto limiter_state{reinterpret_cast<State*>(result_state.state.data())};
|
||||
limiter_state->envelope = 1.0f;
|
||||
limiter_state->gain_reduction = 1.0f;
|
||||
limiter_state->peak_hold = 0.0f;
|
||||
limiter_state->peak_hold_count = 0;
|
||||
limiter_state->channel_peaks.fill(0.0f);
|
||||
}
|
||||
|
||||
void LimiterInfo::UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state) {
|
||||
cpu_state = dsp_state;
|
||||
}
|
||||
|
||||
CpuAddr LimiterInfo::GetWorkbuffer(s32 index) {
|
||||
return GetSingleBuffer(index);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::Renderer
|
||||
101
src/audio_core/renderer/effect/limiter.h
Normal file
101
src/audio_core/renderer/effect/limiter.h
Normal file
@@ -0,0 +1,101 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Citron Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "audio_core/common/common.h"
|
||||
#include "audio_core/renderer/effect/effect_info_base.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
/**
|
||||
* A full-featured limiter effect with attack, release, and threshold controls.
|
||||
* More sophisticated than LightLimiter.
|
||||
*/
|
||||
class LimiterInfo : public EffectInfoBase {
|
||||
public:
|
||||
struct ParameterVersion1 {
|
||||
/* 0x00 */ std::array<s8, MaxChannels> inputs;
|
||||
/* 0x06 */ std::array<s8, MaxChannels> outputs;
|
||||
/* 0x0C */ u16 channel_count;
|
||||
/* 0x0E */ u16 padding;
|
||||
/* 0x10 */ s32 sample_rate;
|
||||
/* 0x14 */ f32 attack_time; // Attack time in milliseconds
|
||||
/* 0x18 */ f32 release_time; // Release time in milliseconds
|
||||
/* 0x1C */ f32 threshold; // Threshold in dB
|
||||
/* 0x20 */ f32 makeup_gain; // Makeup gain in dB
|
||||
/* 0x24 */ f32 ratio; // Compression ratio
|
||||
/* 0x28 */ ParameterState state;
|
||||
/* 0x29 */ bool is_enabled;
|
||||
/* 0x2A */ char unk2A[0x2];
|
||||
};
|
||||
static_assert(sizeof(ParameterVersion1) <= sizeof(EffectInfoBase::InParameterVersion1),
|
||||
"LimiterInfo::ParameterVersion1 has the wrong size!");
|
||||
|
||||
using ParameterVersion2 = ParameterVersion1;
|
||||
|
||||
struct State {
|
||||
/* 0x00 */ f32 envelope;
|
||||
/* 0x04 */ f32 gain_reduction;
|
||||
/* 0x08 */ f32 peak_hold;
|
||||
/* 0x0C */ u32 peak_hold_count;
|
||||
/* 0x10 */ std::array<f32, MaxChannels> channel_peaks;
|
||||
};
|
||||
static_assert(sizeof(State) <= sizeof(EffectInfoBase::State),
|
||||
"LimiterInfo::State is too large!");
|
||||
|
||||
/**
|
||||
* Update the info with new parameters.
|
||||
*
|
||||
* @param error_info - Output error information.
|
||||
* @param in_params - Input parameters.
|
||||
* @param pool_mapper - Memory pool mapper for buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const EffectInfoBase::InParameterVersion1& in_params,
|
||||
const PoolMapper& pool_mapper);
|
||||
|
||||
/**
|
||||
* Update the info with new parameters (version 2).
|
||||
*
|
||||
* @param error_info - Output error information.
|
||||
* @param in_params - Input parameters.
|
||||
* @param pool_mapper - Memory pool mapper for buffers.
|
||||
*/
|
||||
void Update(BehaviorInfo::ErrorInfo& error_info,
|
||||
const EffectInfoBase::InParameterVersion2& in_params,
|
||||
const PoolMapper& pool_mapper);
|
||||
|
||||
/**
|
||||
* Update the usage state for command generation.
|
||||
*/
|
||||
void UpdateForCommandGeneration();
|
||||
|
||||
/**
|
||||
* Initialize the result state.
|
||||
*
|
||||
* @param result_state - Result state to initialize.
|
||||
*/
|
||||
void InitializeResultState(EffectResultState& result_state);
|
||||
|
||||
/**
|
||||
* Update the result state.
|
||||
*
|
||||
* @param cpu_state - CPU-side result state.
|
||||
* @param dsp_state - DSP-side result state.
|
||||
*/
|
||||
void UpdateResultState(EffectResultState& cpu_state, EffectResultState& dsp_state);
|
||||
|
||||
/**
|
||||
* Get a workbuffer address.
|
||||
*
|
||||
* @param index - Index of the workbuffer.
|
||||
* @return Address of the workbuffer.
|
||||
*/
|
||||
CpuAddr GetWorkbuffer(s32 index);
|
||||
};
|
||||
|
||||
} // namespace AudioCore::Renderer
|
||||
Reference in New Issue
Block a user