Project Andio

This commit is contained in:
Kelebek1 2022-07-16 23:48:45 +01:00
parent 6e36f4d230
commit 458da8a948
270 changed files with 33712 additions and 8445 deletions

View file

@ -0,0 +1,714 @@
// 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/command/command_buffer.h"
#include "audio_core/renderer/command/command_list_header.h"
#include "audio_core/renderer/command/command_processing_time_estimator.h"
#include "audio_core/renderer/effect/biquad_filter.h"
#include "audio_core/renderer/effect/delay.h"
#include "audio_core/renderer/effect/reverb.h"
#include "audio_core/renderer/memory/memory_pool_info.h"
#include "audio_core/renderer/mix/mix_info.h"
#include "audio_core/renderer/sink/circular_buffer_sink_info.h"
#include "audio_core/renderer/sink/device_sink_info.h"
#include "audio_core/renderer/sink/sink_info_base.h"
#include "audio_core/renderer/voice/voice_info.h"
#include "audio_core/renderer/voice/voice_state.h"
namespace AudioCore::AudioRenderer {
template <typename T, CommandId Id>
T& CommandBuffer::GenerateStart(const s32 node_id) {
if (size + sizeof(T) >= command_list.size_bytes()) {
LOG_ERROR(
Service_Audio,
"Attempting to write commands beyond the end of allocated command buffer memory!");
UNREACHABLE();
}
auto& cmd{*std::construct_at<T>(reinterpret_cast<T*>(&command_list[size]))};
cmd.magic = CommandMagic;
cmd.enabled = true;
cmd.type = Id;
cmd.size = sizeof(T);
cmd.node_id = node_id;
return cmd;
}
template <typename T>
void CommandBuffer::GenerateEnd(T& cmd) {
cmd.estimated_process_time = time_estimator->Estimate(cmd);
estimated_process_time += cmd.estimated_process_time;
size += sizeof(T);
count++;
}
void CommandBuffer::GeneratePcmInt16Version1Command(const s32 node_id,
const MemoryPoolInfo& memory_pool_,
VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<PcmInt16DataSourceVersion1Command, CommandId::DataSourcePcmInt16Version1>(
node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
GenerateEnd<PcmInt16DataSourceVersion1Command>(cmd);
}
void CommandBuffer::GeneratePcmInt16Version2Command(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<PcmInt16DataSourceVersion2Command, CommandId::DataSourcePcmInt16Version2>(
node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
GenerateEnd<PcmInt16DataSourceVersion2Command>(cmd);
}
void CommandBuffer::GeneratePcmFloatVersion1Command(const s32 node_id,
const MemoryPoolInfo& memory_pool_,
VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<PcmFloatDataSourceVersion1Command, CommandId::DataSourcePcmFloatVersion1>(
node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
GenerateEnd<PcmFloatDataSourceVersion1Command>(cmd);
}
void CommandBuffer::GeneratePcmFloatVersion2Command(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<PcmFloatDataSourceVersion2Command, CommandId::DataSourcePcmFloatVersion2>(
node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
GenerateEnd<PcmFloatDataSourceVersion2Command>(cmd);
}
void CommandBuffer::GenerateAdpcmVersion1Command(const s32 node_id,
const MemoryPoolInfo& memory_pool_,
VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<AdpcmDataSourceVersion1Command, CommandId::DataSourceAdpcmVersion1>(node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool_.Translate(CpuAddr(&voice_state), sizeof(VoiceState));
cmd.data_address = voice_info.data_address.GetReference(true);
cmd.data_size = voice_info.data_address.GetSize();
GenerateEnd<AdpcmDataSourceVersion1Command>(cmd);
}
void CommandBuffer::GenerateAdpcmVersion2Command(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{
GenerateStart<AdpcmDataSourceVersion2Command, CommandId::DataSourceAdpcmVersion2>(node_id)};
cmd.src_quality = voice_info.src_quality;
cmd.output_index = buffer_count + channel;
cmd.flags = voice_info.flags & 3;
cmd.sample_rate = voice_info.sample_rate;
cmd.pitch = voice_info.pitch;
cmd.channel_index = channel;
cmd.channel_count = voice_info.channel_count;
for (u32 i = 0; i < MaxWaveBuffers; i++) {
voice_info.wavebuffers[i].Copy(cmd.wave_buffers[i]);
}
cmd.voice_state = memory_pool->Translate(CpuAddr(&voice_state), sizeof(VoiceState));
cmd.data_address = voice_info.data_address.GetReference(true);
cmd.data_size = voice_info.data_address.GetSize();
GenerateEnd<AdpcmDataSourceVersion2Command>(cmd);
}
void CommandBuffer::GenerateVolumeCommand(const s32 node_id, const s16 buffer_offset,
const s16 input_index, const f32 volume,
const u8 precision) {
auto& cmd{GenerateStart<VolumeCommand, CommandId::Volume>(node_id)};
cmd.precision = precision;
cmd.input_index = buffer_offset + input_index;
cmd.output_index = buffer_offset + input_index;
cmd.volume = volume;
GenerateEnd<VolumeCommand>(cmd);
}
void CommandBuffer::GenerateVolumeRampCommand(const s32 node_id, VoiceInfo& voice_info,
const s16 buffer_count, const u8 precision) {
auto& cmd{GenerateStart<VolumeRampCommand, CommandId::VolumeRamp>(node_id)};
cmd.input_index = buffer_count;
cmd.output_index = buffer_count;
cmd.prev_volume = voice_info.prev_volume;
cmd.volume = voice_info.volume;
cmd.precision = precision;
GenerateEnd<VolumeRampCommand>(cmd);
}
void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel,
const u32 biquad_index,
const bool use_float_processing) {
auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
cmd.input = buffer_count + channel;
cmd.output = buffer_count + channel;
cmd.biquad = voice_info.biquads[biquad_index];
cmd.state = memory_pool->Translate(CpuAddr(voice_state.biquad_states[biquad_index].data()),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
cmd.needs_init = !voice_info.biquad_initialized[biquad_index];
cmd.use_float_processing = use_float_processing;
GenerateEnd<BiquadFilterCommand>(cmd);
}
void CommandBuffer::GenerateBiquadFilterCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset, const s8 channel,
const bool needs_init,
const bool use_float_processing) {
auto& cmd{GenerateStart<BiquadFilterCommand, CommandId::BiquadFilter>(node_id)};
const auto& parameter{
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
const auto state{
reinterpret_cast<VoiceState::BiquadFilterState*>(effect_info.GetStateBuffer())};
cmd.input = buffer_offset + parameter.inputs[channel];
cmd.output = buffer_offset + parameter.outputs[channel];
cmd.biquad.b = parameter.b;
cmd.biquad.a = parameter.a;
cmd.state = memory_pool->Translate(CpuAddr(state),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
cmd.needs_init = needs_init;
cmd.use_float_processing = use_float_processing;
GenerateEnd<BiquadFilterCommand>(cmd);
}
void CommandBuffer::GenerateMixCommand(const s32 node_id, const s16 input_index,
const s16 output_index, const s16 buffer_offset,
const f32 volume, const u8 precision) {
auto& cmd{GenerateStart<MixCommand, CommandId::Mix>(node_id)};
cmd.input_index = input_index;
cmd.output_index = output_index;
cmd.volume = volume;
cmd.precision = precision;
GenerateEnd<MixCommand>(cmd);
}
void CommandBuffer::GenerateMixRampCommand(const s32 node_id,
[[maybe_unused]] const s16 buffer_count,
const s16 input_index, const s16 output_index,
const f32 volume, const f32 prev_volume,
const CpuAddr prev_samples, const u8 precision) {
if (volume == 0.0f && prev_volume == 0.0f) {
return;
}
auto& cmd{GenerateStart<MixRampCommand, CommandId::MixRamp>(node_id)};
cmd.input_index = input_index;
cmd.output_index = output_index;
cmd.prev_volume = prev_volume;
cmd.volume = volume;
cmd.previous_sample = prev_samples;
cmd.precision = precision;
GenerateEnd<MixRampCommand>(cmd);
}
void CommandBuffer::GenerateMixRampGroupedCommand(const s32 node_id, const s16 buffer_count,
const s16 input_index, s16 output_index,
std::span<const f32> volumes,
std::span<const f32> prev_volumes,
const CpuAddr prev_samples, const u8 precision) {
auto& cmd{GenerateStart<MixRampGroupedCommand, CommandId::MixRampGrouped>(node_id)};
cmd.buffer_count = buffer_count;
for (s32 i = 0; i < buffer_count; i++) {
cmd.inputs[i] = input_index;
cmd.outputs[i] = output_index++;
cmd.prev_volumes[i] = prev_volumes[i];
cmd.volumes[i] = volumes[i];
}
cmd.previous_samples = prev_samples;
cmd.precision = precision;
GenerateEnd<MixRampGroupedCommand>(cmd);
}
void CommandBuffer::GenerateDepopPrepareCommand(const s32 node_id, const VoiceState& voice_state,
std::span<const s32> buffer, const s16 buffer_count,
s16 buffer_offset, const bool was_playing) {
auto& cmd{GenerateStart<DepopPrepareCommand, CommandId::DepopPrepare>(node_id)};
cmd.enabled = was_playing;
for (u32 i = 0; i < MaxMixBuffers; i++) {
cmd.inputs[i] = buffer_offset++;
}
cmd.previous_samples = memory_pool->Translate(CpuAddr(voice_state.previous_samples.data()),
MaxMixBuffers * sizeof(s32));
cmd.buffer_count = buffer_count;
cmd.depop_buffer = memory_pool->Translate(CpuAddr(buffer.data()), buffer_count * sizeof(s32));
GenerateEnd<DepopPrepareCommand>(cmd);
}
void CommandBuffer::GenerateDepopForMixBuffersCommand(const s32 node_id, const MixInfo& mix_info,
std::span<const s32> depop_buffer) {
auto& cmd{GenerateStart<DepopForMixBuffersCommand, CommandId::DepopForMixBuffers>(node_id)};
cmd.input = mix_info.buffer_offset;
cmd.count = mix_info.buffer_count;
cmd.decay = mix_info.sample_rate == TargetSampleRate ? 0.96218872f : 0.94369507f;
cmd.depop_buffer =
memory_pool->Translate(CpuAddr(depop_buffer.data()), mix_info.buffer_count * sizeof(s32));
GenerateEnd<DepopForMixBuffersCommand>(cmd);
}
void CommandBuffer::GenerateDelayCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset) {
auto& cmd{GenerateStart<DelayCommand, CommandId::Delay>(node_id)};
const auto& parameter{
*reinterpret_cast<DelayInfo::ParameterVersion1*>(effect_info.GetParameter())};
const auto state{effect_info.GetStateBuffer()};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(DelayInfo::State))};
if (state_buffer) {
for (s16 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
if (!behavior->IsDelayChannelMappingChanged() && parameter.channel_count == 6) {
UseOldChannelMapping(cmd.inputs, cmd.outputs);
}
cmd.parameter = parameter;
cmd.effect_enabled = effect_info.IsEnabled();
cmd.state = state_buffer;
cmd.workbuffer = effect_info.GetWorkbuffer(-1);
}
}
GenerateEnd<DelayCommand>(cmd);
}
void CommandBuffer::GenerateUpsampleCommand(const s32 node_id, const s16 buffer_offset,
UpsamplerInfo& upsampler_info, const u32 input_count,
std::span<const s8> inputs, const s16 buffer_count,
const u32 sample_count_, const u32 sample_rate_) {
auto& cmd{GenerateStart<UpsampleCommand, CommandId::Upsample>(node_id)};
cmd.samples_buffer = memory_pool->Translate(upsampler_info.samples_pos,
upsampler_info.sample_count * sizeof(s32));
cmd.inputs = memory_pool->Translate(CpuAddr(upsampler_info.inputs.data()), MaxChannels);
cmd.buffer_count = buffer_count;
cmd.unk_20 = 0;
cmd.source_sample_count = sample_count_;
cmd.source_sample_rate = sample_rate_;
upsampler_info.input_count = input_count;
for (u32 i = 0; i < input_count; i++) {
upsampler_info.inputs[i] = buffer_offset + inputs[i];
}
cmd.upsampler_info = memory_pool->Translate(CpuAddr(&upsampler_info), sizeof(UpsamplerInfo));
GenerateEnd<UpsampleCommand>(cmd);
}
void CommandBuffer::GenerateDownMix6chTo2chCommand(const s32 node_id, std::span<const s8> inputs,
const s16 buffer_offset,
std::span<const f32> downmix_coeff) {
auto& cmd{GenerateStart<DownMix6chTo2chCommand, CommandId::DownMix6chTo2ch>(node_id)};
for (u32 i = 0; i < MaxChannels; i++) {
cmd.inputs[i] = buffer_offset + inputs[i];
cmd.outputs[i] = buffer_offset + inputs[i];
}
for (u32 i = 0; i < 4; i++) {
cmd.down_mix_coeff[i] = downmix_coeff[i];
}
GenerateEnd<DownMix6chTo2chCommand>(cmd);
}
void CommandBuffer::GenerateAuxCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 input_index, const s16 output_index,
const s16 buffer_offset, const u32 update_count,
const u32 count_max, const u32 write_offset) {
auto& cmd{GenerateStart<AuxCommand, CommandId::Aux>(node_id)};
if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
cmd.input = buffer_offset + input_index;
cmd.output = buffer_offset + output_index;
cmd.send_buffer_info = effect_info.GetSendBufferInfo();
cmd.send_buffer = effect_info.GetSendBuffer();
cmd.return_buffer_info = effect_info.GetReturnBufferInfo();
cmd.return_buffer = effect_info.GetReturnBuffer();
cmd.count_max = count_max;
cmd.write_offset = write_offset;
cmd.update_count = update_count;
cmd.effect_enabled = effect_info.IsEnabled();
}
GenerateEnd<AuxCommand>(cmd);
}
void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffer_offset,
SinkInfoBase& sink_info, const u32 session_id,
std::span<s32> samples_buffer) {
auto& cmd{GenerateStart<DeviceSinkCommand, CommandId::DeviceSink>(node_id)};
const auto& parameter{
*reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
cmd.session_id = session_id;
if (state.upsampler_info != nullptr) {
const auto size_{state.upsampler_info->sample_count * parameter.input_count};
const auto size_bytes{size_ * sizeof(s32)};
const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)};
cmd.sample_buffer = {reinterpret_cast<s32*>(addr),
parameter.input_count * state.upsampler_info->sample_count};
} else {
cmd.sample_buffer = samples_buffer;
}
cmd.input_count = parameter.input_count;
for (u32 i = 0; i < parameter.input_count; i++) {
cmd.inputs[i] = buffer_offset + parameter.inputs[i];
}
GenerateEnd<DeviceSinkCommand>(cmd);
}
void CommandBuffer::GenerateCircularBufferSinkCommand(const s32 node_id, SinkInfoBase& sink_info,
const s16 buffer_offset) {
auto& cmd{GenerateStart<CircularBufferSinkCommand, CommandId::CircularBufferSink>(node_id)};
const auto& parameter{*reinterpret_cast<CircularBufferSinkInfo::CircularBufferInParameter*>(
sink_info.GetParameter())};
auto state{
*reinterpret_cast<CircularBufferSinkInfo::CircularBufferState*>(sink_info.GetState())};
cmd.input_count = parameter.input_count;
for (u32 i = 0; i < parameter.input_count; i++) {
cmd.inputs[i] = buffer_offset + parameter.inputs[i];
}
cmd.address = state.address_info.GetReference(true);
cmd.size = parameter.size;
cmd.pos = state.current_pos;
GenerateEnd<CircularBufferSinkCommand>(cmd);
}
void CommandBuffer::GenerateReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset,
const bool long_size_pre_delay_supported) {
auto& cmd{GenerateStart<ReverbCommand, CommandId::Reverb>(node_id)};
const auto& parameter{
*reinterpret_cast<ReverbInfo::ParameterVersion2*>(effect_info.GetParameter())};
const auto state{effect_info.GetStateBuffer()};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(ReverbInfo::State))};
if (state_buffer) {
for (s16 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
if (!behavior->IsReverbChannelMappingChanged() && parameter.channel_count == 6) {
UseOldChannelMapping(cmd.inputs, cmd.outputs);
}
cmd.parameter = parameter;
cmd.effect_enabled = effect_info.IsEnabled();
cmd.state = state_buffer;
cmd.workbuffer = effect_info.GetWorkbuffer(-1);
cmd.long_size_pre_delay_supported = long_size_pre_delay_supported;
}
}
GenerateEnd<ReverbCommand>(cmd);
}
void CommandBuffer::GenerateI3dl2ReverbCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset) {
auto& cmd{GenerateStart<I3dl2ReverbCommand, CommandId::I3dl2Reverb>(node_id)};
const auto& parameter{
*reinterpret_cast<I3dl2ReverbInfo::ParameterVersion1*>(effect_info.GetParameter())};
const auto state{effect_info.GetStateBuffer()};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{
memory_pool->Translate(CpuAddr(state), sizeof(I3dl2ReverbInfo::State))};
if (state_buffer) {
for (s16 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
if (!behavior->IsI3dl2ReverbChannelMappingChanged() && parameter.channel_count == 6) {
UseOldChannelMapping(cmd.inputs, cmd.outputs);
}
cmd.parameter = parameter;
cmd.effect_enabled = effect_info.IsEnabled();
cmd.state = state_buffer;
cmd.workbuffer = effect_info.GetWorkbuffer(-1);
}
}
GenerateEnd<I3dl2ReverbCommand>(cmd);
}
void CommandBuffer::GeneratePerformanceCommand(const s32 node_id, const PerformanceState state,
const PerformanceEntryAddresses& entry_addresses) {
auto& cmd{GenerateStart<PerformanceCommand, CommandId::Performance>(node_id)};
cmd.state = state;
cmd.entry_address = entry_addresses;
GenerateEnd<PerformanceCommand>(cmd);
}
void CommandBuffer::GenerateClearMixCommand(const s32 node_id) {
auto& cmd{GenerateStart<ClearMixBufferCommand, CommandId::ClearMixBuffer>(node_id)};
GenerateEnd<ClearMixBufferCommand>(cmd);
}
void CommandBuffer::GenerateCopyMixBufferCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 buffer_offset, const s8 channel) {
auto& cmd{GenerateStart<CopyMixBufferCommand, CommandId::CopyMixBuffer>(node_id)};
const auto& parameter{
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
cmd.input_index = buffer_offset + parameter.inputs[channel];
cmd.output_index = buffer_offset + parameter.outputs[channel];
GenerateEnd<CopyMixBufferCommand>(cmd);
}
void CommandBuffer::GenerateLightLimiterCommand(
const s32 node_id, const s16 buffer_offset,
const LightLimiterInfo::ParameterVersion1& parameter, const LightLimiterInfo::State& state,
const bool enabled, const CpuAddr workbuffer) {
auto& cmd{GenerateStart<LightLimiterVersion1Command, CommandId::LightLimiterVersion1>(node_id)};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{
memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
if (state_buffer) {
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
std::memcpy(&cmd.parameter, &parameter, sizeof(LightLimiterInfo::ParameterVersion1));
cmd.effect_enabled = enabled;
cmd.state = state_buffer;
cmd.workbuffer = workbuffer;
}
}
GenerateEnd<LightLimiterVersion1Command>(cmd);
}
void CommandBuffer::GenerateLightLimiterCommand(
const s32 node_id, const s16 buffer_offset,
const LightLimiterInfo::ParameterVersion2& parameter,
const LightLimiterInfo::StatisticsInternal& statistics, const LightLimiterInfo::State& state,
const bool enabled, const CpuAddr workbuffer) {
auto& cmd{GenerateStart<LightLimiterVersion2Command, CommandId::LightLimiterVersion2>(node_id)};
if (IsChannelCountValid(parameter.channel_count)) {
const auto state_buffer{
memory_pool->Translate(CpuAddr(&state), sizeof(LightLimiterInfo::State))};
if (state_buffer) {
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
cmd.parameter = parameter;
cmd.effect_enabled = enabled;
cmd.state = state_buffer;
if (cmd.parameter.statistics_enabled) {
cmd.result_state = memory_pool->Translate(
CpuAddr(&statistics), sizeof(LightLimiterInfo::StatisticsInternal));
} else {
cmd.result_state = 0;
}
cmd.workbuffer = workbuffer;
}
}
GenerateEnd<LightLimiterVersion2Command>(cmd);
}
void CommandBuffer::GenerateMultitapBiquadFilterCommand(const s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel) {
auto& cmd{GenerateStart<MultiTapBiquadFilterCommand, CommandId::MultiTapBiquadFilter>(node_id)};
cmd.input = buffer_count + channel;
cmd.output = buffer_count + channel;
cmd.biquads = voice_info.biquads;
cmd.states[0] =
memory_pool->Translate(CpuAddr(voice_state.biquad_states[0].data()),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
cmd.states[1] =
memory_pool->Translate(CpuAddr(voice_state.biquad_states[1].data()),
MaxBiquadFilters * sizeof(VoiceState::BiquadFilterState));
cmd.needs_init[0] = !voice_info.biquad_initialized[0];
cmd.needs_init[1] = !voice_info.biquad_initialized[1];
cmd.filter_tap_count = MaxBiquadFilters;
GenerateEnd<MultiTapBiquadFilterCommand>(cmd);
}
void CommandBuffer::GenerateCaptureCommand(const s32 node_id, EffectInfoBase& effect_info,
const s16 input_index, const s16 output_index,
const s16 buffer_offset, const u32 update_count,
const u32 count_max, const u32 write_offset) {
auto& cmd{GenerateStart<CaptureCommand, CommandId::Capture>(node_id)};
if (effect_info.GetSendBuffer()) {
cmd.input = buffer_offset + input_index;
cmd.output = buffer_offset + output_index;
cmd.send_buffer_info = effect_info.GetSendBufferInfo();
cmd.send_buffer = effect_info.GetSendBuffer();
cmd.count_max = count_max;
cmd.write_offset = write_offset;
cmd.update_count = update_count;
cmd.effect_enabled = effect_info.IsEnabled();
}
GenerateEnd<CaptureCommand>(cmd);
}
void CommandBuffer::GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id) {
auto& cmd{GenerateStart<CompressorCommand, CommandId::Compressor>(node_id)};
auto& parameter{
*reinterpret_cast<CompressorInfo::ParameterVersion2*>(effect_info.GetParameter())};
auto state{reinterpret_cast<CompressorInfo::State*>(effect_info.GetStateBuffer())};
if (IsChannelCountValid(parameter.channel_count)) {
auto state_buffer{memory_pool->Translate(CpuAddr(state), sizeof(CompressorInfo::State))};
if (state_buffer) {
for (u16 channel = 0; channel < parameter.channel_count; channel++) {
cmd.inputs[channel] = buffer_offset + parameter.inputs[channel];
cmd.outputs[channel] = buffer_offset + parameter.outputs[channel];
}
cmd.parameter = parameter;
cmd.workbuffer = state_buffer;
cmd.enabled = effect_info.IsEnabled();
}
}
GenerateEnd<CompressorCommand>(cmd);
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,466 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/renderer/command/commands.h"
#include "audio_core/renderer/effect/light_limiter.h"
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
struct UpsamplerInfo;
struct VoiceState;
class EffectInfoBase;
class ICommandProcessingTimeEstimator;
class MixInfo;
class MemoryPoolInfo;
class SinkInfoBase;
class VoiceInfo;
/**
* Utility functions to generate and add commands into the current command list.
*/
class CommandBuffer {
public:
/**
* Generate a PCM s16 version 1 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GeneratePcmInt16Version1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
VoiceInfo& voice_info, const VoiceState& voice_state,
s16 buffer_count, s8 channel);
/**
* Generate a PCM s16 version 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GeneratePcmInt16Version2Command(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count,
s8 channel);
/**
* Generate a PCM f32 version 1 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GeneratePcmFloatVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
VoiceInfo& voice_info, const VoiceState& voice_state,
s16 buffer_count, s8 channel);
/**
* Generate a PCM f32 version 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GeneratePcmFloatVersion2Command(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count,
s8 channel);
/**
* Generate an ADPCM version 1 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param memory_pool - Memory pool for translating buffer addresses to the DSP.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GenerateAdpcmVersion1Command(s32 node_id, const MemoryPoolInfo& memory_pool,
VoiceInfo& voice_info, const VoiceState& voice_state,
s16 buffer_count, s8 channel);
/**
* Generate an ADPCM version 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command is generated from.
* @param voice_state - The voice state the DSP will use for this command.
* @param buffer_count - Number of mix buffers in use,
* data will be read into this index + channel.
* @param channel - Channel index for this command.
*/
void GenerateAdpcmVersion2Command(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count, s8 channel);
/**
* Generate a volume command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer index to generate this command at.
* @param input_index - Channel index and mix buffer offset for this command.
* @param volume - Mix volume added to the input samples.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateVolumeCommand(s32 node_id, s16 buffer_offset, s16 input_index, f32 volume,
u8 precision);
/**
* Generate a volume ramp command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command takes its volumes from.
* @param buffer_count - Number of active mix buffers, command will generate at this index.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateVolumeRampCommand(s32 node_id, VoiceInfo& voice_info, s16 buffer_count,
u8 precision);
/**
* Generate a biquad filter command from a voice, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command takes biquad parameters from.
* @param voice_state - Used by the AudioRenderer to track previous samples.
* @param buffer_count - Number of active mix buffers,
* command will generate at this index + channel.
* @param channel - Channel index for this filter to work on.
* @param biquad_index - Which biquad filter to use for this command (0-1).
* @param use_float_processing - Should int or float processing be used?
*/
void GenerateBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count, s8 channel,
u32 biquad_index, bool use_float_processing);
/**
* Generate a biquad filter effect command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - The effect info this command takes biquad parameters from.
* @param buffer_offset - Mix buffer offset this command will use,
* command will generate at this index + channel.
* @param channel - Channel index for this filter to work on.
* @param needs_init - True if the biquad state needs initialisation.
* @param use_float_processing - Should int or float processing be used?
*/
void GenerateBiquadFilterCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
s8 channel, bool needs_init, bool use_float_processing);
/**
* Generate a mix command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param input_index - Input mix buffer index for this command.
* Added to the buffer offset.
* @param output_index - Output mix buffer index for this command.
* Added to the buffer offset.
* @param buffer_offset - Mix buffer offset this command will use.
* @param volume - Volume to be applied to the input.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateMixCommand(s32 node_id, s16 input_index, s16 output_index, s16 buffer_offset,
f32 volume, u8 precision);
/**
* Generate a mix ramp command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_count - Number of active mix buffers.
* @param input_index - Input mix buffer index for this command.
* Added to buffer_count.
* @param output_index - Output mix buffer index for this command.
* Added to buffer_count.
* @param volume - Current mix volume used for calculating the ramp.
* @param prev_volume - Previous mix volume, used for calculating the ramp,
* also applied to the input.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index,
f32 volume, f32 prev_volume, CpuAddr prev_samples, u8 precision);
/**
* Generate a mix ramp grouped command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_count - Number of active mix buffers.
* @param input_index - Input mix buffer index for this command.
* Added to buffer_count.
* @param output_index - Output mix buffer index for this command.
* Added to buffer_count.
* @param volumes - Current mix volumes used for calculating the ramp.
* @param prev_volumes - Previous mix volumes, used for calculating the ramp,
* also applied to the input.
* @param precision - Number of decimal bits for fixed point operations.
*/
void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index,
s16 output_index, std::span<const f32> volumes,
std::span<const f32> prev_volumes, CpuAddr prev_samples,
u8 precision);
/**
* Generate a depop prepare command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_state - State to track the previous depop samples for each mix buffer.
* @param buffer - State to track the current depop samples for each mix buffer.
* @param buffer_count - Number of active mix buffers.
* @param buffer_offset - Base mix buffer index to generate the channel depops at.
* @param was_playing - Command only needs to work if the voice was previously playing.
*/
void GenerateDepopPrepareCommand(s32 node_id, const VoiceState& voice_state,
std::span<const s32> buffer, s16 buffer_count,
s16 buffer_offset, bool was_playing);
/**
* Generate a depop command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param mix_info - Mix info to get the buffer count and base offsets from.
* @param depop_buffer - Buffer of current depop sample values to be added to the input
* channels.
*/
void GenerateDepopForMixBuffersCommand(s32 node_id, const MixInfo& mix_info,
std::span<const s32> depop_buffer);
/**
* Generate a delay command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - Delay effect info to generate this command from.
* @param buffer_offset - Base mix buffer offset to apply the apply the delay.
*/
void GenerateDelayCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
/**
* Generate an upsample command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer offset to upsample.
* @param upsampler_info - Upsampler info to control the upsampling.
* @param input_count - Number of input channels to upsample.
* @param inputs - Input mix buffer indexes.
* @param buffer_count - Number of active mix buffers.
* @param sample_count - Source sample count of the input.
* @param sample_rate - Source sample rate of the input.
*/
void GenerateUpsampleCommand(s32 node_id, s16 buffer_offset, UpsamplerInfo& upsampler_info,
u32 input_count, std::span<const s8> inputs, s16 buffer_count,
u32 sample_count, u32 sample_rate);
/**
* Generate a downmix 6 -> 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param inputs - Input mix buffer indexes.
* @param buffer_offset - Base mix buffer offset of the channels to downmix.
* @param downmix_coeff - Downmixing coefficients.
*/
void GenerateDownMix6chTo2chCommand(s32 node_id, std::span<const s8> inputs, s16 buffer_offset,
std::span<const f32> downmix_coeff);
/**
* Generate an aux buffer command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - Aux effect info to generate this command from.
* @param input_index - Input mix buffer index for this command.
* Added to buffer_offset.
* @param output_index - Output mix buffer index for this command.
* Added to buffer_offset.
* @param buffer_offset - Base mix buffer offset to use.
* @param update_count - Number of samples to write back to the game as updated, can be 0.
* @param count_max - Maximum number of samples to read or write.
* @param write_offset - Current read or write offset within the buffer.
*/
void GenerateAuxCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
s16 output_index, s16 buffer_offset, u32 update_count, u32 count_max,
u32 write_offset);
/**
* Generate a device sink command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer offset to use.
* @param sink_info - The sink_info to generate this command from.
* @session_id - System session id this command is generated from.
* @samples_buffer - The buffer to be sent to the sink if upsampling is not used.
*/
void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info,
u32 session_id, std::span<s32> samples_buffer);
/**
* Generate a circular buffer sink command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param sink_info - The sink_info to generate this command from.
* @param buffer_offset - Base mix buffer offset to use.
*/
void GenerateCircularBufferSinkCommand(s32 node_id, SinkInfoBase& sink_info, s16 buffer_offset);
/**
* Generate a reverb command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - Reverb effect info to generate this command from.
* @param buffer_offset - Base mix buffer offset to use.
* @param long_size_pre_delay_supported - Should a longer pre-delay time be used before reverb
* begins?
*/
void GenerateReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
bool long_size_pre_delay_supported);
/**
* Generate an I3DL2 reverb command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - I3DL2Reverb effect info to generate this command from.
* @param buffer_offset - Base mix buffer offset to use.
*/
void GenerateI3dl2ReverbCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset);
/**
* Generate a performance command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param state - State of the performance.
* @param entry_addresses - The addresses to be filled in by the AudioRenderer.
*/
void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
const PerformanceEntryAddresses& entry_addresses);
/**
* Generate a clear mix command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
*/
void GenerateClearMixCommand(s32 node_id);
/**
* Generate a copy mix command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - BiquadFilter effect info to generate this command from.
* @param buffer_offset - Base mix buffer offset to use.
* @param channel - Index to the effect's parameters input indexes for this command.
*/
void GenerateCopyMixBufferCommand(s32 node_id, EffectInfoBase& effect_info, s16 buffer_offset,
s8 channel);
/**
* Generate a light limiter version 1 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer offset to use.
* @param parameter - Effect parameter to generate from.
* @param state - State used by the AudioRenderer between commands.
* @param enabled - Is this command enabled?
* @param workbuffer - Game-supplied memory for the state.
*/
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
const LightLimiterInfo::ParameterVersion1& parameter,
const LightLimiterInfo::State& state, bool enabled,
CpuAddr workbuffer);
/**
* Generate a light limiter version 2 command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param buffer_offset - Base mix buffer offset to use.
* @param parameter - Effect parameter to generate from.
* @param statistics - Statistics reported by the AudioRenderer on the limiter's state.
* @param state - State used by the AudioRenderer between commands.
* @param enabled - Is this command enabled?
* @param workbuffer - Game-supplied memory for the state.
*/
void GenerateLightLimiterCommand(s32 node_id, s16 buffer_offset,
const LightLimiterInfo::ParameterVersion2& parameter,
const LightLimiterInfo::StatisticsInternal& statistics,
const LightLimiterInfo::State& state, bool enabled,
CpuAddr workbuffer);
/**
* Generate a multitap biquad filter command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param voice_info - The voice info this command takes biquad parameters from.
* @param voice_state - Used by the AudioRenderer to track previous samples.
* @param buffer_count - Number of active mix buffers,
* command will generate at this index + channel.
* @param channel - Channel index for this filter to work on.
*/
void GenerateMultitapBiquadFilterCommand(s32 node_id, VoiceInfo& voice_info,
const VoiceState& voice_state, s16 buffer_count,
s8 channel);
/**
* Generate a capture command, adding it to the command list.
*
* @param node_id - Node id of the voice this command is generated for.
* @param effect_info - Capture effect info to generate this command from.
* @param input_index - Input mix buffer index for this command.
* Added to buffer_offset.
* @param output_index - Output mix buffer index for this command (unused).
* Added to buffer_offset.
* @param buffer_offset - Base mix buffer offset to use.
* @param update_count - Number of samples to write back to the game as updated, can be 0.
* @param count_max - Maximum number of samples to read or write.
* @param write_offset - Current read or write offset within the buffer.
*/
void GenerateCaptureCommand(s32 node_id, EffectInfoBase& effect_info, s16 input_index,
s16 output_index, s16 buffer_offset, u32 update_count,
u32 count_max, u32 write_offset);
/**
* Generate a compressor command, adding it to the command list.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info - Capture effect info to generate this command from.
* @param node_id - Node id of the voice this command is generated for.
*/
void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
/// Command list buffer generated commands will be added to
std::span<u8> command_list{};
/// Input sample count, unused
u32 sample_count{};
/// Input sample rate, unused
u32 sample_rate{};
/// Current size of the command buffer
u64 size{};
/// Current number of commands added
u32 count{};
/// Current estimated processing time for all commands
u32 estimated_process_time{};
/// Used for mapping buffers for the AudioRenderer
MemoryPoolInfo* memory_pool{};
/// Used for estimating command process times
ICommandProcessingTimeEstimator* time_estimator{};
/// Used to check which rendering features are currently enabled
BehaviorInfo* behavior{};
private:
template <typename T, CommandId Id>
T& GenerateStart(const s32 node_id);
template <typename T>
void GenerateEnd(T& cmd);
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,796 @@
// 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/renderer/behavior/behavior_info.h"
#include "audio_core/renderer/command/command_buffer.h"
#include "audio_core/renderer/command/command_generator.h"
#include "audio_core/renderer/command/command_list_header.h"
#include "audio_core/renderer/effect/aux_.h"
#include "audio_core/renderer/effect/biquad_filter.h"
#include "audio_core/renderer/effect/buffer_mixer.h"
#include "audio_core/renderer/effect/capture.h"
#include "audio_core/renderer/effect/effect_context.h"
#include "audio_core/renderer/effect/light_limiter.h"
#include "audio_core/renderer/mix/mix_context.h"
#include "audio_core/renderer/performance/detail_aspect.h"
#include "audio_core/renderer/performance/entry_aspect.h"
#include "audio_core/renderer/sink/device_sink_info.h"
#include "audio_core/renderer/sink/sink_context.h"
#include "audio_core/renderer/splitter/splitter_context.h"
#include "audio_core/renderer/voice/voice_context.h"
#include "common/alignment.h"
namespace AudioCore::AudioRenderer {
CommandGenerator::CommandGenerator(CommandBuffer& command_buffer_,
const CommandListHeader& command_list_header_,
const AudioRendererSystemContext& render_context_,
VoiceContext& voice_context_, MixContext& mix_context_,
EffectContext& effect_context_, SinkContext& sink_context_,
SplitterContext& splitter_context_,
PerformanceManager* performance_manager_)
: command_buffer{command_buffer_}, command_header{command_list_header_},
render_context{render_context_}, voice_context{voice_context_}, mix_context{mix_context_},
effect_context{effect_context_}, sink_context{sink_context_},
splitter_context{splitter_context_}, performance_manager{performance_manager_} {
command_buffer.GenerateClearMixCommand(InvalidNodeId);
}
void CommandGenerator::GenerateDataSourceCommand(VoiceInfo& voice_info,
const VoiceState& voice_state, const s8 channel) {
if (voice_info.mix_id == UnusedMixId) {
if (voice_info.splitter_id != UnusedSplitterId) {
auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, 0)};
u32 dest_id{0};
while (destination != nullptr) {
if (destination->IsConfigured()) {
auto mix_id{destination->GetMixId()};
if (mix_id < mix_context.GetCount()) {
auto mix_info{mix_context.GetInfo(mix_id)};
command_buffer.GenerateDepopPrepareCommand(
voice_info.node_id, voice_state, render_context.depop_buffer,
mix_info->buffer_count, mix_info->buffer_offset,
voice_info.was_playing);
}
}
dest_id++;
destination = splitter_context.GetDesintationData(voice_info.splitter_id, dest_id);
}
}
} else {
auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
command_buffer.GenerateDepopPrepareCommand(
voice_info.node_id, voice_state, render_context.depop_buffer, mix_info->buffer_count,
mix_info->buffer_offset, voice_info.was_playing);
}
if (voice_info.was_playing) {
return;
}
if (render_context.behavior->IsWaveBufferVer2Supported()) {
switch (voice_info.sample_format) {
case SampleFormat::PcmInt16:
command_buffer.GeneratePcmInt16Version2Command(
voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
channel);
break;
case SampleFormat::PcmFloat:
command_buffer.GeneratePcmFloatVersion2Command(
voice_info.node_id, voice_info, voice_state, render_context.mix_buffer_count,
channel);
break;
case SampleFormat::Adpcm:
command_buffer.GenerateAdpcmVersion2Command(voice_info.node_id, voice_info, voice_state,
render_context.mix_buffer_count, channel);
break;
default:
LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
static_cast<u32>(voice_info.sample_format));
break;
}
} else {
switch (voice_info.sample_format) {
case SampleFormat::PcmInt16:
command_buffer.GeneratePcmInt16Version1Command(
voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
render_context.mix_buffer_count, channel);
break;
case SampleFormat::PcmFloat:
command_buffer.GeneratePcmFloatVersion1Command(
voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
render_context.mix_buffer_count, channel);
break;
case SampleFormat::Adpcm:
command_buffer.GenerateAdpcmVersion1Command(
voice_info.node_id, *command_buffer.memory_pool, voice_info, voice_state,
render_context.mix_buffer_count, channel);
break;
default:
LOG_ERROR(Service_Audio, "Invalid SampleFormat {}",
static_cast<u32>(voice_info.sample_format));
break;
}
}
}
void CommandGenerator::GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
std::span<const f32> prev_mix_volumes,
const VoiceState& voice_state, s16 output_index,
const s16 buffer_count, const s16 input_index,
const s32 node_id) {
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
if (buffer_count > 8) {
const auto prev_samples{render_context.memory_pool_info->Translate(
CpuAddr(voice_state.previous_samples.data()), buffer_count * sizeof(s32))};
command_buffer.GenerateMixRampGroupedCommand(node_id, buffer_count, input_index,
output_index, mix_volumes, prev_mix_volumes,
prev_samples, precision);
} else {
for (s16 i = 0; i < buffer_count; i++, output_index++) {
const auto prev_samples{render_context.memory_pool_info->Translate(
CpuAddr(&voice_state.previous_samples[i]), sizeof(s32))};
command_buffer.GenerateMixRampCommand(node_id, buffer_count, input_index, output_index,
mix_volumes[i], prev_mix_volumes[i], prev_samples,
precision);
}
}
}
void CommandGenerator::GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info,
const VoiceState& voice_state,
const s16 buffer_count, const s8 channel,
const s32 node_id) {
const bool both_biquads_enabled{voice_info.biquads[0].enabled && voice_info.biquads[1].enabled};
const auto use_float_processing{render_context.behavior->UseBiquadFilterFloatProcessing()};
if (both_biquads_enabled && render_context.behavior->UseMultiTapBiquadFilterProcessing() &&
use_float_processing) {
command_buffer.GenerateMultitapBiquadFilterCommand(node_id, voice_info, voice_state,
buffer_count, channel);
} else {
for (u32 i = 0; i < MaxBiquadFilters; i++) {
if (voice_info.biquads[i].enabled) {
command_buffer.GenerateBiquadFilterCommand(node_id, voice_info, voice_state,
buffer_count, channel, i,
use_float_processing);
}
}
}
}
void CommandGenerator::GenerateVoiceCommand(VoiceInfo& voice_info) {
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
for (s8 channel = 0; channel < voice_info.channel_count; channel++) {
const auto resource_id{voice_info.channel_resource_ids[channel]};
auto& voice_state{voice_context.GetDspSharedState(resource_id)};
auto& channel_resource{voice_context.GetChannelResource(resource_id)};
PerformanceDetailType detail_type{PerformanceDetailType::Invalid};
switch (voice_info.sample_format) {
case SampleFormat::PcmInt16:
detail_type = PerformanceDetailType::Unk1;
break;
case SampleFormat::PcmFloat:
detail_type = PerformanceDetailType::Unk10;
break;
default:
detail_type = PerformanceDetailType::Unk2;
break;
}
DetailAspect data_source_detail(*this, PerformanceEntryType::Voice, voice_info.node_id,
detail_type);
GenerateDataSourceCommand(voice_info, voice_state, channel);
if (data_source_detail.initialized) {
command_buffer.GeneratePerformanceCommand(data_source_detail.node_id,
PerformanceState::Stop,
data_source_detail.performance_entry_address);
}
if (voice_info.was_playing) {
voice_info.prev_volume = 0.0f;
continue;
}
if (!voice_info.HasAnyConnection()) {
continue;
}
DetailAspect biquad_detail_aspect(*this, PerformanceEntryType::Voice, voice_info.node_id,
PerformanceDetailType::Unk4);
GenerateBiquadFilterCommandForVoice(
voice_info, voice_state, render_context.mix_buffer_count, channel, voice_info.node_id);
if (biquad_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
biquad_detail_aspect.node_id, PerformanceState::Stop,
biquad_detail_aspect.performance_entry_address);
}
DetailAspect volume_ramp_detail_aspect(*this, PerformanceEntryType::Voice,
voice_info.node_id, PerformanceDetailType::Unk3);
command_buffer.GenerateVolumeRampCommand(
voice_info.node_id, voice_info, render_context.mix_buffer_count + channel, precision);
if (volume_ramp_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
volume_ramp_detail_aspect.node_id, PerformanceState::Stop,
volume_ramp_detail_aspect.performance_entry_address);
}
voice_info.prev_volume = voice_info.volume;
if (voice_info.mix_id == UnusedMixId) {
if (voice_info.splitter_id != UnusedSplitterId) {
auto i{channel};
auto destination{splitter_context.GetDesintationData(voice_info.splitter_id, i)};
while (destination != nullptr) {
if (destination->IsConfigured()) {
const auto mix_id{destination->GetMixId()};
if (mix_id < mix_context.GetCount() &&
static_cast<s32>(mix_id) != UnusedSplitterId) {
auto mix_info{mix_context.GetInfo(mix_id)};
GenerateVoiceMixCommand(
destination->GetMixVolume(), destination->GetMixVolumePrev(),
voice_state, mix_info->buffer_offset, mix_info->buffer_count,
render_context.mix_buffer_count + channel, voice_info.node_id);
destination->MarkAsNeedToUpdateInternalState();
}
}
i += voice_info.channel_count;
destination = splitter_context.GetDesintationData(voice_info.splitter_id, i);
}
}
} else {
DetailAspect volume_mix_detail_aspect(*this, PerformanceEntryType::Voice,
voice_info.node_id, PerformanceDetailType::Unk3);
auto mix_info{mix_context.GetInfo(voice_info.mix_id)};
GenerateVoiceMixCommand(channel_resource.mix_volumes, channel_resource.prev_mix_volumes,
voice_state, mix_info->buffer_offset, mix_info->buffer_count,
render_context.mix_buffer_count + channel, voice_info.node_id);
if (volume_mix_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
volume_mix_detail_aspect.node_id, PerformanceState::Stop,
volume_mix_detail_aspect.performance_entry_address);
}
channel_resource.prev_mix_volumes = channel_resource.mix_volumes;
}
voice_info.biquad_initialized[0] = voice_info.biquads[0].enabled;
voice_info.biquad_initialized[1] = voice_info.biquads[1].enabled;
}
}
void CommandGenerator::GenerateVoiceCommands() {
const auto voice_count{voice_context.GetCount()};
for (u32 i = 0; i < voice_count; i++) {
auto sorted_info{voice_context.GetSortedInfo(i)};
if (sorted_info->ShouldSkip() || !sorted_info->UpdateForCommandGeneration(voice_context)) {
continue;
}
EntryAspect voice_entry_aspect(*this, PerformanceEntryType::Voice, sorted_info->node_id);
GenerateVoiceCommand(*sorted_info);
if (voice_entry_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(voice_entry_aspect.node_id,
PerformanceState::Stop,
voice_entry_aspect.performance_entry_address);
}
}
splitter_context.UpdateInternalState();
}
void CommandGenerator::GenerateBufferMixerCommand(const s16 buffer_offset,
EffectInfoBase& effect_info, const s32 node_id) {
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
if (effect_info.IsEnabled()) {
const auto& parameter{
*reinterpret_cast<BufferMixerInfo::ParameterVersion1*>(effect_info.GetParameter())};
for (u32 i = 0; i < parameter.mix_count; i++) {
if (parameter.volumes[i] != 0.0f) {
command_buffer.GenerateMixCommand(node_id, buffer_offset + parameter.inputs[i],
buffer_offset + parameter.outputs[i],
buffer_offset, parameter.volumes[i], precision);
}
}
}
}
void CommandGenerator::GenerateDelayCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id) {
command_buffer.GenerateDelayCommand(node_id, effect_info, buffer_offset);
}
void CommandGenerator::GenerateReverbCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id,
const bool long_size_pre_delay_supported) {
command_buffer.GenerateReverbCommand(node_id, effect_info, buffer_offset,
long_size_pre_delay_supported);
}
void CommandGenerator::GenerateI3dl2ReverbEffectCommand(const s16 buffer_offset,
EffectInfoBase& effect_info,
const s32 node_id) {
command_buffer.GenerateI3dl2ReverbCommand(node_id, effect_info, buffer_offset);
}
void CommandGenerator::GenerateAuxCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id) {
if (effect_info.IsEnabled()) {
effect_info.GetWorkbuffer(0);
effect_info.GetWorkbuffer(1);
}
if (effect_info.GetSendBuffer() != 0 && effect_info.GetReturnBuffer() != 0) {
const auto& parameter{
*reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
auto channel_index{parameter.mix_buffer_count - 1};
u32 write_offset{0};
for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
auto new_update_count{command_header.sample_count + write_offset};
const auto update_count{channel_index > 0 ? 0 : new_update_count};
command_buffer.GenerateAuxCommand(node_id, effect_info, parameter.inputs[i],
parameter.outputs[i], buffer_offset, update_count,
parameter.count_max, write_offset);
write_offset = new_update_count;
}
}
}
void CommandGenerator::GenerateBiquadFilterEffectCommand(const s16 buffer_offset,
EffectInfoBase& effect_info,
const s32 node_id) {
const auto& parameter{
*reinterpret_cast<BiquadFilterInfo::ParameterVersion1*>(effect_info.GetParameter())};
if (effect_info.IsEnabled()) {
bool needs_init{false};
switch (parameter.state) {
case EffectInfoBase::ParameterState::Initialized:
needs_init = true;
break;
case EffectInfoBase::ParameterState::Updating:
case EffectInfoBase::ParameterState::Updated:
if (render_context.behavior->IsBiquadFilterEffectStateClearBugFixed()) {
needs_init = false;
} else {
needs_init = parameter.state == EffectInfoBase::ParameterState::Updating;
}
break;
default:
LOG_ERROR(Service_Audio, "Invalid biquad parameter state {}",
static_cast<u32>(parameter.state));
break;
}
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
command_buffer.GenerateBiquadFilterCommand(
node_id, effect_info, buffer_offset, channel, needs_init,
render_context.behavior->UseBiquadFilterFloatProcessing());
}
} else {
for (s8 channel = 0; channel < parameter.channel_count; channel++) {
command_buffer.GenerateCopyMixBufferCommand(node_id, effect_info, buffer_offset,
channel);
}
}
}
void CommandGenerator::GenerateLightLimiterEffectCommand(const s16 buffer_offset,
EffectInfoBase& effect_info,
const s32 node_id,
const u32 effect_index) {
const auto& state{*reinterpret_cast<LightLimiterInfo::State*>(effect_info.GetStateBuffer())};
if (render_context.behavior->IsEffectInfoVersion2Supported()) {
const auto& parameter{
*reinterpret_cast<LightLimiterInfo::ParameterVersion2*>(effect_info.GetParameter())};
const auto& result_state{*reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(
&effect_context.GetDspSharedResultState(effect_index))};
command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, result_state,
state, effect_info.IsEnabled(),
effect_info.GetWorkbuffer(-1));
} else {
const auto& parameter{
*reinterpret_cast<LightLimiterInfo::ParameterVersion1*>(effect_info.GetParameter())};
command_buffer.GenerateLightLimiterCommand(node_id, buffer_offset, parameter, state,
effect_info.IsEnabled(),
effect_info.GetWorkbuffer(-1));
}
}
void CommandGenerator::GenerateCaptureCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id) {
if (effect_info.IsEnabled()) {
effect_info.GetWorkbuffer(0);
}
if (effect_info.GetSendBuffer()) {
const auto& parameter{
*reinterpret_cast<AuxInfo::ParameterVersion1*>(effect_info.GetParameter())};
auto channel_index{parameter.mix_buffer_count - 1};
u32 write_offset{0};
for (u32 i = 0; i < parameter.mix_buffer_count; i++, channel_index--) {
auto new_update_count{command_header.sample_count + write_offset};
const auto update_count{channel_index > 0 ? 0 : new_update_count};
command_buffer.GenerateCaptureCommand(node_id, effect_info, parameter.inputs[i],
parameter.outputs[i], buffer_offset, update_count,
parameter.count_max, write_offset);
write_offset = new_update_count;
}
}
}
void CommandGenerator::GenerateCompressorCommand(const s16 buffer_offset,
EffectInfoBase& effect_info, const s32 node_id) {
command_buffer.GenerateCompressorCommand(buffer_offset, effect_info, node_id);
}
void CommandGenerator::GenerateEffectCommand(MixInfo& mix_info) {
const auto effect_count{effect_context.GetCount()};
for (u32 i = 0; i < effect_count; i++) {
const auto effect_index{mix_info.effect_order_buffer[i]};
if (effect_index == -1) {
break;
}
auto& effect_info = effect_context.GetInfo(effect_index);
if (effect_info.ShouldSkip()) {
continue;
}
const auto entry_type{mix_info.mix_id == FinalMixId ? PerformanceEntryType::FinalMix
: PerformanceEntryType::SubMix};
switch (effect_info.GetType()) {
case EffectInfoBase::Type::Mix: {
DetailAspect mix_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk5);
GenerateBufferMixerCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (mix_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
mix_detail_aspect.node_id, PerformanceState::Stop,
mix_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Aux: {
DetailAspect aux_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk7);
GenerateAuxCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (aux_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
aux_detail_aspect.node_id, PerformanceState::Stop,
aux_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Delay: {
DetailAspect delay_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk6);
GenerateDelayCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (delay_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
delay_detail_aspect.node_id, PerformanceState::Stop,
delay_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Reverb: {
DetailAspect reverb_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk8);
GenerateReverbCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
render_context.behavior->IsLongSizePreDelaySupported());
if (reverb_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
reverb_detail_aspect.node_id, PerformanceState::Stop,
reverb_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::I3dl2Reverb: {
DetailAspect i3dl2_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk9);
GenerateI3dl2ReverbEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (i3dl2_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
i3dl2_detail_aspect.node_id, PerformanceState::Stop,
i3dl2_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::BiquadFilter: {
DetailAspect biquad_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk4);
GenerateBiquadFilterEffectCommand(mix_info.buffer_offset, effect_info,
mix_info.node_id);
if (biquad_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
biquad_detail_aspect.node_id, PerformanceState::Stop,
biquad_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::LightLimiter: {
DetailAspect light_limiter_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk11);
GenerateLightLimiterEffectCommand(mix_info.buffer_offset, effect_info, mix_info.node_id,
effect_index);
if (light_limiter_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
light_limiter_detail_aspect.node_id, PerformanceState::Stop,
light_limiter_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Capture: {
DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk12);
GenerateCaptureCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (capture_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
capture_detail_aspect.node_id, PerformanceState::Stop,
capture_detail_aspect.performance_entry_address);
}
} break;
case EffectInfoBase::Type::Compressor: {
DetailAspect capture_detail_aspect(*this, entry_type, mix_info.node_id,
PerformanceDetailType::Unk13);
GenerateCompressorCommand(mix_info.buffer_offset, effect_info, mix_info.node_id);
if (capture_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
capture_detail_aspect.node_id, PerformanceState::Stop,
capture_detail_aspect.performance_entry_address);
}
} break;
default:
LOG_ERROR(Service_Audio, "Invalid effect type {}",
static_cast<u32>(effect_info.GetType()));
break;
}
effect_info.UpdateForCommandGeneration();
}
}
void CommandGenerator::GenerateMixCommands(MixInfo& mix_info) {
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
if (!mix_info.HasAnyConnection()) {
return;
}
if (mix_info.dst_mix_id == UnusedMixId) {
if (mix_info.dst_splitter_id != UnusedSplitterId) {
s16 dest_id{0};
auto destination{
splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id)};
while (destination != nullptr) {
if (destination->IsConfigured()) {
auto splitter_mix_id{destination->GetMixId()};
if (splitter_mix_id < mix_context.GetCount()) {
auto splitter_mix_info{mix_context.GetInfo(splitter_mix_id)};
const s16 input_index{static_cast<s16>(mix_info.buffer_offset +
(dest_id % mix_info.buffer_count))};
for (s16 i = 0; i < splitter_mix_info->buffer_count; i++) {
auto volume{mix_info.volume * destination->GetMixVolume(i)};
if (volume != 0.0f) {
command_buffer.GenerateMixCommand(
mix_info.node_id, input_index,
splitter_mix_info->buffer_offset + i, mix_info.buffer_offset,
volume, precision);
}
}
}
}
dest_id++;
destination =
splitter_context.GetDesintationData(mix_info.dst_splitter_id, dest_id);
}
}
} else {
auto dest_mix_info{mix_context.GetInfo(mix_info.dst_mix_id)};
for (s16 i = 0; i < mix_info.buffer_count; i++) {
for (s16 j = 0; j < dest_mix_info->buffer_count; j++) {
auto volume{mix_info.volume * mix_info.mix_volumes[i][j]};
if (volume != 0.0f) {
command_buffer.GenerateMixCommand(mix_info.node_id, mix_info.buffer_offset + i,
dest_mix_info->buffer_offset + j,
mix_info.buffer_offset, volume, precision);
}
}
}
}
}
void CommandGenerator::GenerateSubMixCommand(MixInfo& mix_info) {
command_buffer.GenerateDepopForMixBuffersCommand(mix_info.node_id, mix_info,
render_context.depop_buffer);
GenerateEffectCommand(mix_info);
DetailAspect mix_detail_aspect(*this, PerformanceEntryType::SubMix, mix_info.node_id,
PerformanceDetailType::Unk5);
GenerateMixCommands(mix_info);
if (mix_detail_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(mix_detail_aspect.node_id, PerformanceState::Stop,
mix_detail_aspect.performance_entry_address);
}
}
void CommandGenerator::GenerateSubMixCommands() {
const auto submix_count{mix_context.GetCount()};
for (s32 i = 0; i < submix_count; i++) {
auto sorted_info{mix_context.GetSortedInfo(i)};
if (!sorted_info->in_use || sorted_info->mix_id == FinalMixId) {
continue;
}
EntryAspect submix_entry_aspect(*this, PerformanceEntryType::SubMix, sorted_info->node_id);
GenerateSubMixCommand(*sorted_info);
if (submix_entry_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(
submix_entry_aspect.node_id, PerformanceState::Stop,
submix_entry_aspect.performance_entry_address);
}
}
}
void CommandGenerator::GenerateFinalMixCommand() {
auto& final_mix_info{*mix_context.GetFinalMixInfo()};
command_buffer.GenerateDepopForMixBuffersCommand(final_mix_info.node_id, final_mix_info,
render_context.depop_buffer);
GenerateEffectCommand(final_mix_info);
u8 precision{15};
if (render_context.behavior->IsVolumeMixParameterPrecisionQ23Supported()) {
precision = 23;
}
for (s16 i = 0; i < final_mix_info.buffer_count; i++) {
DetailAspect volume_aspect(*this, PerformanceEntryType::FinalMix, final_mix_info.node_id,
PerformanceDetailType::Unk3);
command_buffer.GenerateVolumeCommand(final_mix_info.node_id, final_mix_info.buffer_offset,
i, final_mix_info.volume, precision);
if (volume_aspect.initialized) {
command_buffer.GeneratePerformanceCommand(volume_aspect.node_id, PerformanceState::Stop,
volume_aspect.performance_entry_address);
}
}
}
void CommandGenerator::GenerateFinalMixCommands() {
auto final_mix_info{mix_context.GetFinalMixInfo()};
EntryAspect final_mix_entry(*this, PerformanceEntryType::FinalMix, final_mix_info->node_id);
GenerateFinalMixCommand();
if (final_mix_entry.initialized) {
command_buffer.GeneratePerformanceCommand(final_mix_entry.node_id, PerformanceState::Stop,
final_mix_entry.performance_entry_address);
}
}
void CommandGenerator::GenerateSinkCommands() {
const auto sink_count{sink_context.GetCount()};
for (u32 i = 0; i < sink_count; i++) {
auto sink_info{sink_context.GetInfo(i)};
if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::DeviceSink) {
auto state{reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info->GetState())};
if (command_header.sample_rate != TargetSampleRate &&
state->upsampler_info == nullptr) {
auto device_state{sink_info->GetDeviceState()};
device_state->upsampler_info = render_context.upsampler_manager->Allocate();
}
EntryAspect device_sink_entry(*this, PerformanceEntryType::Sink,
sink_info->GetNodeId());
auto final_mix{mix_context.GetFinalMixInfo()};
GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
if (device_sink_entry.initialized) {
command_buffer.GeneratePerformanceCommand(
device_sink_entry.node_id, PerformanceState::Stop,
device_sink_entry.performance_entry_address);
}
}
}
for (u32 i = 0; i < sink_count; i++) {
auto sink_info{sink_context.GetInfo(i)};
if (sink_info->IsUsed() && sink_info->GetType() == SinkInfoBase::Type::CircularBufferSink) {
EntryAspect circular_buffer_entry(*this, PerformanceEntryType::Sink,
sink_info->GetNodeId());
auto final_mix{mix_context.GetFinalMixInfo()};
GenerateSinkCommand(final_mix->buffer_offset, *sink_info);
if (circular_buffer_entry.initialized) {
command_buffer.GeneratePerformanceCommand(
circular_buffer_entry.node_id, PerformanceState::Stop,
circular_buffer_entry.performance_entry_address);
}
}
}
}
void CommandGenerator::GenerateSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
if (sink_info.ShouldSkip()) {
return;
}
switch (sink_info.GetType()) {
case SinkInfoBase::Type::DeviceSink:
GenerateDeviceSinkCommand(buffer_offset, sink_info);
break;
case SinkInfoBase::Type::CircularBufferSink:
command_buffer.GenerateCircularBufferSinkCommand(sink_info.GetNodeId(), sink_info,
buffer_offset);
break;
default:
LOG_ERROR(Service_Audio, "Invalid sink type {}", static_cast<u32>(sink_info.GetType()));
break;
}
sink_info.UpdateForCommandGeneration();
}
void CommandGenerator::GenerateDeviceSinkCommand(const s16 buffer_offset, SinkInfoBase& sink_info) {
auto& parameter{
*reinterpret_cast<DeviceSinkInfo::DeviceInParameter*>(sink_info.GetParameter())};
auto state{*reinterpret_cast<DeviceSinkInfo::DeviceState*>(sink_info.GetState())};
if (render_context.channels == 2 && parameter.downmix_enabled) {
command_buffer.GenerateDownMix6chTo2chCommand(InvalidNodeId, parameter.inputs,
buffer_offset, parameter.downmix_coeff);
}
if (state.upsampler_info != nullptr) {
command_buffer.GenerateUpsampleCommand(
InvalidNodeId, buffer_offset, *state.upsampler_info, parameter.input_count,
parameter.inputs, command_header.buffer_count, command_header.sample_count,
command_header.sample_rate);
}
command_buffer.GenerateDeviceSinkCommand(InvalidNodeId, buffer_offset, sink_info,
render_context.session_id,
command_header.samples_buffer);
}
void CommandGenerator::GeneratePerformanceCommand(
s32 node_id, PerformanceState state, const PerformanceEntryAddresses& entry_addresses) {
command_buffer.GeneratePerformanceCommand(node_id, state, entry_addresses);
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,349 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/renderer/command/commands.h"
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_types.h"
namespace AudioCore {
struct AudioRendererSystemContext;
namespace AudioRenderer {
class CommandBuffer;
struct CommandListHeader;
class VoiceContext;
class MixContext;
class EffectContext;
class SplitterContext;
class SinkContext;
class BehaviorInfo;
class VoiceInfo;
struct VoiceState;
class MixInfo;
class SinkInfoBase;
/**
* Generates all commands to build up a command list, which are sent to the AudioRender for
* processing.
*/
class CommandGenerator {
public:
explicit CommandGenerator(CommandBuffer& command_buffer,
const CommandListHeader& command_list_header,
const AudioRendererSystemContext& render_context,
VoiceContext& voice_context, MixContext& mix_context,
EffectContext& effect_context, SinkContext& sink_context,
SplitterContext& splitter_context,
PerformanceManager* performance_manager);
/**
* Calculate the buffer size needed for commands.
*
* @param behavior - Used to check what features are enabled.
* @param params - Input rendering parameters for numbers of voices/mixes/sinks etc.
*/
static u64 CalculateCommandBufferSize(const BehaviorInfo& behavior,
const AudioRendererParameterInternal& params) {
u64 size{0};
// Effects
size += params.effects * sizeof(EffectInfoBase);
// Voices
u64 voice_size{0};
if (behavior.IsWaveBufferVer2Supported()) {
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion2Command),
sizeof(PcmInt16DataSourceVersion2Command)),
sizeof(PcmFloatDataSourceVersion2Command));
} else {
voice_size = std::max(std::max(sizeof(AdpcmDataSourceVersion1Command),
sizeof(PcmInt16DataSourceVersion1Command)),
sizeof(PcmFloatDataSourceVersion1Command));
}
voice_size += sizeof(BiquadFilterCommand) * MaxBiquadFilters;
voice_size += sizeof(VolumeRampCommand);
voice_size += sizeof(MixRampGroupedCommand);
size += params.voices * (params.splitter_infos * sizeof(DepopPrepareCommand) + voice_size);
// Sub mixes
size += sizeof(DepopForMixBuffersCommand) +
(sizeof(MixCommand) * MaxMixBuffers) * MaxMixBuffers;
// Final mix
size += sizeof(DepopForMixBuffersCommand) + sizeof(VolumeCommand) * MaxMixBuffers;
// Splitters
size += params.splitter_destinations * sizeof(MixRampCommand) * MaxMixBuffers;
// Sinks
size +=
params.sinks * std::max(sizeof(DeviceSinkCommand), sizeof(CircularBufferSinkCommand));
// Performance
size += (params.effects + params.voices + params.sinks + params.sub_mixes + 1 +
PerformanceManager::MaxDetailEntries) *
sizeof(PerformanceCommand);
return size;
}
/**
* Get the current command buffer used to generate commands.
*
* @return The command buffer.
*/
CommandBuffer& GetCommandBuffer() {
return command_buffer;
}
/**
* Get the current performance manager,
*
* @return The performance manager. May be nullptr.
*/
PerformanceManager* GetPerformanceManager() {
return performance_manager;
}
/**
* Generate a data source command.
* These are the basis for all audio output.
*
* @param voice_info - Generate the command from this voice.
* @param voice_state - State used by the AudioRenderer across calls.
* @param channel - Channel index to generate the command into.
*/
void GenerateDataSourceCommand(VoiceInfo& voice_info, const VoiceState& voice_state,
s8 channel);
/**
* Generate voice mixing commands.
* These are used to mix buffers together, to mix one input to many outputs,
* and also used as copy commands to move data around and prevent it being accidentally
* overwritten, e.g by another data source command into the same channel.
*
* @param mix_volumes - Current volumes of the mix.
* @param prev_mix_volumes - Previous volumes of the mix.
* @param voice_state - State used by the AudioRenderer across calls.
* @param output_index - Output mix buffer index.
* @param buffer_count - Number of active mix buffers.
* @param input_index - Input mix buffer index.
* @param node_id - Node id of the voice this command is generated for.
*/
void GenerateVoiceMixCommand(std::span<const f32> mix_volumes,
std::span<const f32> prev_mix_volumes,
const VoiceState& voice_state, s16 output_index, s16 buffer_count,
s16 input_index, s32 node_id);
/**
* Generate a biquad filter command for a voice.
*
* @param voice_info - Voice info this command is generated from.
* @param voice_state - State used by the AudioRenderer across calls.
* @param buffer_count - Number of active mix buffers.
* @param channel - Channel index of this command.
* @param node_id - Node id of the voice this command is generated for.
*/
void GenerateBiquadFilterCommandForVoice(VoiceInfo& voice_info, const VoiceState& voice_state,
s16 buffer_count, s8 channel, s32 node_id);
/**
* Generate commands for a voice.
* Includes a data source, biquad filter, volume and mixing.
*
* @param voice_info - Voice info these commands are generated from.
*/
void GenerateVoiceCommand(VoiceInfo& voice_info);
/**
* Generate commands for all voices.
*/
void GenerateVoiceCommands();
/**
* Generate a mixing command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - BufferMixer effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateBufferMixerCommand(s16 buffer_offset, EffectInfoBase& effect_info_base,
s32 node_id);
/**
* Generate a delay effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Delay effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateDelayCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id);
/**
* Generate a reverb effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Reverb effect info.
* @param node_id - Node id of the mix this command is generated for.
* @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb starts.
*/
void GenerateReverbCommand(s16 buffer_offset, EffectInfoBase& effect_info_base, s32 node_id,
bool long_size_pre_delay_supported);
/**
* Generate an I3DL2 reverb effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - I3DL2Reverb effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id);
/**
* Generate an aux effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Aux effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
/**
* Generate a biquad filter effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Aux effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id);
/**
* Generate a light limiter effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Limiter effect info.
* @param node_id - Node id of the mix this command is generated for.
* @param effect_index - Index for the statistics state.
*/
void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info,
s32 node_id, u32 effect_index);
/**
* Generate a capture effect command.
* Writes a mix buffer back to game memory.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Capture effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id);
/**
* Generate a compressor effect command.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param effect_info_base - Compressor effect info.
* @param node_id - Node id of the mix this command is generated for.
*/
void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info,
const s32 node_id);
/**
* Generate all effect commands for a mix.
*
* @param mix_info - Mix to generate effects from.
*/
void GenerateEffectCommand(MixInfo& mix_info);
/**
* Generate all mix commands.
*
* @param mix_info - Mix to generate effects from.
*/
void GenerateMixCommands(MixInfo& mix_info);
/**
* Generate a submix command.
* Generates all effects and all mixing commands.
*
* @param mix_info - Mix to generate effects from.
*/
void GenerateSubMixCommand(MixInfo& mix_info);
/**
* Generate all submix command.
*/
void GenerateSubMixCommands();
/**
* Generate the final mix.
*/
void GenerateFinalMixCommand();
/**
* Generate the final mix commands.
*/
void GenerateFinalMixCommands();
/**
* Generate all sink commands.
*/
void GenerateSinkCommands();
/**
* Generate a sink command.
* Sends samples out to the backend, or a game-supplied circular buffer.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param sink_info - Sink info to generate the commands from.
*/
void GenerateSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
/**
* Generate a device sink command.
* Sends samples out to the backend.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param sink_info - Sink info to generate the commands from.
*/
void GenerateDeviceSinkCommand(s16 buffer_offset, SinkInfoBase& sink_info);
/**
* Generate a performance command.
* Used to report performance metrics of the AudioRenderer back to the game.
*
* @param buffer_offset - Base mix buffer offset to use.
* @param sink_info - Sink info to generate the commands from.
*/
void GeneratePerformanceCommand(s32 node_id, PerformanceState state,
const PerformanceEntryAddresses& entry_addresses);
private:
/// Commands will be written by this buffer
CommandBuffer& command_buffer;
/// Header information for the commands generated
const CommandListHeader& command_header;
/// Various things to control generation
const AudioRendererSystemContext& render_context;
/// Used for generating voices
VoiceContext& voice_context;
/// Used for generating mixes
MixContext& mix_context;
/// Used for generating effects
EffectContext& effect_context;
/// Used for generating sinks
SinkContext& sink_context;
/// Used for generating submixes
SplitterContext& splitter_context;
/// Used for generating performance
PerformanceManager* performance_manager;
};
} // namespace AudioRenderer
} // namespace AudioCore

View file

@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/common/common.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
struct CommandListHeader {
u64 buffer_size;
u32 command_count;
std::span<s32> samples_buffer;
s16 buffer_count;
u32 sample_count;
u32 sample_rate;
};
} // namespace AudioCore::AudioRenderer

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,254 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "audio_core/renderer/command/commands.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
/**
* Estimate the processing time required for all commands.
*/
class ICommandProcessingTimeEstimator {
public:
virtual ~ICommandProcessingTimeEstimator() = default;
virtual u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const = 0;
virtual u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const = 0;
virtual u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const = 0;
virtual u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const = 0;
virtual u32 Estimate(const AdpcmDataSourceVersion1Command& command) const = 0;
virtual u32 Estimate(const AdpcmDataSourceVersion2Command& command) const = 0;
virtual u32 Estimate(const VolumeCommand& command) const = 0;
virtual u32 Estimate(const VolumeRampCommand& command) const = 0;
virtual u32 Estimate(const BiquadFilterCommand& command) const = 0;
virtual u32 Estimate(const MixCommand& command) const = 0;
virtual u32 Estimate(const MixRampCommand& command) const = 0;
virtual u32 Estimate(const MixRampGroupedCommand& command) const = 0;
virtual u32 Estimate(const DepopPrepareCommand& command) const = 0;
virtual u32 Estimate(const DepopForMixBuffersCommand& command) const = 0;
virtual u32 Estimate(const DelayCommand& command) const = 0;
virtual u32 Estimate(const UpsampleCommand& command) const = 0;
virtual u32 Estimate(const DownMix6chTo2chCommand& command) const = 0;
virtual u32 Estimate(const AuxCommand& command) const = 0;
virtual u32 Estimate(const DeviceSinkCommand& command) const = 0;
virtual u32 Estimate(const CircularBufferSinkCommand& command) const = 0;
virtual u32 Estimate(const ReverbCommand& command) const = 0;
virtual u32 Estimate(const I3dl2ReverbCommand& command) const = 0;
virtual u32 Estimate(const PerformanceCommand& command) const = 0;
virtual u32 Estimate(const ClearMixBufferCommand& command) const = 0;
virtual u32 Estimate(const CopyMixBufferCommand& command) const = 0;
virtual u32 Estimate(const LightLimiterVersion1Command& command) const = 0;
virtual u32 Estimate(const LightLimiterVersion2Command& command) const = 0;
virtual u32 Estimate(const MultiTapBiquadFilterCommand& command) const = 0;
virtual u32 Estimate(const CaptureCommand& command) const = 0;
virtual u32 Estimate(const CompressorCommand& command) const = 0;
};
class CommandProcessingTimeEstimatorVersion1 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion1(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
class CommandProcessingTimeEstimatorVersion2 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion2(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
class CommandProcessingTimeEstimatorVersion3 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion3(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
class CommandProcessingTimeEstimatorVersion4 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion4(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
class CommandProcessingTimeEstimatorVersion5 final : public ICommandProcessingTimeEstimator {
public:
CommandProcessingTimeEstimatorVersion5(u32 sample_count_, u32 buffer_count_)
: sample_count{sample_count_}, buffer_count{buffer_count_} {}
u32 Estimate(const PcmInt16DataSourceVersion1Command& command) const override;
u32 Estimate(const PcmInt16DataSourceVersion2Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion1Command& command) const override;
u32 Estimate(const PcmFloatDataSourceVersion2Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion1Command& command) const override;
u32 Estimate(const AdpcmDataSourceVersion2Command& command) const override;
u32 Estimate(const VolumeCommand& command) const override;
u32 Estimate(const VolumeRampCommand& command) const override;
u32 Estimate(const BiquadFilterCommand& command) const override;
u32 Estimate(const MixCommand& command) const override;
u32 Estimate(const MixRampCommand& command) const override;
u32 Estimate(const MixRampGroupedCommand& command) const override;
u32 Estimate(const DepopPrepareCommand& command) const override;
u32 Estimate(const DepopForMixBuffersCommand& command) const override;
u32 Estimate(const DelayCommand& command) const override;
u32 Estimate(const UpsampleCommand& command) const override;
u32 Estimate(const DownMix6chTo2chCommand& command) const override;
u32 Estimate(const AuxCommand& command) const override;
u32 Estimate(const DeviceSinkCommand& command) const override;
u32 Estimate(const CircularBufferSinkCommand& command) const override;
u32 Estimate(const ReverbCommand& command) const override;
u32 Estimate(const I3dl2ReverbCommand& command) const override;
u32 Estimate(const PerformanceCommand& command) const override;
u32 Estimate(const ClearMixBufferCommand& command) const override;
u32 Estimate(const CopyMixBufferCommand& command) const override;
u32 Estimate(const LightLimiterVersion1Command& command) const override;
u32 Estimate(const LightLimiterVersion2Command& command) const override;
u32 Estimate(const MultiTapBiquadFilterCommand& command) const override;
u32 Estimate(const CaptureCommand& command) const override;
u32 Estimate(const CompressorCommand& command) const override;
private:
u32 sample_count{};
u32 buffer_count{};
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "audio_core/renderer/command/data_source/adpcm.h"
#include "audio_core/renderer/command/data_source/pcm_float.h"
#include "audio_core/renderer/command/data_source/pcm_int16.h"
#include "audio_core/renderer/command/effect/aux_.h"
#include "audio_core/renderer/command/effect/biquad_filter.h"
#include "audio_core/renderer/command/effect/capture.h"
#include "audio_core/renderer/command/effect/compressor.h"
#include "audio_core/renderer/command/effect/delay.h"
#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
#include "audio_core/renderer/command/effect/light_limiter.h"
#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
#include "audio_core/renderer/command/effect/reverb.h"
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/command/mix/clear_mix.h"
#include "audio_core/renderer/command/mix/copy_mix.h"
#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
#include "audio_core/renderer/command/mix/depop_prepare.h"
#include "audio_core/renderer/command/mix/mix.h"
#include "audio_core/renderer/command/mix/mix_ramp.h"
#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
#include "audio_core/renderer/command/mix/volume.h"
#include "audio_core/renderer/command/mix/volume_ramp.h"
#include "audio_core/renderer/command/performance/performance.h"
#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
#include "audio_core/renderer/command/resample/upsample.h"
#include "audio_core/renderer/command/sink/circular_buffer.h"
#include "audio_core/renderer/command/sink/device.h"

View file

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/data_source/adpcm.h"
#include "audio_core/renderer/command/data_source/decode.h"
namespace AudioCore::AudioRenderer {
void AdpcmDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("AdpcmDataSourceVersion1Command\n\toutput_index {:02X} source sample "
"rate {} target sample rate {} src quality {}\n",
output_index, sample_rate, processor.target_sample_rate, src_quality);
}
void AdpcmDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::Adpcm},
.output{out_buffer},
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
.wave_buffers{wave_buffers},
.channel{0},
.channel_count{1},
.src_quality{src_quality},
.pitch{pitch},
.source_sample_rate{sample_rate},
.target_sample_rate{processor.target_sample_rate},
.sample_count{processor.sample_count},
.data_address{data_address},
.data_size{data_size},
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
};
DecodeFromWaveBuffers(*processor.memory, args);
}
bool AdpcmDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
void AdpcmDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("AdpcmDataSourceVersion2Command\n\toutput_index {:02X} source sample "
"rate {} target sample rate {} src quality {}\n",
output_index, sample_rate, processor.target_sample_rate, src_quality);
}
void AdpcmDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::Adpcm},
.output{out_buffer},
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
.wave_buffers{wave_buffers},
.channel{0},
.channel_count{1},
.src_quality{src_quality},
.pitch{pitch},
.source_sample_rate{sample_rate},
.target_sample_rate{processor.target_sample_rate},
.sample_count{processor.sample_count},
.data_address{data_address},
.data_size{data_size},
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
};
DecodeFromWaveBuffers(*processor.memory, args);
}
bool AdpcmDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,119 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <string>
#include "audio_core/common/common.h"
#include "audio_core/common/wave_buffer.h"
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command to decode ADPCM-encoded version 1 wavebuffers
* into the output_index mix buffer.
*/
struct AdpcmDataSourceVersion1Command : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
/// Pitch used for sample rate conversion
f32 pitch;
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
/// Voice state, updated each call and written back to game
CpuAddr voice_state;
/// Coefficients data address
CpuAddr data_address;
/// Coefficients data size
u64 data_size;
};
/**
* AudioRenderer command to decode ADPCM-encoded version 2 wavebuffers
* into the output_index mix buffer.
*/
struct AdpcmDataSourceVersion2Command : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
/// Pitch used for sample rate conversion
f32 pitch;
/// Target channel to read within the wavebuffer
s8 channel_index;
/// Number of channels within the wavebuffer
s8 channel_count;
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
/// Voice state, updated each call and written back to game
CpuAddr voice_state;
/// Coefficients data address
CpuAddr data_address;
/// Coefficients data size
u64 data_size;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,428 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <vector>
#include "audio_core/renderer/command/data_source/decode.h"
#include "audio_core/renderer/command/resample/resample.h"
#include "common/fixed_point.h"
#include "common/logging/log.h"
#include "core/memory.h"
namespace AudioCore::AudioRenderer {
constexpr u32 TempBufferSize = 0x3F00;
constexpr std::array<u8, 3> PitchBySrcQuality = {4, 8, 4};
/**
* Decode PCM data. Only s16 or f32 is supported.
*
* @tparam T - Type to decode. Only s16 and f32 are supported.
* @param memory - Core memory for reading samples.
* @param out_buffer - Output mix buffer to receive the samples.
* @param req - Information for how to decode.
* @return Number of samples decoded.
*/
template <typename T>
static u32 DecodePcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
const DecodeArg& req) {
constexpr s32 min{std::numeric_limits<s16>::min()};
constexpr s32 max{std::numeric_limits<s16>::max()};
if (req.buffer == 0 || req.buffer_size == 0) {
return 0;
}
if (req.start_offset >= req.end_offset) {
return 0;
}
auto samples_to_decode{
std::min(req.samples_to_read, req.end_offset - req.start_offset - req.offset)};
u32 channel_count{static_cast<u32>(req.channel_count)};
switch (req.channel_count) {
default: {
const VAddr source{req.buffer +
(((req.start_offset + req.offset) * channel_count) * sizeof(T))};
const u64 size{channel_count * samples_to_decode};
const u64 size_bytes{size * sizeof(T)};
std::vector<T> samples(size);
memory.ReadBlockUnsafe(source, samples.data(), size_bytes);
if constexpr (std::is_floating_point_v<T>) {
for (u32 i = 0; i < samples_to_decode; i++) {
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
std::numeric_limits<s16>::max())};
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
}
} else {
for (u32 i = 0; i < samples_to_decode; i++) {
out_buffer[i] = samples[i * channel_count + req.target_channel];
}
}
} break;
case 1:
if (req.target_channel != 0) {
LOG_ERROR(Service_Audio, "Invalid target channel, expected 0, got {}",
req.target_channel);
return 0;
}
const VAddr source{req.buffer + ((req.start_offset + req.offset) * sizeof(T))};
std::vector<T> samples(samples_to_decode);
memory.ReadBlockUnsafe(source, samples.data(), samples_to_decode * sizeof(T));
if constexpr (std::is_floating_point_v<T>) {
for (u32 i = 0; i < samples_to_decode; i++) {
auto sample{static_cast<s32>(samples[i * channel_count + req.target_channel] *
std::numeric_limits<s16>::max())};
out_buffer[i] = static_cast<s16>(std::clamp(sample, min, max));
}
} else {
std::memcpy(out_buffer.data(), samples.data(), samples_to_decode * sizeof(s16));
}
break;
}
return samples_to_decode;
}
/**
* Decode ADPCM data.
*
* @param memory - Core memory for reading samples.
* @param out_buffer - Output mix buffer to receive the samples.
* @param req - Information for how to decode.
* @return Number of samples decoded.
*/
static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
const DecodeArg& req) {
constexpr u32 SamplesPerFrame{14};
constexpr u32 NibblesPerFrame{16};
if (req.buffer == 0 || req.buffer_size == 0) {
return 0;
}
if (req.end_offset < req.start_offset) {
return 0;
}
auto end{(req.end_offset % SamplesPerFrame) +
NibblesPerFrame * (req.end_offset / SamplesPerFrame)};
if (req.end_offset % SamplesPerFrame) {
end += 3;
} else {
end += 1;
}
if (req.buffer_size < end / 2) {
return 0;
}
auto samples_to_process{
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
auto samples_to_read{samples_to_process};
auto start_pos{req.start_offset + req.offset};
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
samples_remaining_in_frame};
if (samples_remaining_in_frame) {
position_in_frame += 2;
}
const auto size{std::max((samples_to_process / 8U) * SamplesPerFrame, 8U)};
std::vector<u8> wavebuffer(size);
memory.ReadBlockUnsafe(req.buffer + position_in_frame / 2, wavebuffer.data(),
wavebuffer.size());
auto context{req.adpcm_context};
auto header{context->header};
u8 coeff_index{static_cast<u8>((header >> 4U) & 0xFU)};
u8 scale{static_cast<u8>(header & 0xFU)};
s32 coeff0{req.coefficients[coeff_index * 2 + 0]};
s32 coeff1{req.coefficients[coeff_index * 2 + 1]};
auto yn0{context->yn0};
auto yn1{context->yn1};
static constexpr std::array<s32, 16> Steps{
0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1,
};
const auto decode_sample = [&](const s32 code) -> s16 {
const auto xn = code * (1 << scale);
const auto prediction = coeff0 * yn0 + coeff1 * yn1;
const auto sample = ((xn << 11) + 0x400 + prediction) >> 11;
const auto saturated = std::clamp<s32>(sample, -0x8000, 0x7FFF);
yn1 = yn0;
yn0 = static_cast<s16>(saturated);
return yn0;
};
u32 read_index{0};
u32 write_index{0};
while (samples_to_read > 0) {
// Are we at a new frame?
if ((position_in_frame % NibblesPerFrame) == 0) {
header = wavebuffer[read_index++];
coeff_index = (header >> 4) & 0xF;
scale = header & 0xF;
coeff0 = req.coefficients[coeff_index * 2 + 0];
coeff1 = req.coefficients[coeff_index * 2 + 1];
position_in_frame += 2;
// Can we consume all of this frame's samples?
if (samples_to_read >= SamplesPerFrame) {
// Can grab all samples until the next header
for (u32 i = 0; i < SamplesPerFrame / 2; i++) {
auto code0{Steps[(wavebuffer[read_index] >> 4) & 0xF]};
auto code1{Steps[wavebuffer[read_index] & 0xF]};
read_index++;
out_buffer[write_index++] = decode_sample(code0);
out_buffer[write_index++] = decode_sample(code1);
}
position_in_frame += SamplesPerFrame;
samples_to_read -= SamplesPerFrame;
continue;
}
}
// Decode a single sample
auto code{wavebuffer[read_index]};
if (position_in_frame & 1) {
code &= 0xF;
read_index++;
} else {
code >>= 4;
}
out_buffer[write_index++] = decode_sample(Steps[code]);
position_in_frame++;
samples_to_read--;
}
context->header = header;
context->yn0 = yn0;
context->yn1 = yn1;
return samples_to_process;
}
/**
* Decode implementation.
* Decode wavebuffers according to the given args.
*
* @param memory - Core memory to read data from.
* @param args - The wavebuffer data, and information for how to decode it.
*/
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
auto& voice_state{*args.voice_state};
auto remaining_sample_count{args.sample_count};
auto fraction{voice_state.fraction};
const auto sample_rate_ratio{
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
args.pitch};
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
if (size_required < 0) {
return;
}
auto pitch{PitchBySrcQuality[static_cast<u32>(args.src_quality)]};
if (static_cast<u32>(pitch + size_required.to_int_floor()) > TempBufferSize) {
return;
}
auto max_remaining_sample_count{
((Common::FixedPoint<17, 15>(TempBufferSize) - fraction) / sample_rate_ratio)
.to_uint_floor()};
max_remaining_sample_count = std::min(max_remaining_sample_count, remaining_sample_count);
auto wavebuffers_consumed{voice_state.wave_buffers_consumed};
auto wavebuffer_index{voice_state.wave_buffer_index};
auto played_sample_count{voice_state.played_sample_count};
bool is_buffer_starved{false};
u32 offset{voice_state.offset};
auto output_buffer{args.output};
std::vector<s16> temp_buffer(TempBufferSize, 0);
while (remaining_sample_count > 0) {
const auto samples_to_write{std::min(remaining_sample_count, max_remaining_sample_count)};
const auto samples_to_read{
(fraction + samples_to_write * sample_rate_ratio).to_uint_floor()};
u32 temp_buffer_pos{0};
if (!args.IsVoicePitchAndSrcSkippedSupported) {
for (u32 i = 0; i < pitch; i++) {
temp_buffer[i] = voice_state.sample_history[i];
}
temp_buffer_pos = pitch;
}
u32 samples_read{0};
while (samples_read < samples_to_read) {
if (wavebuffer_index >= MaxWaveBuffers) {
LOG_ERROR(Service_Audio, "Invalid wavebuffer index! {}", wavebuffer_index);
wavebuffer_index = 0;
voice_state.wave_buffer_valid.fill(false);
wavebuffers_consumed = MaxWaveBuffers;
}
if (!voice_state.wave_buffer_valid[wavebuffer_index]) {
is_buffer_starved = true;
break;
}
auto& wavebuffer{args.wave_buffers[wavebuffer_index]};
if (offset == 0 && args.sample_format == SampleFormat::Adpcm &&
wavebuffer.context != 0) {
memory.ReadBlockUnsafe(wavebuffer.context, &voice_state.adpcm_context,
wavebuffer.context_size);
}
auto start_offset{wavebuffer.start_offset};
auto end_offset{wavebuffer.end_offset};
if (wavebuffer.loop && voice_state.loop_count > 0 &&
wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
start_offset = wavebuffer.loop_start_offset;
end_offset = wavebuffer.loop_end_offset;
}
DecodeArg decode_arg{.buffer{wavebuffer.buffer},
.buffer_size{wavebuffer.buffer_size},
.start_offset{start_offset},
.end_offset{end_offset},
.channel_count{args.channel_count},
.coefficients{},
.adpcm_context{nullptr},
.target_channel{args.channel},
.offset{offset},
.samples_to_read{samples_to_read - samples_read}};
s32 samples_decoded{0};
switch (args.sample_format) {
case SampleFormat::PcmInt16:
samples_decoded = DecodePcm<s16>(
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
decode_arg);
break;
case SampleFormat::PcmFloat:
samples_decoded = DecodePcm<f32>(
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
decode_arg);
break;
case SampleFormat::Adpcm: {
decode_arg.adpcm_context = &voice_state.adpcm_context;
memory.ReadBlockUnsafe(args.data_address, &decode_arg.coefficients, args.data_size);
samples_decoded = DecodeAdpcm(
memory, {&temp_buffer[temp_buffer_pos], TempBufferSize - temp_buffer_pos},
decode_arg);
} break;
default:
LOG_ERROR(Service_Audio, "Invalid sample format to decode {}",
static_cast<u32>(args.sample_format));
samples_decoded = 0;
break;
}
played_sample_count += samples_decoded;
samples_read += samples_decoded;
temp_buffer_pos += samples_decoded;
offset += samples_decoded;
if (samples_decoded == 0 || offset >= end_offset - start_offset) {
offset = 0;
if (!wavebuffer.loop) {
voice_state.wave_buffer_valid[wavebuffer_index] = false;
voice_state.loop_count = 0;
if (wavebuffer.stream_ended) {
played_sample_count = 0;
}
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
wavebuffers_consumed++;
} else {
voice_state.loop_count++;
if (wavebuffer.loop_count > 0 &&
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
voice_state.wave_buffer_valid[wavebuffer_index] = false;
voice_state.loop_count = 0;
if (wavebuffer.stream_ended) {
played_sample_count = 0;
}
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
wavebuffers_consumed++;
}
if (samples_decoded == 0) {
is_buffer_starved = true;
break;
}
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
played_sample_count = 0;
}
}
}
}
if (args.IsVoicePitchAndSrcSkippedSupported) {
if (samples_read > output_buffer.size()) {
LOG_ERROR(Service_Audio, "Attempting to write past the end of output buffer!");
}
for (u32 i = 0; i < samples_read; i++) {
output_buffer[i] = temp_buffer[i];
}
} else {
std::memset(&temp_buffer[temp_buffer_pos], 0,
(samples_to_read - samples_read) * sizeof(s16));
Resample(output_buffer, temp_buffer, sample_rate_ratio, fraction, samples_to_write,
args.src_quality);
std::memcpy(voice_state.sample_history.data(), &temp_buffer[samples_to_read],
pitch * sizeof(s16));
}
remaining_sample_count -= samples_to_write;
if (remaining_sample_count != 0 && is_buffer_starved) {
LOG_ERROR(Service_Audio, "Samples remaining but buffer is starving??");
break;
}
output_buffer = output_buffer.subspan(samples_to_write);
}
voice_state.wave_buffers_consumed = wavebuffers_consumed;
voice_state.played_sample_count = played_sample_count;
voice_state.wave_buffer_index = wavebuffer_index;
voice_state.offset = offset;
voice_state.fraction = fraction;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,59 @@
// 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 "audio_core/common/wave_buffer.h"
#include "audio_core/renderer/voice/voice_state.h"
#include "common/common_types.h"
namespace Core::Memory {
class Memory;
}
namespace AudioCore::AudioRenderer {
struct DecodeFromWaveBuffersArgs {
SampleFormat sample_format;
std::span<s32> output;
VoiceState* voice_state;
std::span<WaveBufferVersion2> wave_buffers;
s8 channel;
s8 channel_count;
SrcQuality src_quality;
f32 pitch;
u32 source_sample_rate;
u32 target_sample_rate;
u32 sample_count;
CpuAddr data_address;
u64 data_size;
bool IsVoicePlayedSampleCountResetAtLoopPointSupported;
bool IsVoicePitchAndSrcSkippedSupported;
};
struct DecodeArg {
CpuAddr buffer;
u64 buffer_size;
u32 start_offset;
u32 end_offset;
s8 channel_count;
std::array<s16, 16> coefficients;
VoiceState::AdpcmContext* adpcm_context;
s8 target_channel;
u32 offset;
u32 samples_to_read;
};
/**
* Decode wavebuffers according to the given args.
*
* @param memory - Core memory to read data from.
* @param args - The wavebuffer data, and information for how to decode it.
*/
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args);
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/data_source/decode.h"
#include "audio_core/renderer/command/data_source/pcm_float.h"
namespace AudioCore::AudioRenderer {
void PcmFloatDataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
std::string& string) {
string +=
fmt::format("PcmFloatDataSourceVersion1Command\n\toutput_index {:02X} channel {} "
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
output_index, channel_index, channel_count, sample_rate,
processor.target_sample_rate, src_quality);
}
void PcmFloatDataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count);
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmFloat},
.output{out_buffer},
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
.wave_buffers{wave_buffers},
.channel{channel_index},
.channel_count{channel_count},
.src_quality{src_quality},
.pitch{pitch},
.source_sample_rate{sample_rate},
.target_sample_rate{processor.target_sample_rate},
.sample_count{processor.sample_count},
.data_address{0},
.data_size{0},
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
};
DecodeFromWaveBuffers(*processor.memory, args);
}
bool PcmFloatDataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
void PcmFloatDataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
std::string& string) {
string +=
fmt::format("PcmFloatDataSourceVersion2Command\n\toutput_index {:02X} channel {} "
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
output_index, channel_index, channel_count, sample_rate,
processor.target_sample_rate, src_quality);
}
void PcmFloatDataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count);
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmFloat},
.output{out_buffer},
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
.wave_buffers{wave_buffers},
.channel{channel_index},
.channel_count{channel_count},
.src_quality{src_quality},
.pitch{pitch},
.source_sample_rate{sample_rate},
.target_sample_rate{processor.target_sample_rate},
.sample_count{processor.sample_count},
.data_address{0},
.data_size{0},
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
};
DecodeFromWaveBuffers(*processor.memory, args);
}
bool PcmFloatDataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/common/wave_buffer.h"
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command to decode PCM float-encoded version 1 wavebuffers
* into the output_index mix buffer.
*/
struct PcmFloatDataSourceVersion1Command : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
/// Pitch used for sample rate conversion
f32 pitch;
/// Target channel to read within the wavebuffer
s8 channel_index;
/// Number of channels within the wavebuffer
s8 channel_count;
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
/// Voice state, updated each call and written back to game
CpuAddr voice_state;
};
/**
* AudioRenderer command to decode PCM float-encoded version 2 wavebuffers
* into the output_index mix buffer.
*/
struct PcmFloatDataSourceVersion2Command : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
/// Pitch used for sample rate conversion
f32 pitch;
/// Target channel to read within the wavebuffer
s8 channel_index;
/// Number of channels within the wavebuffer
s8 channel_count;
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
/// Voice state, updated each call and written back to game
CpuAddr voice_state;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <span>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/data_source/decode.h"
#include "audio_core/renderer/command/data_source/pcm_int16.h"
namespace AudioCore::AudioRenderer {
void PcmInt16DataSourceVersion1Command::Dump(const ADSP::CommandListProcessor& processor,
std::string& string) {
string +=
fmt::format("PcmInt16DataSourceVersion1Command\n\toutput_index {:02X} channel {} "
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
output_index, channel_index, channel_count, sample_rate,
processor.target_sample_rate, src_quality);
}
void PcmInt16DataSourceVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count);
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmInt16},
.output{out_buffer},
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
.wave_buffers{wave_buffers},
.channel{channel_index},
.channel_count{channel_count},
.src_quality{src_quality},
.pitch{pitch},
.source_sample_rate{sample_rate},
.target_sample_rate{processor.target_sample_rate},
.sample_count{processor.sample_count},
.data_address{0},
.data_size{0},
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
};
DecodeFromWaveBuffers(*processor.memory, args);
}
bool PcmInt16DataSourceVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
void PcmInt16DataSourceVersion2Command::Dump(const ADSP::CommandListProcessor& processor,
std::string& string) {
string +=
fmt::format("PcmInt16DataSourceVersion2Command\n\toutput_index {:02X} channel {} "
"channel count {} source sample rate {} target sample rate {} src quality {}\n",
output_index, channel_index, channel_count, sample_rate,
processor.target_sample_rate, src_quality);
}
void PcmInt16DataSourceVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count);
DecodeFromWaveBuffersArgs args{
.sample_format{SampleFormat::PcmInt16},
.output{out_buffer},
.voice_state{reinterpret_cast<VoiceState*>(voice_state)},
.wave_buffers{wave_buffers},
.channel{channel_index},
.channel_count{channel_count},
.src_quality{src_quality},
.pitch{pitch},
.source_sample_rate{sample_rate},
.target_sample_rate{processor.target_sample_rate},
.sample_count{processor.sample_count},
.data_address{0},
.data_size{0},
.IsVoicePlayedSampleCountResetAtLoopPointSupported{(flags & 1) != 0},
.IsVoicePitchAndSrcSkippedSupported{(flags & 2) != 0},
};
DecodeFromWaveBuffers(*processor.memory, args);
}
bool PcmInt16DataSourceVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,110 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/common/wave_buffer.h"
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command to decode PCM s16-encoded version 1 wavebuffers
* into the output_index mix buffer.
*/
struct PcmInt16DataSourceVersion1Command : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
/// Pitch used for sample rate conversion
f32 pitch;
/// Target channel to read within the wavebuffer
s8 channel_index;
/// Number of channels within the wavebuffer
s8 channel_count;
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
/// Voice state, updated each call and written back to game
CpuAddr voice_state;
};
/**
* AudioRenderer command to decode PCM s16-encoded version 2 wavebuffers
* into the output_index mix buffer.
*/
struct PcmInt16DataSourceVersion2Command : ICommand {
/**
* Print this command's information to a string.
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Quality used for sample rate conversion
SrcQuality src_quality;
/// Mix buffer index for decoded samples
s16 output_index;
/// Flags to control decoding (see AudioCore::AudioRenderer::VoiceInfo::Flags)
u16 flags;
/// Wavebuffer sample rate
u32 sample_rate;
/// Pitch used for sample rate conversion
f32 pitch;
/// Target channel to read within the wavebuffer
s8 channel_index;
/// Number of channels within the wavebuffer
s8 channel_count;
/// Wavebuffers containing the wavebuffer address, context address, looping information etc
std::array<WaveBufferVersion2, MaxWaveBuffers> wave_buffers;
/// Voice state, updated each call and written back to game
CpuAddr voice_state;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,207 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/aux_.h"
#include "audio_core/renderer/effect/aux_.h"
#include "core/memory.h"
namespace AudioCore::AudioRenderer {
/**
* Reset an AuxBuffer.
*
* @param memory - Core memory for writing.
* @param aux_info - Memory address pointing to the AuxInfo to reset.
*/
static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
if (aux_info == 0) {
LOG_ERROR(Service_Audio, "Aux info is 0!");
return;
}
auto info{reinterpret_cast<AuxInfo::AuxInfoDsp*>(memory.GetPointer(aux_info))};
info->read_offset = 0;
info->write_offset = 0;
info->total_sample_count = 0;
}
/**
* Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
* update_count is set, to notify the game that an update happened.
*
* @param memory - Core memory for writing.
* @param send_info_ - Meta information for where to write the mix buffer.
* @param sample_count - Unused.
* @param send_buffer - Memory address to write the mix buffer to.
* @param count_max - Maximum number of samples in the receiving buffer.
* @param input - Input mix buffer to write.
* @param write_count_ - Number of samples to write.
* @param write_offset - Current offset to begin writing the receiving buffer at.
* @param update_count - If non-zero, send_info_ will be updated.
* @return Number of samples written.
*/
static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
[[maybe_unused]] u32 sample_count, const CpuAddr send_buffer,
const u32 count_max, std::span<const s32> input,
const u32 write_count_, const u32 write_offset,
const u32 update_count) {
if (write_count_ > count_max) {
LOG_ERROR(Service_Audio,
"write_count must be smaller than count_max! write_count {}, count_max {}",
write_count_, count_max);
return 0;
}
if (input.empty()) {
LOG_ERROR(Service_Audio, "input buffer is empty!");
return 0;
}
if (send_buffer == 0) {
LOG_ERROR(Service_Audio, "send_buffer is 0!");
return 0;
}
if (count_max == 0) {
return 0;
}
AuxInfo::AuxInfoDsp send_info{};
memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
u32 target_write_offset{send_info.write_offset + write_offset};
if (target_write_offset > count_max || write_count_ == 0) {
return 0;
}
u32 write_count{write_count_};
u32 write_pos{0};
while (write_count > 0) {
u32 to_write{std::min(count_max - target_write_offset, write_count)};
if (to_write > 0) {
memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
&input[write_pos], to_write * sizeof(s32));
}
target_write_offset = (target_write_offset + to_write) % count_max;
write_count -= to_write;
write_pos += to_write;
}
if (update_count) {
send_info.write_offset = (send_info.write_offset + update_count) % count_max;
}
memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxInfoDsp));
return write_count_;
}
/**
* Read the given memory at return_buffer into the output mix buffer, and update return_info_ if
* update_count is set, to notify the game that an update happened.
*
* @param memory - Core memory for writing.
* @param return_info_ - Meta information for where to read the mix buffer.
* @param return_buffer - Memory address to read the samples from.
* @param count_max - Maximum number of samples in the receiving buffer.
* @param output - Output mix buffer which will receive the samples.
* @param count_ - Number of samples to read.
* @param read_offset - Current offset to begin reading the return_buffer at.
* @param update_count - If non-zero, send_info_ will be updated.
* @return Number of samples read.
*/
static u32 ReadAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr return_info_,
const CpuAddr return_buffer, const u32 count_max, std::span<s32> output,
const u32 count_, const u32 read_offset, const u32 update_count) {
if (count_max == 0) {
return 0;
}
if (count_ > count_max) {
LOG_ERROR(Service_Audio, "count must be smaller than count_max! count {}, count_max {}",
count_, count_max);
return 0;
}
if (output.empty()) {
LOG_ERROR(Service_Audio, "output buffer is empty!");
return 0;
}
if (return_buffer == 0) {
LOG_ERROR(Service_Audio, "return_buffer is 0!");
return 0;
}
AuxInfo::AuxInfoDsp return_info{};
memory.ReadBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
u32 target_read_offset{return_info.read_offset + read_offset};
if (target_read_offset > count_max) {
return 0;
}
u32 read_count{count_};
u32 read_pos{0};
while (read_count > 0) {
u32 to_read{std::min(count_max - target_read_offset, read_count)};
if (to_read > 0) {
memory.ReadBlockUnsafe(return_buffer + target_read_offset * sizeof(s32),
&output[read_pos], to_read * sizeof(s32));
}
target_read_offset = (target_read_offset + to_read) % count_max;
read_count -= to_read;
read_pos += to_read;
}
if (update_count) {
return_info.read_offset = (return_info.read_offset + update_count) % count_max;
}
memory.WriteBlockUnsafe(return_info_, &return_info, sizeof(AuxInfo::AuxInfoDsp));
return count_;
}
void AuxCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("AuxCommand\n\tenabled {} input {:02X} output {:02X}\n", effect_enabled,
input, output);
}
void AuxCommand::Process(const ADSP::CommandListProcessor& processor) {
auto input_buffer{
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
auto output_buffer{
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
if (effect_enabled) {
WriteAuxBufferDsp(*processor.memory, send_buffer_info, processor.sample_count, send_buffer,
count_max, input_buffer, processor.sample_count, write_offset,
update_count);
auto read{ReadAuxBufferDsp(*processor.memory, return_buffer_info, return_buffer, count_max,
output_buffer, processor.sample_count, write_offset,
update_count)};
if (read != processor.sample_count) {
std::memset(&output_buffer[read], 0, processor.sample_count - read);
}
} else {
ResetAuxBufferDsp(*processor.memory, send_buffer_info);
ResetAuxBufferDsp(*processor.memory, return_buffer_info);
if (input != output) {
std::memcpy(output_buffer.data(), input_buffer.data(), output_buffer.size_bytes());
}
}
}
bool AuxCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command to read and write an auxiliary buffer, writing the input mix buffer to game
* memory, and reading into the output buffer from game memory.
*/
struct AuxCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input;
/// Output mix buffer index
s16 output;
/// Meta info for writing
CpuAddr send_buffer_info;
/// Meta info for reading
CpuAddr return_buffer_info;
/// Game memory write buffer
CpuAddr send_buffer;
/// Game memory read buffer
CpuAddr return_buffer;
/// Max samples to read/write
u32 count_max;
/// Current read/write offset
u32 write_offset;
/// Number of samples to update per call
u32 update_count;
/// is this effect enabled?
bool effect_enabled;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,118 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/biquad_filter.h"
#include "audio_core/renderer/voice/voice_state.h"
namespace AudioCore::AudioRenderer {
/**
* Biquad filter float implementation.
*
* @param output - Output container for filtered samples.
* @param input - Input container for samples to be filtered.
* @param b - Feedforward coefficients.
* @param a - Feedback coefficients.
* @param state - State to track previous samples between calls.
* @param sample_count - Number of samples to process.
*/
void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
std::array<s16, 3>& b_, std::array<s16, 2>& a_,
VoiceState::BiquadFilterState& state, const u32 sample_count) {
constexpr s64 min{std::numeric_limits<s32>::min()};
constexpr s64 max{std::numeric_limits<s32>::max()};
std::array<f64, 3> b{Common::FixedPoint<50, 14>::from_base(b_[0]).to_double(),
Common::FixedPoint<50, 14>::from_base(b_[1]).to_double(),
Common::FixedPoint<50, 14>::from_base(b_[2]).to_double()};
std::array<f64, 2> a{Common::FixedPoint<50, 14>::from_base(a_[0]).to_double(),
Common::FixedPoint<50, 14>::from_base(a_[1]).to_double()};
std::array<f64, 4> s{state.s0.to_double(), state.s1.to_double(), state.s2.to_double(),
state.s3.to_double()};
for (u32 i = 0; i < sample_count; i++) {
f64 in_sample{static_cast<f64>(input[i])};
auto sample{in_sample * b[0] + s[0] * b[1] + s[1] * b[2] + s[2] * a[0] + s[3] * a[1]};
output[i] = static_cast<s32>(std::clamp(static_cast<s64>(sample), min, max));
s[1] = s[0];
s[0] = in_sample;
s[3] = s[2];
s[2] = sample;
}
state.s0 = s[0];
state.s1 = s[1];
state.s2 = s[2];
state.s3 = s[3];
}
/**
* Biquad filter s32 implementation.
*
* @param output - Output container for filtered samples.
* @param input - Input container for samples to be filtered.
* @param b - Feedforward coefficients.
* @param a - Feedback coefficients.
* @param state - State to track previous samples between calls.
* @param sample_count - Number of samples to process.
*/
static void ApplyBiquadFilterInt(std::span<s32> output, std::span<const s32> input,
std::array<s16, 3>& b_, std::array<s16, 2>& a_,
VoiceState::BiquadFilterState& state, const u32 sample_count) {
constexpr s64 min{std::numeric_limits<s32>::min()};
constexpr s64 max{std::numeric_limits<s32>::max()};
std::array<Common::FixedPoint<50, 14>, 3> b{
Common::FixedPoint<50, 14>::from_base(b_[0]),
Common::FixedPoint<50, 14>::from_base(b_[1]),
Common::FixedPoint<50, 14>::from_base(b_[2]),
};
std::array<Common::FixedPoint<50, 14>, 3> a{
Common::FixedPoint<50, 14>::from_base(a_[0]),
Common::FixedPoint<50, 14>::from_base(a_[1]),
};
for (u32 i = 0; i < sample_count; i++) {
s64 in_sample{input[i]};
auto sample{in_sample * b[0] + state.s0};
const auto out_sample{std::clamp(sample.to_long(), min, max)};
output[i] = static_cast<s32>(out_sample);
state.s0 = state.s1 + b[1] * in_sample + a[0] * out_sample;
state.s1 = 0 + b[2] * in_sample + a[1] * out_sample;
}
}
void BiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format(
"BiquadFilterCommand\n\tinput {:02X} output {:02X} needs_init {} use_float_processing {}\n",
input, output, needs_init, use_float_processing);
}
void BiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
auto state_{reinterpret_cast<VoiceState::BiquadFilterState*>(state)};
if (needs_init) {
std::memset(state_, 0, sizeof(VoiceState::BiquadFilterState));
}
auto input_buffer{
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
auto output_buffer{
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
if (use_float_processing) {
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
processor.sample_count);
} else {
ApplyBiquadFilterInt(output_buffer, input_buffer, biquad.b, biquad.a, *state_,
processor.sample_count);
}
}
bool BiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/voice/voice_info.h"
#include "audio_core/renderer/voice/voice_state.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for applying a biquad filter to the input mix buffer, saving the results to
* the output mix buffer.
*/
struct BiquadFilterCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input;
/// Output mix buffer index
s16 output;
/// Input parameters for biquad
VoiceInfo::BiquadFilterParameter biquad;
/// Biquad state, updated each call
CpuAddr state;
/// If true, reset the state
bool needs_init;
/// If true, use float processing rather than int
bool use_float_processing;
};
/**
* Biquad filter float implementation.
*
* @param output - Output container for filtered samples.
* @param input - Input container for samples to be filtered.
* @param b - Feedforward coefficients.
* @param a - Feedback coefficients.
* @param state - State to track previous samples.
* @param sample_count - Number of samples to process.
*/
void ApplyBiquadFilterFloat(std::span<s32> output, std::span<const s32> input,
std::array<s16, 3>& b, std::array<s16, 2>& a,
VoiceState::BiquadFilterState& state, const u32 sample_count);
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,142 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/capture.h"
#include "audio_core/renderer/effect/aux_.h"
#include "core/memory.h"
namespace AudioCore::AudioRenderer {
/**
* Reset an AuxBuffer.
*
* @param memory - Core memory for writing.
* @param aux_info - Memory address pointing to the AuxInfo to reset.
*/
static void ResetAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr aux_info) {
if (aux_info == 0) {
LOG_ERROR(Service_Audio, "Aux info is 0!");
return;
}
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, read_offset)), 0);
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, write_offset)), 0);
memory.Write32(VAddr(aux_info + offsetof(AuxInfo::AuxInfoDsp, total_sample_count)), 0);
}
/**
* Write the given input mix buffer to the memory at send_buffer, and update send_info_ if
* update_count is set, to notify the game that an update happened.
*
* @param memory - Core memory for writing.
* @param send_info_ - Header information for where to write the mix buffer.
* @param send_buffer - Memory address to write the mix buffer to.
* @param count_max - Maximum number of samples in the receiving buffer.
* @param input - Input mix buffer to write.
* @param write_count_ - Number of samples to write.
* @param write_offset - Current offset to begin writing the receiving buffer at.
* @param update_count - If non-zero, send_info_ will be updated.
* @return Number of samples written.
*/
static u32 WriteAuxBufferDsp(Core::Memory::Memory& memory, const CpuAddr send_info_,
const CpuAddr send_buffer, u32 count_max, std::span<const s32> input,
const u32 write_count_, const u32 write_offset,
const u32 update_count) {
if (write_count_ > count_max) {
LOG_ERROR(Service_Audio,
"write_count must be smaller than count_max! write_count {}, count_max {}",
write_count_, count_max);
return 0;
}
if (send_info_ == 0) {
LOG_ERROR(Service_Audio, "send_info is 0!");
return 0;
}
if (input.empty()) {
LOG_ERROR(Service_Audio, "input buffer is empty!");
return 0;
}
if (send_buffer == 0) {
LOG_ERROR(Service_Audio, "send_buffer is 0!");
return 0;
}
if (count_max == 0) {
return 0;
}
AuxInfo::AuxBufferInfo send_info{};
memory.ReadBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
u32 target_write_offset{send_info.dsp_info.write_offset + write_offset};
if (target_write_offset > count_max || write_count_ == 0) {
return 0;
}
u32 write_count{write_count_};
u32 write_pos{0};
while (write_count > 0) {
u32 to_write{std::min(count_max - target_write_offset, write_count)};
if (to_write > 0) {
memory.WriteBlockUnsafe(send_buffer + target_write_offset * sizeof(s32),
&input[write_pos], to_write * sizeof(s32));
}
target_write_offset = (target_write_offset + to_write) % count_max;
write_count -= to_write;
write_pos += to_write;
}
if (update_count) {
const auto count_diff{send_info.dsp_info.total_sample_count -
send_info.cpu_info.total_sample_count};
if (count_diff >= count_max) {
auto dsp_lost_count{send_info.dsp_info.lost_sample_count + update_count};
if (dsp_lost_count - send_info.cpu_info.lost_sample_count <
send_info.dsp_info.lost_sample_count - send_info.cpu_info.lost_sample_count) {
dsp_lost_count = send_info.cpu_info.lost_sample_count - 1;
}
send_info.dsp_info.lost_sample_count = dsp_lost_count;
}
send_info.dsp_info.write_offset =
(send_info.dsp_info.write_offset + update_count + count_max) % count_max;
auto new_sample_count{send_info.dsp_info.total_sample_count + update_count};
if (new_sample_count - send_info.cpu_info.total_sample_count < count_diff) {
new_sample_count = send_info.cpu_info.total_sample_count - 1;
}
send_info.dsp_info.total_sample_count = new_sample_count;
}
memory.WriteBlockUnsafe(send_info_, &send_info, sizeof(AuxInfo::AuxBufferInfo));
return write_count_;
}
void CaptureCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("CaptureCommand\n\tenabled {} input {:02X} output {:02X}", effect_enabled,
input, output);
}
void CaptureCommand::Process(const ADSP::CommandListProcessor& processor) {
if (effect_enabled) {
auto input_buffer{
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
WriteAuxBufferDsp(*processor.memory, send_buffer_info, send_buffer, count_max, input_buffer,
processor.sample_count, write_offset, update_count);
} else {
ResetAuxBufferDsp(*processor.memory, send_buffer_info);
}
}
bool CaptureCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for capturing a mix buffer. That is, writing it back to a given game memory
* address.
*/
struct CaptureCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input;
/// Output mix buffer index
s16 output;
/// Meta info for writing
CpuAddr send_buffer_info;
/// Game memory write buffer
CpuAddr send_buffer;
/// Max samples to read/write
u32 count_max;
/// Current read/write offset
u32 write_offset;
/// Number of samples to update per call
u32 update_count;
/// is this effect enabled?
bool effect_enabled;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,156 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cmath>
#include <span>
#include <vector>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/compressor.h"
#include "audio_core/renderer/effect/compressor.h"
namespace AudioCore::AudioRenderer {
static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params,
CompressorInfo::State& state) {
const auto ratio{1.0f / params.compressor_ratio};
auto makeup_gain{0.0f};
if (params.makeup_gain_enabled) {
makeup_gain = (params.threshold * 0.5f) * (ratio - 1.0f) - 3.0f;
}
state.makeup_gain = makeup_gain;
state.unk_18 = params.unk_28;
const auto a{(params.out_gain + makeup_gain) / 20.0f * 3.3219f};
const auto b{(a - std::trunc(a)) * 0.69315f};
const auto c{std::pow(2.0f, b)};
state.unk_0C = (1.0f - ratio) / 6.0f;
state.unk_14 = params.threshold + 1.5f;
state.unk_10 = params.threshold - 1.5f;
state.unk_20 = c;
}
static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params,
CompressorInfo::State& state) {
std::memset(&state, 0, sizeof(CompressorInfo::State));
state.unk_00 = 0;
state.unk_04 = 1.0f;
state.unk_08 = 1.0f;
SetCompressorEffectParameter(params, state);
}
static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params,
CompressorInfo::State& state, bool enabled,
std::vector<std::span<const s32>> input_buffers,
std::vector<std::span<s32>> output_buffers, u32 sample_count) {
if (enabled) {
auto state_00{state.unk_00};
auto state_04{state.unk_04};
auto state_08{state.unk_08};
auto state_18{state.unk_18};
for (u32 i = 0; i < sample_count; i++) {
auto a{0.0f};
for (s16 channel = 0; channel < params.channel_count; channel++) {
const auto input_sample{Common::FixedPoint<49, 15>(input_buffers[channel][i])};
a += (input_sample * input_sample).to_float();
}
state_00 += params.unk_24 * ((a / params.channel_count) - state.unk_00);
auto b{-100.0f};
auto c{0.0f};
if (state_00 >= 1.0e-10) {
b = std::log10(state_00) * 10.0f;
c = 1.0f;
}
if (b >= state.unk_10) {
const auto d{b >= state.unk_14
? ((1.0f / params.compressor_ratio) - 1.0f) *
(b - params.threshold)
: (b - state.unk_10) * (b - state.unk_10) * -state.unk_0C};
const auto e{d / 20.0f * 3.3219f};
const auto f{(e - std::trunc(e)) * 0.69315f};
c = std::pow(2.0f, f);
}
state_18 = params.unk_28;
auto tmp{c};
if ((state_04 - c) <= 0.08f) {
state_18 = params.unk_2C;
if (((state_04 - c) >= -0.08f) && (std::abs(state_08 - c) >= 0.001f)) {
tmp = state_04;
}
}
state_04 = tmp;
state_08 += (c - state_08) * state_18;
for (s16 channel = 0; channel < params.channel_count; channel++) {
output_buffers[channel][i] = static_cast<s32>(
static_cast<f32>(input_buffers[channel][i]) * state_08 * state.unk_20);
}
}
state.unk_00 = state_00;
state.unk_04 = state_04;
state.unk_08 = state_08;
state.unk_18 = state_18;
} else {
for (s16 channel = 0; channel < params.channel_count; channel++) {
if (params.inputs[channel] != params.outputs[channel]) {
std::memcpy((char*)output_buffers[channel].data(),
(char*)input_buffers[channel].data(),
output_buffers[channel].size_bytes());
}
}
}
}
void CompressorCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("CompressorCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
for (s16 i = 0; i < parameter.channel_count; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n\toutputs: ";
for (s16 i = 0; i < parameter.channel_count; i++) {
string += fmt::format("{:02X}, ", outputs[i]);
}
string += "\n";
}
void CompressorCommand::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
for (s16 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
processor.sample_count);
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
processor.sample_count);
}
auto state_{reinterpret_cast<CompressorInfo::State*>(state)};
if (effect_enabled) {
if (parameter.state == CompressorInfo::ParameterState::Updating) {
SetCompressorEffectParameter(parameter, *state_);
} else if (parameter.state == CompressorInfo::ParameterState::Initialized) {
InitializeCompressorEffect(parameter, *state_);
}
}
ApplyCompressorEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
processor.sample_count);
}
bool CompressorCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/effect/compressor.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for limiting volume between a high and low threshold.
* Version 1.
*/
struct CompressorCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
/// Output mix buffer offsets for each channel
std::array<s16, MaxChannels> outputs;
/// Input parameters
CompressorInfo::ParameterVersion2 parameter;
/// State, updated each call
CpuAddr state;
/// Game-supplied workbuffer (Unused)
CpuAddr workbuffer;
/// Is this effect enabled?
bool effect_enabled;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,238 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/delay.h"
namespace AudioCore::AudioRenderer {
/**
* Update the DelayInfo state according to the given parameters.
*
* @param params - Input parameters to update the state.
* @param state - State to be updated.
*/
static void SetDelayEffectParameter(const DelayInfo::ParameterVersion1& params,
DelayInfo::State& state) {
auto channel_spread{params.channel_spread};
state.feedback_gain = params.feedback_gain * 0.97998046875f;
state.delay_feedback_gain = state.feedback_gain * (1.0f - channel_spread);
if (params.channel_count == 4 || params.channel_count == 6) {
channel_spread >>= 1;
}
state.delay_feedback_cross_gain = channel_spread * state.feedback_gain;
state.lowpass_feedback_gain = params.lowpass_amount * 0.949951171875f;
state.lowpass_gain = 1.0f - state.lowpass_feedback_gain;
}
/**
* Initialize a new DelayInfo state according to the given parameters.
*
* @param params - Input parameters to update the state.
* @param state - State to be updated.
* @param workbuffer - Game-supplied memory for the state. (Unused)
*/
static void InitializeDelayEffect(const DelayInfo::ParameterVersion1& params,
DelayInfo::State& state,
[[maybe_unused]] const CpuAddr workbuffer) {
state = {};
for (u32 channel = 0; channel < params.channel_count; channel++) {
Common::FixedPoint<32, 32> sample_count_max{0.064f};
sample_count_max *= params.sample_rate.to_int_floor() * params.delay_time_max;
Common::FixedPoint<18, 14> delay_time{params.delay_time};
delay_time *= params.sample_rate / 1000;
Common::FixedPoint<32, 32> sample_count{delay_time};
if (sample_count > sample_count_max) {
sample_count = sample_count_max;
}
state.delay_lines[channel].sample_count_max = sample_count_max.to_int_floor();
state.delay_lines[channel].sample_count = sample_count.to_int_floor();
state.delay_lines[channel].buffer.resize(state.delay_lines[channel].sample_count, 0);
if (state.delay_lines[channel].buffer.size() == 0) {
state.delay_lines[channel].buffer.push_back(0);
}
state.delay_lines[channel].buffer_pos = 0;
state.delay_lines[channel].decay_rate = 1.0f;
}
SetDelayEffectParameter(params, state);
}
/**
* Delay effect impl, according to the parameters and current state, on the input mix buffers,
* saving the results to the output mix buffers.
*
* @tparam NumChannels - Number of channels to process. 1-6.
* @param params - Input parameters to use.
* @param state - State to use, must be initialized (see InitializeDelayEffect).
* @param inputs - Input mix buffers to performan the delay on.
* @param outputs - Output mix buffers to receive the delayed samples.
* @param sample_count - Number of samples to process.
*/
template <size_t NumChannels>
static void ApplyDelay(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
std::vector<std::span<const s32>>& inputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
std::array<Common::FixedPoint<50, 14>, NumChannels> input_samples{};
for (u32 channel = 0; channel < NumChannels; channel++) {
input_samples[channel] = inputs[channel][sample_index] * 64;
}
std::array<Common::FixedPoint<50, 14>, NumChannels> delay_samples{};
for (u32 channel = 0; channel < NumChannels; channel++) {
delay_samples[channel] = state.delay_lines[channel].Read();
}
// clang-format off
std::array<std::array<Common::FixedPoint<18, 14>, NumChannels>, NumChannels> matrix{};
if constexpr (NumChannels == 1) {
matrix = {{
{state.feedback_gain},
}};
} else if constexpr (NumChannels == 2) {
matrix = {{
{state.delay_feedback_gain, state.delay_feedback_cross_gain},
{state.delay_feedback_cross_gain, state.delay_feedback_gain},
}};
} else if constexpr (NumChannels == 4) {
matrix = {{
{state.delay_feedback_gain, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, 0.0f},
{state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain},
{state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
{0.0f, state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain},
}};
} else if constexpr (NumChannels == 6) {
matrix = {{
{state.delay_feedback_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f, state.delay_feedback_cross_gain, 0.0f},
{0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain},
{state.delay_feedback_cross_gain, state.delay_feedback_cross_gain, state.delay_feedback_gain, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f, params.feedback_gain, 0.0f, 0.0f},
{state.delay_feedback_cross_gain, 0.0f, 0.0f, 0.0f, state.delay_feedback_gain, state.delay_feedback_cross_gain},
{0.0f, state.delay_feedback_cross_gain, 0.0f, 0.0f, state.delay_feedback_cross_gain, state.delay_feedback_gain},
}};
}
// clang-format on
std::array<Common::FixedPoint<50, 14>, NumChannels> gained_samples{};
for (u32 channel = 0; channel < NumChannels; channel++) {
Common::FixedPoint<50, 14> delay{};
for (u32 j = 0; j < NumChannels; j++) {
delay += delay_samples[j] * matrix[j][channel];
}
gained_samples[channel] = input_samples[channel] * params.in_gain + delay;
}
for (u32 channel = 0; channel < NumChannels; channel++) {
state.lowpass_z[channel] = gained_samples[channel] * state.lowpass_gain +
state.lowpass_z[channel] * state.lowpass_feedback_gain;
state.delay_lines[channel].Write(state.lowpass_z[channel]);
}
for (u32 channel = 0; channel < NumChannels; channel++) {
outputs[channel][sample_index] = (input_samples[channel] * params.dry_gain +
delay_samples[channel] * params.wet_gain)
.to_int_floor() /
64;
}
}
}
/**
* Apply a delay effect if enabled, according to the parameters and current state, on the input mix
* buffers, saving the results to the output mix buffers.
*
* @param params - Input parameters to use.
* @param state - State to use, must be initialized (see InitializeDelayEffect).
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
* @param inputs - Input mix buffers to performan the delay on.
* @param outputs - Output mix buffers to receive the delayed samples.
* @param sample_count - Number of samples to process.
*/
static void ApplyDelayEffect(const DelayInfo::ParameterVersion1& params, DelayInfo::State& state,
const bool enabled, std::vector<std::span<const s32>>& inputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
if (!IsChannelCountValid(params.channel_count)) {
LOG_ERROR(Service_Audio, "Invalid delay channels {}", params.channel_count);
return;
}
if (enabled) {
switch (params.channel_count) {
case 1:
ApplyDelay<1>(params, state, inputs, outputs, sample_count);
break;
case 2:
ApplyDelay<2>(params, state, inputs, outputs, sample_count);
break;
case 4:
ApplyDelay<4>(params, state, inputs, outputs, sample_count);
break;
case 6:
ApplyDelay<6>(params, state, inputs, outputs, sample_count);
break;
default:
for (u32 channel = 0; channel < params.channel_count; channel++) {
if (inputs[channel].data() != outputs[channel].data()) {
std::memcpy(outputs[channel].data(), inputs[channel].data(),
sample_count * sizeof(s32));
}
}
break;
}
} else {
for (u32 channel = 0; channel < params.channel_count; channel++) {
if (inputs[channel].data() != outputs[channel].data()) {
std::memcpy(outputs[channel].data(), inputs[channel].data(),
sample_count * sizeof(s32));
}
}
}
}
void DelayCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("DelayCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n\toutputs: ";
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", outputs[i]);
}
string += "\n";
}
void DelayCommand::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
for (s16 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
processor.sample_count);
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
processor.sample_count);
}
auto state_{reinterpret_cast<DelayInfo::State*>(state)};
if (effect_enabled) {
if (parameter.state == DelayInfo::ParameterState::Updating) {
SetDelayEffectParameter(parameter, *state_);
} else if (parameter.state == DelayInfo::ParameterState::Initialized) {
InitializeDelayEffect(parameter, *state_, workbuffer);
}
}
ApplyDelayEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
processor.sample_count);
}
bool DelayCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/effect/delay.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for a delay effect. Delays inputs mix buffers according to the parameters
* and state, outputs receives the delayed samples.
*/
struct DelayCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
/// Output mix buffer offsets for each channel
std::array<s16, MaxChannels> outputs;
/// Input parameters
DelayInfo::ParameterVersion1 parameter;
/// State, updated each call
CpuAddr state;
/// Game-supplied workbuffer (Unused)
CpuAddr workbuffer;
/// Is this effect enabled?
bool effect_enabled;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,437 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <numbers>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/i3dl2_reverb.h"
namespace AudioCore::AudioRenderer {
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MinDelayLineTimes{
5.0f,
6.0f,
13.0f,
14.0f,
};
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> MaxDelayLineTimes{
45.7042007446f,
82.7817001343f,
149.938293457f,
271.575805664f,
};
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay0MaxDelayLineTimes{17.0f, 13.0f,
9.0f, 7.0f};
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayLines> Decay1MaxDelayLineTimes{19.0f, 11.0f,
10.0f, 6.0f};
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyTapTimes{
0.0171360000968f,
0.0591540001333f,
0.161733001471f,
0.390186011791f,
0.425262004137f,
0.455410987139f,
0.689737021923f,
0.74590998888f,
0.833844006062f,
0.859502017498f,
0.0f,
0.0750240013003f,
0.168788000941f,
0.299901008606f,
0.337442994118f,
0.371903002262f,
0.599011003971f,
0.716741025448f,
0.817858994007f,
0.85166400671f,
};
constexpr std::array<f32, I3dl2ReverbInfo::MaxDelayTaps> EarlyGains{
0.67096f, 0.61027f, 1.0f, 0.3568f, 0.68361f, 0.65978f, 0.51939f,
0.24712f, 0.45945f, 0.45021f, 0.64196f, 0.54879f, 0.92925f, 0.3827f,
0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f};
/**
* Update the I3dl2ReverbInfo state according to the given parameters.
*
* @param params - Input parameters to update the state.
* @param state - State to be updated.
* @param reset - If enabled, the state buffers will be reset. Only set this on initialize.
*/
static void UpdateI3dl2ReverbEffectParameter(const I3dl2ReverbInfo::ParameterVersion1& params,
I3dl2ReverbInfo::State& state, const bool reset) {
const auto pow_10 = [](f32 val) -> f32 {
return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
};
const auto sin = [](f32 degrees) -> f32 {
return std::sin(degrees * std::numbers::pi_v<f32> / 180.0f);
};
const auto cos = [](f32 degrees) -> f32 {
return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
};
Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000.0f};
state.dry_gain = params.dry_gain;
Common::FixedPoint<50, 14> early_gain{
std::min(params.room_gain + params.reflection_gain, 5000.0f) / 2000.0f};
state.early_gain = pow_10(early_gain.to_float());
Common::FixedPoint<50, 14> late_gain{std::min(params.room_gain + params.reverb_gain, 5000.0f) /
2000.0f};
state.late_gain = pow_10(late_gain.to_float());
Common::FixedPoint<50, 14> hf_gain{pow_10(params.room_HF_gain / 2000.0f)};
if (hf_gain >= 1.0f) {
state.lowpass_1 = 0.0f;
state.lowpass_2 = 1.0f;
} else {
const auto reference_hf{(params.reference_HF * 256.0f) /
static_cast<f32>(params.sample_rate)};
const Common::FixedPoint<50, 14> a{1.0f - hf_gain.to_float()};
const Common::FixedPoint<50, 14> b{2.0f + (-cos(reference_hf) * (hf_gain * 2.0f))};
const Common::FixedPoint<50, 14> c{
std::sqrt(std::pow(b.to_float(), 2.0f) + (std::pow(a.to_float(), 2.0f) * -4.0f))};
state.lowpass_1 = std::min(((b - c) / (a * 2.0f)).to_float(), 0.99723f);
state.lowpass_2 = 1.0f - state.lowpass_1;
}
state.early_to_late_taps =
(((params.reflection_delay + params.late_reverb_delay_time) * 1000.0f) * delay).to_int();
state.last_reverb_echo = params.late_reverb_diffusion * 0.6f * 0.01f;
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
auto curr_delay{
((MinDelayLineTimes[i] + (params.late_reverb_density / 100.0f) *
(MaxDelayLineTimes[i] - MinDelayLineTimes[i])) *
delay)
.to_int()};
state.fdn_delay_lines[i].SetDelay(curr_delay);
const auto a{
(static_cast<f32>(state.fdn_delay_lines[i].delay + state.decay_delay_lines0[i].delay +
state.decay_delay_lines1[i].delay) *
-60.0f) /
(params.late_reverb_decay_time * static_cast<f32>(params.sample_rate))};
const auto b{a / params.late_reverb_HF_decay_ratio};
const auto c{
cos(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate)) /
sin(((params.reference_HF * 0.5f) * 128.0f) / static_cast<f32>(params.sample_rate))};
const auto d{pow_10((b - a) / 40.0f)};
const auto e{pow_10((b + a) / 40.0f) * 0.7071f};
state.lowpass_coeff[i][0] = ((c * d + 1.0f) * e) / (c + d);
state.lowpass_coeff[i][1] = ((1.0f - (c * d)) * e) / (c + d);
state.lowpass_coeff[i][2] = (c - d) / (c + d);
state.decay_delay_lines0[i].wet_gain = state.last_reverb_echo;
state.decay_delay_lines1[i].wet_gain = state.last_reverb_echo * -0.9f;
}
if (reset) {
state.shelf_filter.fill(0.0f);
state.lowpass_0 = 0.0f;
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
std::ranges::fill(state.decay_delay_lines0[i].buffer, 0);
std::ranges::fill(state.decay_delay_lines1[i].buffer, 0);
}
std::ranges::fill(state.center_delay_line.buffer, 0);
std::ranges::fill(state.early_delay_line.buffer, 0);
}
const auto reflection_time{(params.late_reverb_delay_time * 0.9998f + 0.02f) * 1000.0f};
const auto reflection_delay{params.reflection_delay * 1000.0f};
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayTaps; i++) {
auto length{((reflection_delay + reflection_time * EarlyTapTimes[i]) * delay).to_int()};
if (length >= state.early_delay_line.max_delay) {
length = state.early_delay_line.max_delay;
}
state.early_tap_steps[i] = length;
}
}
/**
* Initialize a new I3dl2ReverbInfo state according to the given parameters.
*
* @param params - Input parameters to update the state.
* @param state - State to be updated.
* @param workbuffer - Game-supplied memory for the state. (Unused)
*/
static void InitializeI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
I3dl2ReverbInfo::State& state, const CpuAddr workbuffer) {
state = {};
Common::FixedPoint<50, 14> delay{static_cast<f32>(params.sample_rate) / 1000};
for (u32 i = 0; i < I3dl2ReverbInfo::MaxDelayLines; i++) {
auto fdn_delay_time{(MaxDelayLineTimes[i] * delay).to_uint_floor()};
state.fdn_delay_lines[i].Initialize(fdn_delay_time);
auto decay0_delay_time{(Decay0MaxDelayLineTimes[i] * delay).to_uint_floor()};
state.decay_delay_lines0[i].Initialize(decay0_delay_time);
auto decay1_delay_time{(Decay1MaxDelayLineTimes[i] * delay).to_uint_floor()};
state.decay_delay_lines1[i].Initialize(decay1_delay_time);
}
const auto center_delay_time{(5 * delay).to_uint_floor()};
state.center_delay_line.Initialize(center_delay_time);
const auto early_delay_time{(400 * delay).to_uint_floor()};
state.early_delay_line.Initialize(early_delay_time);
UpdateI3dl2ReverbEffectParameter(params, state, true);
}
/**
* Pass-through the effect, copying input to output directly, with no reverb applied.
*
* @param inputs - Array of input mix buffers to copy.
* @param outputs - Array of output mix buffers to receive copy.
* @param channel_count - Number of channels in inputs and outputs.
* @param sample_count - Number of samples within each channel (unused).
*/
static void ApplyI3dl2ReverbEffectBypass(std::span<std::span<const s32>> inputs,
std::span<std::span<s32>> outputs, const u32 channel_count,
[[maybe_unused]] const u32 sample_count) {
for (u32 i = 0; i < channel_count; i++) {
if (inputs[i].data() != outputs[i].data()) {
std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
}
}
}
/**
* Tick the delay lines, reading and returning their current output, and writing a new decaying
* sample (mix).
*
* @param decay0 - The first decay line.
* @param decay1 - The second decay line.
* @param fdn - Feedback delay network.
* @param mix - The new calculated sample to be written and decayed.
* @return The next delayed and decayed sample.
*/
static Common::FixedPoint<50, 14> Axfx2AllPassTick(I3dl2ReverbInfo::I3dl2DelayLine& decay0,
I3dl2ReverbInfo::I3dl2DelayLine& decay1,
I3dl2ReverbInfo::I3dl2DelayLine& fdn,
const Common::FixedPoint<50, 14> mix) {
auto val{decay0.Read()};
auto mixed{mix - (val * decay0.wet_gain)};
auto out{decay0.Tick(mixed) + (mixed * decay0.wet_gain)};
val = decay1.Read();
mixed = out - (val * decay1.wet_gain);
out = decay1.Tick(mixed) + (mixed * decay1.wet_gain);
fdn.Tick(out);
return out;
}
/**
* Impl. Apply a I3DL2 reverb according to the current state, on the input mix buffers,
* saving the results to the output mix buffers.
*
* @tparam NumChannels - Number of channels to process. 1-6.
Inputs/outputs should have this many buffers.
* @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
* @param inputs - Input mix buffers to perform the reverb on.
* @param outputs - Output mix buffers to receive the reverbed samples.
* @param sample_count - Number of samples to process.
*/
template <size_t NumChannels>
static void ApplyI3dl2ReverbEffect(I3dl2ReverbInfo::State& state,
std::span<std::span<const s32>> inputs,
std::span<std::span<s32>> outputs, const u32 sample_count) {
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
};
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3,
};
constexpr std::array<u8, I3dl2ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
2, 0, 0, 1, 1, 1, 1, 4, 4, 4, 1, 1, 1, 0, 0, 0, 0, 5, 5, 5,
};
std::span<const u8> tap_indexes{};
if constexpr (NumChannels == 1) {
tap_indexes = OutTapIndexes1Ch;
} else if constexpr (NumChannels == 2) {
tap_indexes = OutTapIndexes2Ch;
} else if constexpr (NumChannels == 4) {
tap_indexes = OutTapIndexes4Ch;
} else if constexpr (NumChannels == 6) {
tap_indexes = OutTapIndexes6Ch;
}
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
Common::FixedPoint<50, 14> early_to_late_tap{
state.early_delay_line.TapOut(state.early_to_late_taps)};
std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
for (u32 early_tap = 0; early_tap < I3dl2ReverbInfo::MaxDelayTaps; early_tap++) {
output_samples[tap_indexes[early_tap]] +=
state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
EarlyGains[early_tap];
if constexpr (NumChannels == 6) {
output_samples[static_cast<u32>(Channels::LFE)] +=
state.early_delay_line.TapOut(state.early_tap_steps[early_tap]) *
EarlyGains[early_tap];
}
}
Common::FixedPoint<50, 14> current_sample{};
for (u32 channel = 0; channel < NumChannels; channel++) {
current_sample += inputs[channel][sample_index];
}
state.lowpass_0 =
(current_sample * state.lowpass_2 + state.lowpass_0 * state.lowpass_1).to_float();
state.early_delay_line.Tick(state.lowpass_0);
for (u32 channel = 0; channel < NumChannels; channel++) {
output_samples[channel] *= state.early_gain;
}
std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> filtered_samples{};
for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
filtered_samples[delay_line] =
state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][0] +
state.shelf_filter[delay_line];
state.shelf_filter[delay_line] =
(filtered_samples[delay_line] * state.lowpass_coeff[delay_line][2] +
state.fdn_delay_lines[delay_line].Read() * state.lowpass_coeff[delay_line][1])
.to_float();
}
const std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> mix_matrix{
filtered_samples[1] + filtered_samples[2] + early_to_late_tap * state.late_gain,
-filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
filtered_samples[0] - filtered_samples[3] + early_to_late_tap * state.late_gain,
filtered_samples[1] - filtered_samples[2] + early_to_late_tap * state.late_gain,
};
std::array<Common::FixedPoint<50, 14>, I3dl2ReverbInfo::MaxDelayLines> allpass_samples{};
for (u32 delay_line = 0; delay_line < I3dl2ReverbInfo::MaxDelayLines; delay_line++) {
allpass_samples[delay_line] = Axfx2AllPassTick(
state.decay_delay_lines0[delay_line], state.decay_delay_lines1[delay_line],
state.fdn_delay_lines[delay_line], mix_matrix[delay_line]);
}
if constexpr (NumChannels == 6) {
const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
allpass_samples[3], allpass_samples[2], allpass_samples[3],
};
for (u32 channel = 0; channel < NumChannels; channel++) {
Common::FixedPoint<50, 14> allpass{};
if (channel == static_cast<u32>(Channels::Center)) {
allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
} else {
allpass = allpass_outputs[channel];
}
auto out_sample{output_samples[channel] + allpass +
state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
outputs[channel][sample_index] =
static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
}
} else {
for (u32 channel = 0; channel < NumChannels; channel++) {
auto out_sample{output_samples[channel] + allpass_samples[channel] +
state.dry_gain * static_cast<f32>(inputs[channel][sample_index])};
outputs[channel][sample_index] =
static_cast<s32>(std::clamp(out_sample.to_float(), -8388600.0f, 8388600.0f));
}
}
}
}
/**
* Apply a I3DL2 reverb if enabled, according to the current state, on the input mix buffers,
* saving the results to the output mix buffers.
*
* @param params - Input parameters to use.
* @param state - State to use, must be initialized (see InitializeI3dl2ReverbEffect).
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
* @param inputs - Input mix buffers to performan the delay on.
* @param outputs - Output mix buffers to receive the delayed samples.
* @param sample_count - Number of samples to process.
*/
static void ApplyI3dl2ReverbEffect(const I3dl2ReverbInfo::ParameterVersion1& params,
I3dl2ReverbInfo::State& state, const bool enabled,
std::span<std::span<const s32>> inputs,
std::span<std::span<s32>> outputs, const u32 sample_count) {
if (enabled) {
switch (params.channel_count) {
case 0:
return;
case 1:
ApplyI3dl2ReverbEffect<1>(state, inputs, outputs, sample_count);
break;
case 2:
ApplyI3dl2ReverbEffect<2>(state, inputs, outputs, sample_count);
break;
case 4:
ApplyI3dl2ReverbEffect<4>(state, inputs, outputs, sample_count);
break;
case 6:
ApplyI3dl2ReverbEffect<6>(state, inputs, outputs, sample_count);
break;
default:
ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
break;
}
} else {
ApplyI3dl2ReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
}
}
void I3dl2ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("I3dl2ReverbCommand\n\tenabled {} \n\tinputs: ", effect_enabled);
for (u32 i = 0; i < parameter.channel_count; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n\toutputs: ";
for (u32 i = 0; i < parameter.channel_count; i++) {
string += fmt::format("{:02X}, ", outputs[i]);
}
string += "\n";
}
void I3dl2ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
for (u32 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
processor.sample_count);
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
processor.sample_count);
}
auto state_{reinterpret_cast<I3dl2ReverbInfo::State*>(state)};
if (effect_enabled) {
if (parameter.state == I3dl2ReverbInfo::ParameterState::Updating) {
UpdateI3dl2ReverbEffectParameter(parameter, *state_, false);
} else if (parameter.state == I3dl2ReverbInfo::ParameterState::Initialized) {
InitializeI3dl2ReverbEffect(parameter, *state_, workbuffer);
}
}
ApplyI3dl2ReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
processor.sample_count);
}
bool I3dl2ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/effect/i3dl2.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for a I3DL2Reverb effect. Apply a reverb to inputs mix buffer according to
* the I3DL2 spec, outputs receives the results.
*/
struct I3dl2ReverbCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
/// Output mix buffer offsets for each channel
std::array<s16, MaxChannels> outputs;
/// Input parameters
I3dl2ReverbInfo::ParameterVersion1 parameter;
/// State, updated each call
CpuAddr state;
/// Game-supplied workbuffer (Unused)
CpuAddr workbuffer;
/// Is this effect enabled?
bool effect_enabled;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,222 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/light_limiter.h"
namespace AudioCore::AudioRenderer {
/**
* Update the LightLimiterInfo state according to the given parameters.
* A no-op.
*
* @param params - Input parameters to update the state.
* @param state - State to be updated.
*/
static void UpdateLightLimiterEffectParameter(const LightLimiterInfo::ParameterVersion2& params,
LightLimiterInfo::State& state) {}
/**
* Initialize a new LightLimiterInfo state according to the given parameters.
*
* @param params - Input parameters to update the state.
* @param state - State to be updated.
* @param workbuffer - Game-supplied memory for the state. (Unused)
*/
static void InitializeLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
LightLimiterInfo::State& state, const CpuAddr workbuffer) {
state = {};
state.samples_average.fill(0.0f);
state.compression_gain.fill(1.0f);
state.look_ahead_sample_offsets.fill(0);
for (u32 i = 0; i < params.channel_count; i++) {
state.look_ahead_sample_buffers[i].resize(params.look_ahead_samples_max, 0.0f);
}
}
/**
* Apply a light limiter effect if enabled, according to the current state, on the input mix
* buffers, saving the results to the output mix buffers.
*
* @param params - Input parameters to use.
* @param state - State to use, must be initialized (see InitializeLightLimiterEffect).
* @param enabled - If enabled, limiter will be applied, otherwise input is copied to output.
* @param inputs - Input mix buffers to perform the limiter on.
* @param outputs - Output mix buffers to receive the limited samples.
* @param sample_count - Number of samples to process.
* @params statistics - Optional output statistics, only used with version 2.
*/
static void ApplyLightLimiterEffect(const LightLimiterInfo::ParameterVersion2& params,
LightLimiterInfo::State& state, const bool enabled,
std::vector<std::span<const s32>>& inputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count,
LightLimiterInfo::StatisticsInternal* statistics) {
constexpr s64 min{std::numeric_limits<s32>::min()};
constexpr s64 max{std::numeric_limits<s32>::max()};
const auto recip_estimate = [](f64 a) -> f64 {
s32 q, s;
f64 r;
q = (s32)(a * 512.0); /* a in units of 1/512 rounded down */
r = 1.0 / (((f64)q + 0.5) / 512.0); /* reciprocal r */
s = (s32)(256.0 * r + 0.5); /* r in units of 1/256 rounded to nearest */
return ((f64)s / 256.0);
};
if (enabled) {
if (statistics && params.statistics_reset_required) {
for (u32 i = 0; i < params.channel_count; i++) {
statistics->channel_compression_gain_min[i] = 1.0f;
statistics->channel_max_sample[i] = 0;
}
}
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
for (u32 channel = 0; channel < params.channel_count; channel++) {
auto sample{(Common::FixedPoint<49, 15>(inputs[channel][sample_index]) /
Common::FixedPoint<49, 15>::one) *
params.input_gain};
auto abs_sample{sample};
if (sample < 0.0f) {
abs_sample = -sample;
}
auto coeff{abs_sample > state.samples_average[channel] ? params.attack_coeff
: params.release_coeff};
state.samples_average[channel] +=
((abs_sample - state.samples_average[channel]) * coeff).to_float();
// Reciprocal estimate
auto new_average_sample{Common::FixedPoint<49, 15>(
recip_estimate(state.samples_average[channel].to_double()))};
if (params.processing_mode != LightLimiterInfo::ProcessingMode::Mode1) {
// Two Newton-Raphson steps
auto temp{2.0 - (state.samples_average[channel] * new_average_sample)};
new_average_sample = 2.0 - (state.samples_average[channel] * temp);
}
auto above_threshold{state.samples_average[channel] > params.threshold};
auto attenuation{above_threshold ? params.threshold * new_average_sample : 1.0f};
coeff = attenuation < state.compression_gain[channel] ? params.attack_coeff
: params.release_coeff;
state.compression_gain[channel] +=
(attenuation - state.compression_gain[channel]) * coeff;
auto lookahead_sample{
state.look_ahead_sample_buffers[channel]
[state.look_ahead_sample_offsets[channel]]};
state.look_ahead_sample_buffers[channel][state.look_ahead_sample_offsets[channel]] =
sample;
state.look_ahead_sample_offsets[channel] =
(state.look_ahead_sample_offsets[channel] + 1) % params.look_ahead_samples_min;
outputs[channel][sample_index] = static_cast<s32>(
std::clamp((lookahead_sample * state.compression_gain[channel] *
params.output_gain * Common::FixedPoint<49, 15>::one)
.to_long(),
min, max));
if (statistics) {
statistics->channel_max_sample[channel] =
std::max(statistics->channel_max_sample[channel], abs_sample.to_float());
statistics->channel_compression_gain_min[channel] =
std::min(statistics->channel_compression_gain_min[channel],
state.compression_gain[channel].to_float());
}
}
}
} else {
for (u32 i = 0; i < params.channel_count; i++) {
if (params.inputs[i] != params.outputs[i]) {
std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
}
}
}
}
void LightLimiterVersion1Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("LightLimiterVersion1Command\n\tinputs: ");
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n\toutputs: ";
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", outputs[i]);
}
string += "\n";
}
void LightLimiterVersion1Command::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
for (u32 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
processor.sample_count);
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
processor.sample_count);
}
auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
if (effect_enabled) {
if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
UpdateLightLimiterEffectParameter(parameter, *state_);
} else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
InitializeLightLimiterEffect(parameter, *state_, workbuffer);
}
}
LightLimiterInfo::StatisticsInternal* statistics{nullptr};
ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
processor.sample_count, statistics);
}
bool LightLimiterVersion1Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
void LightLimiterVersion2Command::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("LightLimiterVersion2Command\n\tinputs: \n");
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n\toutputs: ";
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", outputs[i]);
}
string += "\n";
}
void LightLimiterVersion2Command::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
for (u32 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
processor.sample_count);
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
processor.sample_count);
}
auto state_{reinterpret_cast<LightLimiterInfo::State*>(state)};
if (effect_enabled) {
if (parameter.state == LightLimiterInfo::ParameterState::Updating) {
UpdateLightLimiterEffectParameter(parameter, *state_);
} else if (parameter.state == LightLimiterInfo::ParameterState::Initialized) {
InitializeLightLimiterEffect(parameter, *state_, workbuffer);
}
}
auto statistics{reinterpret_cast<LightLimiterInfo::StatisticsInternal*>(result_state)};
ApplyLightLimiterEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
processor.sample_count, statistics);
}
bool LightLimiterVersion2Command::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,103 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/effect/light_limiter.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for limiting volume between a high and low threshold.
* Version 1.
*/
struct LightLimiterVersion1Command : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
/// Output mix buffer offsets for each channel
std::array<s16, MaxChannels> outputs;
/// Input parameters
LightLimiterInfo::ParameterVersion2 parameter;
/// State, updated each call
CpuAddr state;
/// Game-supplied workbuffer (Unused)
CpuAddr workbuffer;
/// Is this effect enabled?
bool effect_enabled;
};
/**
* AudioRenderer command for limiting volume between a high and low threshold.
* Version 2 with output statistics.
*/
struct LightLimiterVersion2Command : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
/// Output mix buffer offsets for each channel
std::array<s16, MaxChannels> outputs;
/// Input parameters
LightLimiterInfo::ParameterVersion2 parameter;
/// State, updated each call
CpuAddr state;
/// Game-supplied workbuffer (Unused)
CpuAddr workbuffer;
/// Optional statistics, sent back to the sysmodule
CpuAddr result_state;
/// Is this effect enabled?
bool effect_enabled;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/biquad_filter.h"
#include "audio_core/renderer/command/effect/multi_tap_biquad_filter.h"
namespace AudioCore::AudioRenderer {
void MultiTapBiquadFilterCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format(
"MultiTapBiquadFilterCommand\n\tinput {:02X}\n\toutput {:02X}\n\tneeds_init ({}, {})\n",
input, output, needs_init[0], needs_init[1]);
}
void MultiTapBiquadFilterCommand::Process(const ADSP::CommandListProcessor& processor) {
if (filter_tap_count > MaxBiquadFilters) {
LOG_ERROR(Service_Audio, "Too many filter taps! {}", filter_tap_count);
filter_tap_count = MaxBiquadFilters;
}
auto input_buffer{
processor.mix_buffers.subspan(input * processor.sample_count, processor.sample_count)};
auto output_buffer{
processor.mix_buffers.subspan(output * processor.sample_count, processor.sample_count)};
// TODO: Fix this, currently just applies the filter to the input twice,
// and doesn't chain the biquads together at all.
for (u32 i = 0; i < filter_tap_count; i++) {
auto state{reinterpret_cast<VoiceState::BiquadFilterState*>(states[i])};
if (needs_init[i]) {
std::memset(state, 0, sizeof(VoiceState::BiquadFilterState));
}
ApplyBiquadFilterFloat(output_buffer, input_buffer, biquads[i].b, biquads[i].a, *state,
processor.sample_count);
}
}
bool MultiTapBiquadFilterCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/voice/voice_info.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for applying multiple biquads at once.
*/
struct MultiTapBiquadFilterCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input;
/// Output mix buffer index
s16 output;
/// Biquad parameters
std::array<VoiceInfo::BiquadFilterParameter, MaxBiquadFilters> biquads;
/// Biquad states, updated each call
std::array<CpuAddr, MaxBiquadFilters> states;
/// If each biquad needs initialisation
std::array<bool, MaxBiquadFilters> needs_init;
/// Number of active biquads
u8 filter_tap_count;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,440 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <numbers>
#include <ranges>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/effect/reverb.h"
namespace AudioCore::AudioRenderer {
constexpr std::array<f32, ReverbInfo::MaxDelayLines> FdnMaxDelayLineTimes = {
53.9532470703125f,
79.19256591796875f,
116.23876953125f,
170.61529541015625f,
};
constexpr std::array<f32, ReverbInfo::MaxDelayLines> DecayMaxDelayLineTimes = {
7.0f,
9.0f,
13.0f,
17.0f,
};
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps + 1>, ReverbInfo::NumEarlyModes>
EarlyDelayTimes = {
{{0.000000f, 3.500000f, 2.799988f, 3.899963f, 2.699951f, 13.399963f, 7.899963f, 8.399963f,
9.899963f, 12.000000f, 12.500000f},
{0.000000f, 11.799988f, 5.500000f, 11.199951f, 10.399963f, 38.099976f, 22.199951f,
29.599976f, 21.199951f, 24.799988f, 40.000000f},
{0.000000f, 41.500000f, 20.500000f, 41.299988f, 0.000000f, 29.500000f, 33.799988f,
45.199951f, 46.799988f, 0.000000f, 50.000000f},
{33.099976f, 43.299988f, 22.799988f, 37.899963f, 14.899963f, 35.299988f, 17.899963f,
34.199951f, 0.000000f, 43.299988f, 50.000000f},
{0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
0.000000f, 0.000000f, 0.000000f}},
};
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayTaps>, ReverbInfo::NumEarlyModes>
EarlyDelayGains = {{
{0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f,
0.679993f, 0.679993f},
{0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.699951f, 0.679993f, 0.679993f, 0.679993f,
0.679993f, 0.679993f},
{0.500000f, 0.699951f, 0.699951f, 0.679993f, 0.500000f, 0.679993f, 0.679993f, 0.699951f,
0.679993f, 0.000000f},
{0.929993f, 0.919983f, 0.869995f, 0.859985f, 0.939941f, 0.809998f, 0.799988f, 0.769958f,
0.759949f, 0.649963f},
{0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f, 0.000000f,
0.000000f, 0.000000f},
}};
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
FdnDelayTimes = {{
{53.953247f, 79.192566f, 116.238770f, 130.615295f},
{53.953247f, 79.192566f, 116.238770f, 170.615295f},
{5.000000f, 10.000000f, 5.000000f, 10.000000f},
{47.029968f, 71.000000f, 103.000000f, 170.000000f},
{53.953247f, 79.192566f, 116.238770f, 170.615295f},
}};
constexpr std::array<std::array<f32, ReverbInfo::MaxDelayLines>, ReverbInfo::NumLateModes>
DecayDelayTimes = {{
{7.000000f, 9.000000f, 13.000000f, 17.000000f},
{7.000000f, 9.000000f, 13.000000f, 17.000000f},
{1.000000f, 1.000000f, 1.000000f, 1.000000f},
{7.000000f, 7.000000f, 13.000000f, 9.000000f},
{7.000000f, 9.000000f, 13.000000f, 17.000000f},
}};
/**
* Update the ReverbInfo state according to the given parameters.
*
* @param params - Input parameters to update the state.
* @param state - State to be updated.
*/
static void UpdateReverbEffectParameter(const ReverbInfo::ParameterVersion2& params,
ReverbInfo::State& state) {
const auto pow_10 = [](f32 val) -> f32 {
return (val >= 0.0f) ? 1.0f : (val <= -5.3f) ? 0.0f : std::pow(10.0f, val);
};
const auto cos = [](f32 degrees) -> f32 {
return std::cos(degrees * std::numbers::pi_v<f32> / 180.0f);
};
static bool unk_initialized{false};
static Common::FixedPoint<50, 14> unk_value{};
const auto sample_rate{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
const auto pre_delay_time{Common::FixedPoint<50, 14>::from_base(params.pre_delay)};
for (u32 i = 0; i < ReverbInfo::MaxDelayTaps; i++) {
auto early_delay{
((pre_delay_time + EarlyDelayTimes[params.early_mode][i]) * sample_rate).to_int()};
early_delay = std::min(early_delay, state.pre_delay_line.sample_count_max);
state.early_delay_times[i] = early_delay + 1;
state.early_gains[i] = Common::FixedPoint<50, 14>::from_base(params.early_gain) *
EarlyDelayGains[params.early_mode][i];
}
if (params.channel_count == 2) {
state.early_gains[4] * 0.5f;
state.early_gains[5] * 0.5f;
}
auto pre_time{
((pre_delay_time + EarlyDelayTimes[params.early_mode][10]) * sample_rate).to_int()};
state.pre_delay_time = std::min(pre_time, state.pre_delay_line.sample_count_max);
if (!unk_initialized) {
unk_value = cos((1280.0f / sample_rate).to_float());
unk_initialized = true;
}
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
const auto fdn_delay{(FdnDelayTimes[params.late_mode][i] * sample_rate).to_int()};
state.fdn_delay_lines[i].sample_count =
std::min(fdn_delay, state.fdn_delay_lines[i].sample_count_max);
state.fdn_delay_lines[i].buffer_end =
&state.fdn_delay_lines[i].buffer[state.fdn_delay_lines[i].sample_count - 1];
const auto decay_delay{(DecayDelayTimes[params.late_mode][i] * sample_rate).to_int()};
state.decay_delay_lines[i].sample_count =
std::min(decay_delay, state.decay_delay_lines[i].sample_count_max);
state.decay_delay_lines[i].buffer_end =
&state.decay_delay_lines[i].buffer[state.decay_delay_lines[i].sample_count - 1];
state.decay_delay_lines[i].decay =
0.5999755859375f * (1.0f - Common::FixedPoint<50, 14>::from_base(params.colouration));
auto a{(Common::FixedPoint<50, 14>(state.fdn_delay_lines[i].sample_count_max) +
state.decay_delay_lines[i].sample_count_max) *
-3};
auto b{a / (Common::FixedPoint<50, 14>::from_base(params.decay_time) * sample_rate)};
Common::FixedPoint<50, 14> c{0.0f};
Common::FixedPoint<50, 14> d{0.0f};
auto hf_decay_ratio{Common::FixedPoint<50, 14>::from_base(params.high_freq_decay_ratio)};
if (hf_decay_ratio > 0.99493408203125f) {
c = 0.0f;
d = 1.0f;
} else {
const auto e{
pow_10(((((1.0f / hf_decay_ratio) - 1.0f) * 2) / 100 * (b / 10)).to_float())};
const auto f{1.0f - e};
const auto g{2.0f - (unk_value * e * 2)};
const auto h{std::sqrt(std::pow(g.to_float(), 2.0f) - (std::pow(f, 2.0f) * 4))};
c = (g - h) / (f * 2.0f);
d = 1.0f - c;
}
state.hf_decay_prev_gain[i] = c;
state.hf_decay_gain[i] = pow_10((b / 1000).to_float()) * d * 0.70709228515625f;
state.prev_feedback_output[i] = 0;
}
}
/**
* Initialize a new ReverbInfo state according to the given parameters.
*
* @param params - Input parameters to update the state.
* @param state - State to be updated.
* @param workbuffer - Game-supplied memory for the state. (Unused)
* @param long_size_pre_delay_supported - Use a longer pre-delay time before reverb begins.
*/
static void InitializeReverbEffect(const ReverbInfo::ParameterVersion2& params,
ReverbInfo::State& state, const CpuAddr workbuffer,
const bool long_size_pre_delay_supported) {
state = {};
auto delay{Common::FixedPoint<50, 14>::from_base(params.sample_rate)};
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
auto fdn_delay_time{(FdnMaxDelayLineTimes[i] * delay).to_uint_floor()};
state.fdn_delay_lines[i].Initialize(fdn_delay_time, 1.0f);
auto decay_delay_time{(DecayMaxDelayLineTimes[i] * delay).to_uint_floor()};
state.decay_delay_lines[i].Initialize(decay_delay_time, 0.0f);
}
const auto pre_delay{long_size_pre_delay_supported ? 350.0f : 150.0f};
const auto pre_delay_line{(pre_delay * delay).to_uint_floor()};
state.pre_delay_line.Initialize(pre_delay_line, 1.0f);
const auto center_delay_time{(5 * delay).to_uint_floor()};
state.center_delay_line.Initialize(center_delay_time, 1.0f);
UpdateReverbEffectParameter(params, state);
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
std::ranges::fill(state.fdn_delay_lines[i].buffer, 0);
std::ranges::fill(state.decay_delay_lines[i].buffer, 0);
}
std::ranges::fill(state.center_delay_line.buffer, 0);
std::ranges::fill(state.pre_delay_line.buffer, 0);
}
/**
* Pass-through the effect, copying input to output directly, with no reverb applied.
*
* @param inputs - Array of input mix buffers to copy.
* @param outputs - Array of output mix buffers to receive copy.
* @param channel_count - Number of channels in inputs and outputs.
* @param sample_count - Number of samples within each channel.
*/
static void ApplyReverbEffectBypass(std::span<std::span<const s32>> inputs,
std::span<std::span<s32>> outputs, const u32 channel_count,
const u32 sample_count) {
for (u32 i = 0; i < channel_count; i++) {
if (inputs[i].data() != outputs[i].data()) {
std::memcpy(outputs[i].data(), inputs[i].data(), outputs[i].size_bytes());
}
}
}
/**
* Tick the delay lines, reading and returning their current output, and writing a new decaying
* sample (mix).
*
* @param decay - The decay line.
* @param fdn - Feedback delay network.
* @param mix - The new calculated sample to be written and decayed.
* @return The next delayed and decayed sample.
*/
static Common::FixedPoint<50, 14> Axfx2AllPassTick(ReverbInfo::ReverbDelayLine& decay,
ReverbInfo::ReverbDelayLine& fdn,
const Common::FixedPoint<50, 14> mix) {
const auto val{decay.Read()};
const auto mixed{mix - (val * decay.decay)};
const auto out{decay.Tick(mixed) + (mixed * decay.decay)};
fdn.Tick(out);
return out;
}
/**
* Impl. Apply a Reverb according to the current state, on the input mix buffers,
* saving the results to the output mix buffers.
*
* @tparam NumChannels - Number of channels to process. 1-6.
Inputs/outputs should have this many buffers.
* @param params - Input parameters to update the state.
* @param state - State to use, must be initialized (see InitializeReverbEffect).
* @param inputs - Input mix buffers to perform the reverb on.
* @param outputs - Output mix buffers to receive the reverbed samples.
* @param sample_count - Number of samples to process.
*/
template <size_t NumChannels>
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
std::vector<std::span<const s32>>& inputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes1Ch{
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes2Ch{
0, 0, 1, 1, 0, 1, 0, 0, 1, 1,
};
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes4Ch{
0, 0, 1, 1, 0, 1, 2, 2, 3, 3,
};
constexpr std::array<u8, ReverbInfo::MaxDelayTaps> OutTapIndexes6Ch{
0, 0, 1, 1, 2, 2, 4, 4, 5, 5,
};
std::span<const u8> tap_indexes{};
if constexpr (NumChannels == 1) {
tap_indexes = OutTapIndexes1Ch;
} else if constexpr (NumChannels == 2) {
tap_indexes = OutTapIndexes2Ch;
} else if constexpr (NumChannels == 4) {
tap_indexes = OutTapIndexes4Ch;
} else if constexpr (NumChannels == 6) {
tap_indexes = OutTapIndexes6Ch;
}
for (u32 sample_index = 0; sample_index < sample_count; sample_index++) {
std::array<Common::FixedPoint<50, 14>, NumChannels> output_samples{};
for (u32 early_tap = 0; early_tap < ReverbInfo::MaxDelayTaps; early_tap++) {
const auto sample{state.pre_delay_line.TapOut(state.early_delay_times[early_tap]) *
state.early_gains[early_tap]};
output_samples[tap_indexes[early_tap]] += sample;
if constexpr (NumChannels == 6) {
output_samples[static_cast<u32>(Channels::LFE)] += sample;
}
}
if constexpr (NumChannels == 6) {
output_samples[static_cast<u32>(Channels::LFE)] *= 0.2f;
}
Common::FixedPoint<50, 14> input_sample{};
for (u32 channel = 0; channel < NumChannels; channel++) {
input_sample += inputs[channel][sample_index];
}
input_sample *= 64;
input_sample *= Common::FixedPoint<50, 14>::from_base(params.base_gain);
state.pre_delay_line.Write(input_sample);
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
state.prev_feedback_output[i] =
state.prev_feedback_output[i] * state.hf_decay_prev_gain[i] +
state.fdn_delay_lines[i].Read() * state.hf_decay_gain[i];
}
Common::FixedPoint<50, 14> pre_delay_sample{
state.pre_delay_line.Read() * Common::FixedPoint<50, 14>::from_base(params.late_gain)};
std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> mix_matrix{
state.prev_feedback_output[2] + state.prev_feedback_output[1] + pre_delay_sample,
-state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
state.prev_feedback_output[0] - state.prev_feedback_output[3] + pre_delay_sample,
state.prev_feedback_output[1] - state.prev_feedback_output[2] + pre_delay_sample,
};
std::array<Common::FixedPoint<50, 14>, ReverbInfo::MaxDelayLines> allpass_samples{};
for (u32 i = 0; i < ReverbInfo::MaxDelayLines; i++) {
allpass_samples[i] = Axfx2AllPassTick(state.decay_delay_lines[i],
state.fdn_delay_lines[i], mix_matrix[i]);
}
const auto dry_gain{Common::FixedPoint<50, 14>::from_base(params.dry_gain)};
const auto wet_gain{Common::FixedPoint<50, 14>::from_base(params.wet_gain)};
if constexpr (NumChannels == 6) {
const std::array<Common::FixedPoint<50, 14>, MaxChannels> allpass_outputs{
allpass_samples[0], allpass_samples[1], allpass_samples[2] - allpass_samples[3],
allpass_samples[3], allpass_samples[2], allpass_samples[3],
};
for (u32 channel = 0; channel < NumChannels; channel++) {
auto in_sample{inputs[channel][sample_index] * dry_gain};
Common::FixedPoint<50, 14> allpass{};
if (channel == static_cast<u32>(Channels::Center)) {
allpass = state.center_delay_line.Tick(allpass_outputs[channel] * 0.5f);
} else {
allpass = allpass_outputs[channel];
}
auto out_sample{((output_samples[channel] + allpass) * wet_gain) / 64};
outputs[channel][sample_index] = (in_sample + out_sample).to_int();
}
} else {
for (u32 channel = 0; channel < NumChannels; channel++) {
auto in_sample{inputs[channel][sample_index] * dry_gain};
auto out_sample{((output_samples[channel] + allpass_samples[channel]) * wet_gain) /
64};
outputs[channel][sample_index] = (in_sample + out_sample).to_int();
}
}
}
}
/**
* Apply a Reverb if enabled, according to the current state, on the input mix buffers,
* saving the results to the output mix buffers.
*
* @param params - Input parameters to use.
* @param state - State to use, must be initialized (see InitializeReverbEffect).
* @param enabled - If enabled, delay will be applied, otherwise input is copied to output.
* @param inputs - Input mix buffers to performan the reverb on.
* @param outputs - Output mix buffers to receive the reverbed samples.
* @param sample_count - Number of samples to process.
*/
static void ApplyReverbEffect(const ReverbInfo::ParameterVersion2& params, ReverbInfo::State& state,
const bool enabled, std::vector<std::span<const s32>>& inputs,
std::vector<std::span<s32>>& outputs, const u32 sample_count) {
if (enabled) {
switch (params.channel_count) {
case 0:
return;
case 1:
ApplyReverbEffect<1>(params, state, inputs, outputs, sample_count);
break;
case 2:
ApplyReverbEffect<2>(params, state, inputs, outputs, sample_count);
break;
case 4:
ApplyReverbEffect<4>(params, state, inputs, outputs, sample_count);
break;
case 6:
ApplyReverbEffect<6>(params, state, inputs, outputs, sample_count);
break;
default:
ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
break;
}
} else {
ApplyReverbEffectBypass(inputs, outputs, params.channel_count, sample_count);
}
}
void ReverbCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format(
"ReverbCommand\n\tenabled {} long_size_pre_delay_supported {}\n\tinputs: ", effect_enabled,
long_size_pre_delay_supported);
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n\toutputs: ";
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", outputs[i]);
}
string += "\n";
}
void ReverbCommand::Process(const ADSP::CommandListProcessor& processor) {
std::vector<std::span<const s32>> input_buffers(parameter.channel_count);
std::vector<std::span<s32>> output_buffers(parameter.channel_count);
for (u32 i = 0; i < parameter.channel_count; i++) {
input_buffers[i] = processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
processor.sample_count);
output_buffers[i] = processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
processor.sample_count);
}
auto state_{reinterpret_cast<ReverbInfo::State*>(state)};
if (effect_enabled) {
if (parameter.state == ReverbInfo::ParameterState::Updating) {
UpdateReverbEffectParameter(parameter, *state_);
} else if (parameter.state == ReverbInfo::ParameterState::Initialized) {
InitializeReverbEffect(parameter, *state_, workbuffer, long_size_pre_delay_supported);
}
}
ApplyReverbEffect(parameter, *state_, effect_enabled, input_buffers, output_buffers,
processor.sample_count);
}
bool ReverbCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/effect/reverb.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for a Reverb effect. Apply a reverb to inputs mix buffer, outputs receives
* the results.
*/
struct ReverbCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
/// Output mix buffer offsets for each channel
std::array<s16, MaxChannels> outputs;
/// Input parameters
ReverbInfo::ParameterVersion2 parameter;
/// State, updated each call
CpuAddr state;
/// Game-supplied workbuffer (Unused)
CpuAddr workbuffer;
/// Is this effect enabled?
bool effect_enabled;
/// Is a longer pre-delay time supported?
bool long_size_pre_delay_supported;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,93 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "audio_core/common/common.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
enum class CommandId : u8 {
/* 0x00 */ Invalid,
/* 0x01 */ DataSourcePcmInt16Version1,
/* 0x02 */ DataSourcePcmInt16Version2,
/* 0x03 */ DataSourcePcmFloatVersion1,
/* 0x04 */ DataSourcePcmFloatVersion2,
/* 0x05 */ DataSourceAdpcmVersion1,
/* 0x06 */ DataSourceAdpcmVersion2,
/* 0x07 */ Volume,
/* 0x08 */ VolumeRamp,
/* 0x09 */ BiquadFilter,
/* 0x0A */ Mix,
/* 0x0B */ MixRamp,
/* 0x0C */ MixRampGrouped,
/* 0x0D */ DepopPrepare,
/* 0x0E */ DepopForMixBuffers,
/* 0x0F */ Delay,
/* 0x10 */ Upsample,
/* 0x11 */ DownMix6chTo2ch,
/* 0x12 */ Aux,
/* 0x13 */ DeviceSink,
/* 0x14 */ CircularBufferSink,
/* 0x15 */ Reverb,
/* 0x16 */ I3dl2Reverb,
/* 0x17 */ Performance,
/* 0x18 */ ClearMixBuffer,
/* 0x19 */ CopyMixBuffer,
/* 0x1A */ LightLimiterVersion1,
/* 0x1B */ LightLimiterVersion2,
/* 0x1C */ MultiTapBiquadFilter,
/* 0x1D */ Capture,
/* 0x1E */ Compressor,
};
constexpr u32 CommandMagic{0xCAFEBABE};
/**
* A command, generated by the host, and processed by the ADSP's AudioRenderer.
*/
struct ICommand {
virtual ~ICommand() = default;
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
virtual void Dump(const ADSP::CommandListProcessor& processor, std::string& string) = 0;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
virtual void Process(const ADSP::CommandListProcessor& processor) = 0;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
virtual bool Verify(const ADSP::CommandListProcessor& processor) = 0;
/// Command magic 0xCAFEBABE
u32 magic{};
/// Command enabled
bool enabled{};
/// Type of this command (see CommandId)
CommandId type{};
/// Size of this command
s16 size{};
/// Estimated processing time for this command
u32 estimated_process_time{};
/// Node id of the voice or mix this command was generated from
u32 node_id{};
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <string>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/clear_mix.h"
namespace AudioCore::AudioRenderer {
void ClearMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("ClearMixBufferCommand\n");
}
void ClearMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
memset(processor.mix_buffers.data(), 0, processor.mix_buffers.size_bytes());
}
bool ClearMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,45 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for a clearing the mix buffers.
* Used at the start of each command list.
*/
struct ClearMixBufferCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,27 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/copy_mix.h"
namespace AudioCore::AudioRenderer {
void CopyMixBufferCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("CopyMixBufferCommand\n\tinput {:02X} output {:02X}\n", input_index,
output_index);
}
void CopyMixBufferCommand::Process(const ADSP::CommandListProcessor& processor) {
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
processor.sample_count)};
std::memcpy(output.data(), input.data(), processor.sample_count * sizeof(s32));
}
bool CopyMixBufferCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for a copying a mix buffer from input to output.
*/
struct CopyMixBufferCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer index
s16 input_index;
/// Output mix buffer index
s16 output_index;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/common/common.h"
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/depop_for_mix_buffers.h"
namespace AudioCore::AudioRenderer {
/**
* Apply depopping. Add the depopped sample to each incoming new sample, decaying it each time
* according to decay.
*
* @param output - Output buffer to be depopped.
* @param depop_sample - Depopped sample to apply to output samples.
* @param decay_ - Amount to decay the depopped sample for every output sample.
* @param sample_count - Samples to process.
* @return Final decayed depop sample.
*/
static s32 ApplyDepopMix(std::span<s32> output, const s32 depop_sample,
Common::FixedPoint<49, 15>& decay_, const u32 sample_count) {
auto sample{std::abs(depop_sample)};
auto decay{decay_.to_raw()};
if (depop_sample <= 0) {
for (u32 i = 0; i < sample_count; i++) {
sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
output[i] -= sample;
}
return -sample;
} else {
for (u32 i = 0; i < sample_count; i++) {
sample = static_cast<s32>((static_cast<s64>(sample) * decay) >> 15);
output[i] += sample;
}
return sample;
}
}
void DepopForMixBuffersCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("DepopForMixBuffersCommand\n\tinput {:02X} count {} decay {}\n", input,
count, decay.to_float());
}
void DepopForMixBuffersCommand::Process(const ADSP::CommandListProcessor& processor) {
auto end_index{std::min(processor.buffer_count, input + count)};
std::span<s32> depop_buff{reinterpret_cast<s32*>(depop_buffer), end_index};
for (u32 index = input; index < end_index; index++) {
const auto depop_sample{depop_buff[index]};
if (depop_sample != 0) {
auto input_buffer{processor.mix_buffers.subspan(index * processor.sample_count,
processor.sample_count)};
depop_buff[index] =
ApplyDepopMix(input_buffer, depop_sample, decay, processor.sample_count);
}
}
}
bool DepopForMixBuffersCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
#include "common/fixed_point.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for depopping a mix buffer.
* Adds a cumulation of previous samples to the current mix buffer with a decay.
*/
struct DepopForMixBuffersCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Starting input mix buffer index
u32 input;
/// Number of mix buffers to depop
u32 count;
/// Amount to decay the depop sample for each new sample
Common::FixedPoint<49, 15> decay;
/// Address of the depop buffer, holding the last sample for every mix buffer
CpuAddr depop_buffer;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/depop_prepare.h"
#include "audio_core/renderer/voice/voice_state.h"
#include "common/fixed_point.h"
namespace AudioCore::AudioRenderer {
void DepopPrepareCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("DepopPrepareCommand\n\tinputs: ");
for (u32 i = 0; i < buffer_count; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n";
}
void DepopPrepareCommand::Process(const ADSP::CommandListProcessor& processor) {
auto samples{reinterpret_cast<s32*>(previous_samples)};
auto buffer{std::span(reinterpret_cast<s32*>(depop_buffer), buffer_count)};
for (u32 i = 0; i < buffer_count; i++) {
if (samples[i]) {
buffer[inputs[i]] += samples[i];
samples[i] = 0;
}
}
}
bool DepopPrepareCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for preparing depop.
* Adds the previusly output last samples to the depop buffer.
*/
struct DepopPrepareCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Depop buffer offset for each mix buffer
std::array<s16, MaxMixBuffers> inputs;
/// Pointer to the previous mix buffer samples
CpuAddr previous_samples;
/// Number of mix buffers to use
u32 buffer_count;
/// Pointer to the current depop values
CpuAddr depop_buffer;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,70 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <limits>
#include <span>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/mix.h"
#include "common/fixed_point.h"
namespace AudioCore::AudioRenderer {
/**
* Mix input mix buffer into output mix buffer, with volume applied to the input.
*
* @tparam Q - Number of bits for fixed point operations.
* @param output - Output mix buffer.
* @param input - Input mix buffer.
* @param volume - Volume applied to the input.
* @param sample_count - Number of samples to process.
*/
template <size_t Q>
static void ApplyMix(std::span<s32> output, std::span<const s32> input, const f32 volume_,
const u32 sample_count) {
const Common::FixedPoint<64 - Q, Q> volume{volume_};
for (u32 i = 0; i < sample_count; i++) {
output[i] = (output[i] + input[i] * volume).to_int();
}
}
void MixCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("MixCommand");
string += fmt::format("\n\tinput {:02X}", input_index);
string += fmt::format("\n\toutput {:02X}", output_index);
string += fmt::format("\n\tvolume {:.8f}", volume);
string += "\n";
}
void MixCommand::Process(const ADSP::CommandListProcessor& processor) {
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
processor.sample_count)};
// If volume is 0, nothing will be added to the output, so just skip.
if (volume == 0.0f) {
return;
}
switch (precision) {
case 15:
ApplyMix<15>(output, input, volume, processor.sample_count);
break;
case 23:
ApplyMix<23>(output, input, volume, processor.sample_count);
break;
default:
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
break;
}
}
bool MixCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
* applied to the input.
*/
struct MixCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
/// Input mix buffer index
s16 input_index;
/// Output mix buffer index
s16 output_index;
/// Mix volume applied to the input
f32 volume;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/mix_ramp.h"
#include "common/fixed_point.h"
#include "common/logging/log.h"
namespace AudioCore::AudioRenderer {
/**
* Mix input mix buffer into output mix buffer, with volume applied to the input.
*
* @tparam Q - Number of bits for fixed point operations.
* @param output - Output mix buffer.
* @param input - Input mix buffer.
* @param volume - Volume applied to the input.
* @param ramp - Ramp applied to volume every sample.
* @param sample_count - Number of samples to process.
* @return The final gained input sample, used for depopping.
*/
template <size_t Q>
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
const f32 ramp_, const u32 sample_count) {
Common::FixedPoint<64 - Q, Q> volume{volume_};
Common::FixedPoint<64 - Q, Q> sample{0};
if (ramp_ == 0.0f) {
for (u32 i = 0; i < sample_count; i++) {
sample = input[i] * volume;
output[i] = (output[i] + sample).to_int();
}
} else {
Common::FixedPoint<64 - Q, Q> ramp{ramp_};
for (u32 i = 0; i < sample_count; i++) {
sample = input[i] * volume;
output[i] = (output[i] + sample).to_int();
volume += ramp;
}
}
return sample.to_int();
}
template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32,
const u32);
template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32,
const u32);
void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
string += fmt::format("MixRampCommand");
string += fmt::format("\n\tinput {:02X}", input_index);
string += fmt::format("\n\toutput {:02X}", output_index);
string += fmt::format("\n\tvolume {:.8f}", volume);
string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
string += fmt::format("\n\tramp {:.8f}", ramp);
string += "\n";
}
void MixRampCommand::Process(const ADSP::CommandListProcessor& processor) {
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
processor.sample_count)};
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
auto prev_sample_ptr{reinterpret_cast<s32*>(previous_sample)};
// If previous volume and ramp are both 0, nothing will be added to the output, so just skip.
if (prev_volume == 0.0f && ramp == 0.0f) {
*prev_sample_ptr = 0;
return;
}
switch (precision) {
case 15:
*prev_sample_ptr =
ApplyMixRamp<15>(output, input, prev_volume, ramp, processor.sample_count);
break;
case 23:
*prev_sample_ptr =
ApplyMixRamp<23>(output, input, prev_volume, ramp, processor.sample_count);
break;
default:
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
break;
}
}
bool MixRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for mixing an input mix buffer to an output mix buffer, with a volume
* applied to the input, and volume ramping to smooth out the transition.
*/
struct MixRampCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
/// Input mix buffer index
s16 input_index;
/// Output mix buffer index
s16 output_index;
/// Previous mix volume
f32 prev_volume;
/// Current mix volume
f32 volume;
/// Pointer to the previous sample buffer, used for depopping
CpuAddr previous_sample;
};
/**
* Mix input mix buffer into output mix buffer, with volume applied to the input.
* @tparam Q - Number of bits for fixed point operations.
* @param output - Output mix buffer.
* @param input - Input mix buffer.
* @param volume - Volume applied to the input.
* @param ramp - Ramp applied to volume every sample.
* @param sample_count - Number of samples to process.
* @return The final gained input sample, used for depopping.
*/
template <size_t Q>
s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_,
const f32 ramp_, const u32 sample_count);
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,65 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/mix_ramp.h"
#include "audio_core/renderer/command/mix/mix_ramp_grouped.h"
namespace AudioCore::AudioRenderer {
void MixRampGroupedCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
string += "MixRampGroupedCommand";
for (u32 i = 0; i < buffer_count; i++) {
string += fmt::format("\n\t{}", i);
const auto ramp{(volumes[i] - prev_volumes[i]) / static_cast<f32>(processor.sample_count)};
string += fmt::format("\n\t\tinput {:02X}", inputs[i]);
string += fmt::format("\n\t\toutput {:02X}", outputs[i]);
string += fmt::format("\n\t\tvolume {:.8f}", volumes[i]);
string += fmt::format("\n\t\tprev_volume {:.8f}", prev_volumes[i]);
string += fmt::format("\n\t\tramp {:.8f}", ramp);
string += "\n";
}
}
void MixRampGroupedCommand::Process(const ADSP::CommandListProcessor& processor) {
std::span<s32> prev_samples = {reinterpret_cast<s32*>(previous_samples), MaxMixBuffers};
for (u32 i = 0; i < buffer_count; i++) {
auto last_sample{0};
if (prev_volumes[i] != 0.0f || volumes[i] != 0.0f) {
const auto output{processor.mix_buffers.subspan(outputs[i] * processor.sample_count,
processor.sample_count)};
const auto input{processor.mix_buffers.subspan(inputs[i] * processor.sample_count,
processor.sample_count)};
const auto ramp{(volumes[i] - prev_volumes[i]) /
static_cast<f32>(processor.sample_count)};
if (prev_volumes[i] == 0.0f && ramp == 0.0f) {
prev_samples[i] = 0;
continue;
}
switch (precision) {
case 15:
last_sample =
ApplyMixRamp<15>(output, input, prev_volumes[i], ramp, processor.sample_count);
break;
case 23:
last_sample =
ApplyMixRamp<23>(output, input, prev_volumes[i], ramp, processor.sample_count);
break;
default:
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
break;
}
}
prev_samples[i] = last_sample;
}
}
bool MixRampGroupedCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,61 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for mixing multiple input mix buffers to multiple output mix buffers, with
* a volume applied to the input, and volume ramping to smooth out the transition.
*/
struct MixRampGroupedCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
/// Number of mix buffers to mix
u32 buffer_count;
/// Input mix buffer indexes for each mix buffer
std::array<s16, MaxMixBuffers> inputs;
/// Output mix buffer indexes for each mix buffer
std::array<s16, MaxMixBuffers> outputs;
/// Previous mix vloumes for each mix buffer
std::array<f32, MaxMixBuffers> prev_volumes;
/// Current mix vloumes for each mix buffer
std::array<f32, MaxMixBuffers> volumes;
/// Pointer to the previous sample buffer, used for depop
CpuAddr previous_samples;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/volume.h"
#include "common/fixed_point.h"
#include "common/logging/log.h"
namespace AudioCore::AudioRenderer {
/**
* Apply volume to the input mix buffer, saving to the output buffer.
*
* @tparam Q - Number of bits for fixed point operations.
* @param output - Output mix buffer.
* @param input - Input mix buffer.
* @param volume - Volume applied to the input.
* @param sample_count - Number of samples to process.
*/
template <size_t Q>
static void ApplyUniformGain(std::span<s32> output, std::span<const s32> input, const f32 volume,
const u32 sample_count) {
if (volume == 1.0f) {
std::memcpy(output.data(), input.data(), input.size_bytes());
} else {
const Common::FixedPoint<64 - Q, Q> gain{volume};
for (u32 i = 0; i < sample_count; i++) {
output[i] = (input[i] * gain).to_int();
}
}
}
void VolumeCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("VolumeCommand");
string += fmt::format("\n\tinput {:02X}", input_index);
string += fmt::format("\n\toutput {:02X}", output_index);
string += fmt::format("\n\tvolume {:.8f}", volume);
string += "\n";
}
void VolumeCommand::Process(const ADSP::CommandListProcessor& processor) {
// If input and output buffers are the same, and the volume is 1.0f, this won't do
// anything, so just skip.
if (input_index == output_index && volume == 1.0f) {
return;
}
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
processor.sample_count)};
switch (precision) {
case 15:
ApplyUniformGain<15>(output, input, volume, processor.sample_count);
break;
case 23:
ApplyUniformGain<23>(output, input, volume, processor.sample_count);
break;
default:
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
break;
}
}
bool VolumeCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for applying volume to a mix buffer.
*/
struct VolumeCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
/// Input mix buffer index
s16 input_index;
/// Output mix buffer index
s16 output_index;
/// Mix volume applied to the input
f32 volume;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/mix/volume_ramp.h"
#include "common/fixed_point.h"
namespace AudioCore::AudioRenderer {
/**
* Apply volume with ramping to the input mix buffer, saving to the output buffer.
*
* @tparam Q - Number of bits for fixed point operations.
* @param output - Output mix buffers.
* @param input - Input mix buffers.
* @param volume - Volume applied to the input.
* @param ramp - Ramp applied to volume every sample.
* @param sample_count - Number of samples to process.
*/
template <size_t Q>
static void ApplyLinearEnvelopeGain(std::span<s32> output, std::span<const s32> input,
const f32 volume, const f32 ramp_, const u32 sample_count) {
if (volume == 0.0f && ramp_ == 0.0f) {
std::memset(output.data(), 0, output.size_bytes());
} else if (volume == 1.0f && ramp_ == 0.0f) {
std::memcpy(output.data(), input.data(), output.size_bytes());
} else if (ramp_ == 0.0f) {
const Common::FixedPoint<64 - Q, Q> gain{volume};
for (u32 i = 0; i < sample_count; i++) {
output[i] = (input[i] * gain).to_int();
}
} else {
Common::FixedPoint<64 - Q, Q> gain{volume};
const Common::FixedPoint<64 - Q, Q> ramp{ramp_};
for (u32 i = 0; i < sample_count; i++) {
output[i] = (input[i] * gain).to_int();
gain += ramp;
}
}
}
void VolumeRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) {
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
string += fmt::format("VolumeRampCommand");
string += fmt::format("\n\tinput {:02X}", input_index);
string += fmt::format("\n\toutput {:02X}", output_index);
string += fmt::format("\n\tvolume {:.8f}", volume);
string += fmt::format("\n\tprev_volume {:.8f}", prev_volume);
string += fmt::format("\n\tramp {:.8f}", ramp);
string += "\n";
}
void VolumeRampCommand::Process(const ADSP::CommandListProcessor& processor) {
auto output{processor.mix_buffers.subspan(output_index * processor.sample_count,
processor.sample_count)};
auto input{processor.mix_buffers.subspan(input_index * processor.sample_count,
processor.sample_count)};
const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)};
// If input and output buffers are the same, and the volume is 1.0f, and there's no ramping,
// this won't do anything, so just skip.
if (input_index == output_index && prev_volume == 1.0f && ramp == 0.0f) {
return;
}
switch (precision) {
case 15:
ApplyLinearEnvelopeGain<15>(output, input, prev_volume, ramp, processor.sample_count);
break;
case 23:
ApplyLinearEnvelopeGain<23>(output, input, prev_volume, ramp, processor.sample_count);
break;
default:
LOG_ERROR(Service_Audio, "Invalid precision {}", precision);
break;
}
}
bool VolumeRampCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,56 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for applying volume to a mix buffer, with ramping for the volume to smooth
* out the transition.
*/
struct VolumeRampCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Fixed point precision
u8 precision;
/// Input mix buffer index
s16 input_index;
/// Output mix buffer index
s16 output_index;
/// Previous mix volume applied to the input
f32 prev_volume;
/// Current mix volume applied to the input
f32 volume;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/performance/performance.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
namespace AudioCore::AudioRenderer {
void PerformanceCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("PerformanceCommand\n\tstate {}\n", static_cast<u32>(state));
}
void PerformanceCommand::Process(const ADSP::CommandListProcessor& processor) {
auto base{entry_address.translated_address};
if (state == PerformanceState::Start) {
auto start_time_ptr{reinterpret_cast<u32*>(base + entry_address.entry_start_time_offset)};
*start_time_ptr = static_cast<u32>(
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
processor.start_time - processor.current_processing_time)
.count());
} else if (state == PerformanceState::Stop) {
auto processed_time_ptr{
reinterpret_cast<u32*>(base + entry_address.entry_processed_time_offset)};
auto entry_count_ptr{
reinterpret_cast<u32*>(base + entry_address.header_entry_count_offset)};
*processed_time_ptr = static_cast<u32>(
Core::Timing::CyclesToUs(processor.system->CoreTiming().GetClockTicks() -
processor.start_time - processor.current_processing_time)
.count());
(*entry_count_ptr)++;
}
}
bool PerformanceCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,51 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "audio_core/renderer/performance/performance_entry_addresses.h"
#include "audio_core/renderer/performance/performance_manager.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for writing AudioRenderer performance metrics back to the sysmodule.
*/
struct PerformanceCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// State of the performance
PerformanceState state;
/// Pointers to be written
PerformanceEntryAddresses entry_address;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/resample/downmix_6ch_to_2ch.h"
namespace AudioCore::AudioRenderer {
void DownMix6chTo2chCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("DownMix6chTo2chCommand\n\tinputs: ");
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n\toutputs: ";
for (u32 i = 0; i < MaxChannels; i++) {
string += fmt::format("{:02X}, ", outputs[i]);
}
string += "\n";
}
void DownMix6chTo2chCommand::Process(const ADSP::CommandListProcessor& processor) {
auto in_front_left{
processor.mix_buffers.subspan(inputs[0] * processor.sample_count, processor.sample_count)};
auto in_front_right{
processor.mix_buffers.subspan(inputs[1] * processor.sample_count, processor.sample_count)};
auto in_center{
processor.mix_buffers.subspan(inputs[2] * processor.sample_count, processor.sample_count)};
auto in_lfe{
processor.mix_buffers.subspan(inputs[3] * processor.sample_count, processor.sample_count)};
auto in_back_left{
processor.mix_buffers.subspan(inputs[4] * processor.sample_count, processor.sample_count)};
auto in_back_right{
processor.mix_buffers.subspan(inputs[5] * processor.sample_count, processor.sample_count)};
auto out_front_left{
processor.mix_buffers.subspan(outputs[0] * processor.sample_count, processor.sample_count)};
auto out_front_right{
processor.mix_buffers.subspan(outputs[1] * processor.sample_count, processor.sample_count)};
auto out_center{
processor.mix_buffers.subspan(outputs[2] * processor.sample_count, processor.sample_count)};
auto out_lfe{
processor.mix_buffers.subspan(outputs[3] * processor.sample_count, processor.sample_count)};
auto out_back_left{
processor.mix_buffers.subspan(outputs[4] * processor.sample_count, processor.sample_count)};
auto out_back_right{
processor.mix_buffers.subspan(outputs[5] * processor.sample_count, processor.sample_count)};
for (u32 i = 0; i < processor.sample_count; i++) {
const auto left_sample{(in_front_left[i] * down_mix_coeff[0] +
in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
in_back_left[i] * down_mix_coeff[3])
.to_int()};
const auto right_sample{(in_front_right[i] * down_mix_coeff[0] +
in_center[i] * down_mix_coeff[1] + in_lfe[i] * down_mix_coeff[2] +
in_back_right[i] * down_mix_coeff[3])
.to_int()};
out_front_left[i] = left_sample;
out_front_right[i] = right_sample;
}
std::memset(out_center.data(), 0, out_center.size_bytes());
std::memset(out_lfe.data(), 0, out_lfe.size_bytes());
std::memset(out_back_left.data(), 0, out_back_left.size_bytes());
std::memset(out_back_right.data(), 0, out_back_right.size_bytes());
}
bool DownMix6chTo2chCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
#include "common/fixed_point.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for downmixing 6 channels to 2.
* Channel layout (SMPTE):
* 0 - front left
* 1 - front right
* 2 - center
* 3 - lfe
* 4 - back left
* 5 - back right
*/
struct DownMix6chTo2chCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Input mix buffer offsets for each channel
std::array<s16, MaxChannels> inputs;
/// Output mix buffer offsets for each channel
std::array<s16, MaxChannels> outputs;
/// Coefficients used for downmixing
std::array<Common::FixedPoint<48, 16>, 4> down_mix_coeff;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,883 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/renderer/command/resample/resample.h"
namespace AudioCore::AudioRenderer {
static void ResampleLowQuality(std::span<s32> output, std::span<const s16> input,
const Common::FixedPoint<49, 15>& sample_rate_ratio,
Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
if (sample_rate_ratio == 1.0f) {
for (u32 i = 0; i < samples_to_write; i++) {
output[i] = input[i];
}
} else {
u32 read_index{0};
for (u32 i = 0; i < samples_to_write; i++) {
output[i] = input[read_index + (fraction >= 0.5f)];
fraction += sample_rate_ratio;
read_index += static_cast<u32>(fraction.to_int_floor());
fraction.clear_int();
}
}
}
static void ResampleNormalQuality(std::span<s32> output, std::span<const s16> input,
const Common::FixedPoint<49, 15>& sample_rate_ratio,
Common::FixedPoint<49, 15>& fraction,
const u32 samples_to_write) {
static constexpr std::array<f32, 512> lut0 = {
0.20141602f, 0.59283447f, 0.20513916f, 0.00009155f, 0.19772339f, 0.59277344f, 0.20889282f,
0.00027466f, 0.19406128f, 0.59262085f, 0.21264648f, 0.00045776f, 0.19039917f, 0.59240723f,
0.21646118f, 0.00067139f, 0.18679810f, 0.59213257f, 0.22030640f, 0.00085449f, 0.18322754f,
0.59176636f, 0.22415161f, 0.00103760f, 0.17968750f, 0.59133911f, 0.22802734f, 0.00125122f,
0.17617798f, 0.59085083f, 0.23193359f, 0.00146484f, 0.17269897f, 0.59027100f, 0.23583984f,
0.00167847f, 0.16925049f, 0.58963013f, 0.23977661f, 0.00189209f, 0.16583252f, 0.58892822f,
0.24374390f, 0.00210571f, 0.16244507f, 0.58816528f, 0.24774170f, 0.00234985f, 0.15908813f,
0.58731079f, 0.25173950f, 0.00256348f, 0.15576172f, 0.58639526f, 0.25576782f, 0.00280762f,
0.15249634f, 0.58541870f, 0.25979614f, 0.00308228f, 0.14923096f, 0.58435059f, 0.26385498f,
0.00332642f, 0.14602661f, 0.58325195f, 0.26794434f, 0.00360107f, 0.14285278f, 0.58206177f,
0.27203369f, 0.00387573f, 0.13973999f, 0.58078003f, 0.27612305f, 0.00418091f, 0.13662720f,
0.57946777f, 0.28024292f, 0.00448608f, 0.13357544f, 0.57806396f, 0.28436279f, 0.00479126f,
0.13052368f, 0.57662964f, 0.28851318f, 0.00512695f, 0.12753296f, 0.57510376f, 0.29266357f,
0.00546265f, 0.12460327f, 0.57351685f, 0.29681396f, 0.00579834f, 0.12167358f, 0.57183838f,
0.30099487f, 0.00616455f, 0.11880493f, 0.57012939f, 0.30517578f, 0.00656128f, 0.11596680f,
0.56835938f, 0.30935669f, 0.00695801f, 0.11318970f, 0.56649780f, 0.31353760f, 0.00735474f,
0.11041260f, 0.56457520f, 0.31771851f, 0.00778198f, 0.10769653f, 0.56262207f, 0.32192993f,
0.00823975f, 0.10501099f, 0.56057739f, 0.32614136f, 0.00869751f, 0.10238647f, 0.55847168f,
0.33032227f, 0.00915527f, 0.09976196f, 0.55633545f, 0.33453369f, 0.00967407f, 0.09722900f,
0.55410767f, 0.33874512f, 0.01019287f, 0.09469604f, 0.55181885f, 0.34295654f, 0.01071167f,
0.09222412f, 0.54949951f, 0.34713745f, 0.01126099f, 0.08978271f, 0.54708862f, 0.35134888f,
0.01184082f, 0.08737183f, 0.54464722f, 0.35552979f, 0.01245117f, 0.08499146f, 0.54214478f,
0.35974121f, 0.01306152f, 0.08267212f, 0.53958130f, 0.36392212f, 0.01370239f, 0.08041382f,
0.53695679f, 0.36810303f, 0.01437378f, 0.07815552f, 0.53427124f, 0.37225342f, 0.01507568f,
0.07595825f, 0.53155518f, 0.37640381f, 0.01577759f, 0.07379150f, 0.52877808f, 0.38055420f,
0.01651001f, 0.07165527f, 0.52593994f, 0.38470459f, 0.01727295f, 0.06958008f, 0.52307129f,
0.38882446f, 0.01806641f, 0.06753540f, 0.52014160f, 0.39294434f, 0.01889038f, 0.06552124f,
0.51715088f, 0.39703369f, 0.01974487f, 0.06356812f, 0.51409912f, 0.40112305f, 0.02059937f,
0.06164551f, 0.51101685f, 0.40518188f, 0.02148438f, 0.05975342f, 0.50790405f, 0.40921021f,
0.02243042f, 0.05789185f, 0.50473022f, 0.41323853f, 0.02337646f, 0.05609131f, 0.50152588f,
0.41726685f, 0.02435303f, 0.05432129f, 0.49826050f, 0.42123413f, 0.02539062f, 0.05258179f,
0.49493408f, 0.42520142f, 0.02642822f, 0.05087280f, 0.49160767f, 0.42913818f, 0.02749634f,
0.04922485f, 0.48822021f, 0.43307495f, 0.02859497f, 0.04760742f, 0.48477173f, 0.43695068f,
0.02975464f, 0.04602051f, 0.48132324f, 0.44082642f, 0.03091431f, 0.04446411f, 0.47781372f,
0.44467163f, 0.03210449f, 0.04293823f, 0.47424316f, 0.44845581f, 0.03335571f, 0.04147339f,
0.47067261f, 0.45223999f, 0.03460693f, 0.04003906f, 0.46704102f, 0.45599365f, 0.03591919f,
0.03863525f, 0.46340942f, 0.45971680f, 0.03726196f, 0.03726196f, 0.45971680f, 0.46340942f,
0.03863525f, 0.03591919f, 0.45599365f, 0.46704102f, 0.04003906f, 0.03460693f, 0.45223999f,
0.47067261f, 0.04147339f, 0.03335571f, 0.44845581f, 0.47424316f, 0.04293823f, 0.03210449f,
0.44467163f, 0.47781372f, 0.04446411f, 0.03091431f, 0.44082642f, 0.48132324f, 0.04602051f,
0.02975464f, 0.43695068f, 0.48477173f, 0.04760742f, 0.02859497f, 0.43307495f, 0.48822021f,
0.04922485f, 0.02749634f, 0.42913818f, 0.49160767f, 0.05087280f, 0.02642822f, 0.42520142f,
0.49493408f, 0.05258179f, 0.02539062f, 0.42123413f, 0.49826050f, 0.05432129f, 0.02435303f,
0.41726685f, 0.50152588f, 0.05609131f, 0.02337646f, 0.41323853f, 0.50473022f, 0.05789185f,
0.02243042f, 0.40921021f, 0.50790405f, 0.05975342f, 0.02148438f, 0.40518188f, 0.51101685f,
0.06164551f, 0.02059937f, 0.40112305f, 0.51409912f, 0.06356812f, 0.01974487f, 0.39703369f,
0.51715088f, 0.06552124f, 0.01889038f, 0.39294434f, 0.52014160f, 0.06753540f, 0.01806641f,
0.38882446f, 0.52307129f, 0.06958008f, 0.01727295f, 0.38470459f, 0.52593994f, 0.07165527f,
0.01651001f, 0.38055420f, 0.52877808f, 0.07379150f, 0.01577759f, 0.37640381f, 0.53155518f,
0.07595825f, 0.01507568f, 0.37225342f, 0.53427124f, 0.07815552f, 0.01437378f, 0.36810303f,
0.53695679f, 0.08041382f, 0.01370239f, 0.36392212f, 0.53958130f, 0.08267212f, 0.01306152f,
0.35974121f, 0.54214478f, 0.08499146f, 0.01245117f, 0.35552979f, 0.54464722f, 0.08737183f,
0.01184082f, 0.35134888f, 0.54708862f, 0.08978271f, 0.01126099f, 0.34713745f, 0.54949951f,
0.09222412f, 0.01071167f, 0.34295654f, 0.55181885f, 0.09469604f, 0.01019287f, 0.33874512f,
0.55410767f, 0.09722900f, 0.00967407f, 0.33453369f, 0.55633545f, 0.09976196f, 0.00915527f,
0.33032227f, 0.55847168f, 0.10238647f, 0.00869751f, 0.32614136f, 0.56057739f, 0.10501099f,
0.00823975f, 0.32192993f, 0.56262207f, 0.10769653f, 0.00778198f, 0.31771851f, 0.56457520f,
0.11041260f, 0.00735474f, 0.31353760f, 0.56649780f, 0.11318970f, 0.00695801f, 0.30935669f,
0.56835938f, 0.11596680f, 0.00656128f, 0.30517578f, 0.57012939f, 0.11880493f, 0.00616455f,
0.30099487f, 0.57183838f, 0.12167358f, 0.00579834f, 0.29681396f, 0.57351685f, 0.12460327f,
0.00546265f, 0.29266357f, 0.57510376f, 0.12753296f, 0.00512695f, 0.28851318f, 0.57662964f,
0.13052368f, 0.00479126f, 0.28436279f, 0.57806396f, 0.13357544f, 0.00448608f, 0.28024292f,
0.57946777f, 0.13662720f, 0.00418091f, 0.27612305f, 0.58078003f, 0.13973999f, 0.00387573f,
0.27203369f, 0.58206177f, 0.14285278f, 0.00360107f, 0.26794434f, 0.58325195f, 0.14602661f,
0.00332642f, 0.26385498f, 0.58435059f, 0.14923096f, 0.00308228f, 0.25979614f, 0.58541870f,
0.15249634f, 0.00280762f, 0.25576782f, 0.58639526f, 0.15576172f, 0.00256348f, 0.25173950f,
0.58731079f, 0.15908813f, 0.00234985f, 0.24774170f, 0.58816528f, 0.16244507f, 0.00210571f,
0.24374390f, 0.58892822f, 0.16583252f, 0.00189209f, 0.23977661f, 0.58963013f, 0.16925049f,
0.00167847f, 0.23583984f, 0.59027100f, 0.17269897f, 0.00146484f, 0.23193359f, 0.59085083f,
0.17617798f, 0.00125122f, 0.22802734f, 0.59133911f, 0.17968750f, 0.00103760f, 0.22415161f,
0.59176636f, 0.18322754f, 0.00085449f, 0.22030640f, 0.59213257f, 0.18679810f, 0.00067139f,
0.21646118f, 0.59240723f, 0.19039917f, 0.00045776f, 0.21264648f, 0.59262085f, 0.19406128f,
0.00027466f, 0.20889282f, 0.59277344f, 0.19772339f, 0.00009155f, 0.20513916f, 0.59283447f,
0.20141602f,
};
static constexpr std::array<f32, 512> lut1 = {
0.00207520f, 0.99606323f, 0.00210571f, -0.00015259f, -0.00610352f, 0.99578857f,
0.00646973f, -0.00045776f, -0.01000977f, 0.99526978f, 0.01095581f, -0.00079346f,
-0.01373291f, 0.99444580f, 0.01562500f, -0.00109863f, -0.01733398f, 0.99337769f,
0.02041626f, -0.00143433f, -0.02075195f, 0.99203491f, 0.02539062f, -0.00177002f,
-0.02404785f, 0.99041748f, 0.03051758f, -0.00210571f, -0.02719116f, 0.98855591f,
0.03582764f, -0.00244141f, -0.03021240f, 0.98641968f, 0.04125977f, -0.00280762f,
-0.03308105f, 0.98400879f, 0.04687500f, -0.00314331f, -0.03579712f, 0.98135376f,
0.05261230f, -0.00350952f, -0.03839111f, 0.97842407f, 0.05856323f, -0.00390625f,
-0.04083252f, 0.97521973f, 0.06463623f, -0.00427246f, -0.04315186f, 0.97180176f,
0.07086182f, -0.00466919f, -0.04534912f, 0.96810913f, 0.07727051f, -0.00509644f,
-0.04742432f, 0.96414185f, 0.08383179f, -0.00549316f, -0.04934692f, 0.95996094f,
0.09054565f, -0.00592041f, -0.05114746f, 0.95550537f, 0.09741211f, -0.00637817f,
-0.05285645f, 0.95083618f, 0.10443115f, -0.00683594f, -0.05441284f, 0.94589233f,
0.11160278f, -0.00732422f, -0.05584717f, 0.94073486f, 0.11892700f, -0.00781250f,
-0.05718994f, 0.93533325f, 0.12643433f, -0.00830078f, -0.05841064f, 0.92968750f,
0.13406372f, -0.00881958f, -0.05953979f, 0.92382812f, 0.14184570f, -0.00936890f,
-0.06054688f, 0.91772461f, 0.14978027f, -0.00991821f, -0.06146240f, 0.91143799f,
0.15783691f, -0.01046753f, -0.06225586f, 0.90490723f, 0.16607666f, -0.01104736f,
-0.06295776f, 0.89816284f, 0.17443848f, -0.01165771f, -0.06356812f, 0.89120483f,
0.18292236f, -0.01229858f, -0.06408691f, 0.88403320f, 0.19155884f, -0.01293945f,
-0.06451416f, 0.87667847f, 0.20034790f, -0.01358032f, -0.06484985f, 0.86914062f,
0.20925903f, -0.01428223f, -0.06509399f, 0.86138916f, 0.21829224f, -0.01495361f,
-0.06527710f, 0.85345459f, 0.22744751f, -0.01568604f, -0.06536865f, 0.84533691f,
0.23675537f, -0.01641846f, -0.06536865f, 0.83703613f, 0.24615479f, -0.01718140f,
-0.06533813f, 0.82858276f, 0.25567627f, -0.01794434f, -0.06518555f, 0.81991577f,
0.26531982f, -0.01873779f, -0.06500244f, 0.81112671f, 0.27505493f, -0.01956177f,
-0.06472778f, 0.80215454f, 0.28491211f, -0.02038574f, -0.06442261f, 0.79306030f,
0.29489136f, -0.02124023f, -0.06402588f, 0.78378296f, 0.30496216f, -0.02209473f,
-0.06359863f, 0.77438354f, 0.31512451f, -0.02297974f, -0.06307983f, 0.76486206f,
0.32537842f, -0.02389526f, -0.06253052f, 0.75518799f, 0.33569336f, -0.02481079f,
-0.06195068f, 0.74539185f, 0.34613037f, -0.02575684f, -0.06130981f, 0.73547363f,
0.35662842f, -0.02670288f, -0.06060791f, 0.72543335f, 0.36721802f, -0.02767944f,
-0.05987549f, 0.71527100f, 0.37786865f, -0.02865601f, -0.05911255f, 0.70504761f,
0.38858032f, -0.02966309f, -0.05831909f, 0.69470215f, 0.39935303f, -0.03067017f,
-0.05746460f, 0.68426514f, 0.41018677f, -0.03170776f, -0.05661011f, 0.67373657f,
0.42108154f, -0.03271484f, -0.05569458f, 0.66311646f, 0.43200684f, -0.03378296f,
-0.05477905f, 0.65246582f, 0.44299316f, -0.03482056f, -0.05383301f, 0.64169312f,
0.45401001f, -0.03588867f, -0.05285645f, 0.63088989f, 0.46505737f, -0.03695679f,
-0.05187988f, 0.62002563f, 0.47613525f, -0.03802490f, -0.05087280f, 0.60910034f,
0.48721313f, -0.03912354f, -0.04983521f, 0.59814453f, 0.49832153f, -0.04019165f,
-0.04879761f, 0.58712769f, 0.50946045f, -0.04129028f, -0.04772949f, 0.57611084f,
0.52056885f, -0.04235840f, -0.04669189f, 0.56503296f, 0.53170776f, -0.04345703f,
-0.04562378f, 0.55392456f, 0.54281616f, -0.04452515f, -0.04452515f, 0.54281616f,
0.55392456f, -0.04562378f, -0.04345703f, 0.53170776f, 0.56503296f, -0.04669189f,
-0.04235840f, 0.52056885f, 0.57611084f, -0.04772949f, -0.04129028f, 0.50946045f,
0.58712769f, -0.04879761f, -0.04019165f, 0.49832153f, 0.59814453f, -0.04983521f,
-0.03912354f, 0.48721313f, 0.60910034f, -0.05087280f, -0.03802490f, 0.47613525f,
0.62002563f, -0.05187988f, -0.03695679f, 0.46505737f, 0.63088989f, -0.05285645f,
-0.03588867f, 0.45401001f, 0.64169312f, -0.05383301f, -0.03482056f, 0.44299316f,
0.65246582f, -0.05477905f, -0.03378296f, 0.43200684f, 0.66311646f, -0.05569458f,
-0.03271484f, 0.42108154f, 0.67373657f, -0.05661011f, -0.03170776f, 0.41018677f,
0.68426514f, -0.05746460f, -0.03067017f, 0.39935303f, 0.69470215f, -0.05831909f,
-0.02966309f, 0.38858032f, 0.70504761f, -0.05911255f, -0.02865601f, 0.37786865f,
0.71527100f, -0.05987549f, -0.02767944f, 0.36721802f, 0.72543335f, -0.06060791f,
-0.02670288f, 0.35662842f, 0.73547363f, -0.06130981f, -0.02575684f, 0.34613037f,
0.74539185f, -0.06195068f, -0.02481079f, 0.33569336f, 0.75518799f, -0.06253052f,
-0.02389526f, 0.32537842f, 0.76486206f, -0.06307983f, -0.02297974f, 0.31512451f,
0.77438354f, -0.06359863f, -0.02209473f, 0.30496216f, 0.78378296f, -0.06402588f,
-0.02124023f, 0.29489136f, 0.79306030f, -0.06442261f, -0.02038574f, 0.28491211f,
0.80215454f, -0.06472778f, -0.01956177f, 0.27505493f, 0.81112671f, -0.06500244f,
-0.01873779f, 0.26531982f, 0.81991577f, -0.06518555f, -0.01794434f, 0.25567627f,
0.82858276f, -0.06533813f, -0.01718140f, 0.24615479f, 0.83703613f, -0.06536865f,
-0.01641846f, 0.23675537f, 0.84533691f, -0.06536865f, -0.01568604f, 0.22744751f,
0.85345459f, -0.06527710f, -0.01495361f, 0.21829224f, 0.86138916f, -0.06509399f,
-0.01428223f, 0.20925903f, 0.86914062f, -0.06484985f, -0.01358032f, 0.20034790f,
0.87667847f, -0.06451416f, -0.01293945f, 0.19155884f, 0.88403320f, -0.06408691f,
-0.01229858f, 0.18292236f, 0.89120483f, -0.06356812f, -0.01165771f, 0.17443848f,
0.89816284f, -0.06295776f, -0.01104736f, 0.16607666f, 0.90490723f, -0.06225586f,
-0.01046753f, 0.15783691f, 0.91143799f, -0.06146240f, -0.00991821f, 0.14978027f,
0.91772461f, -0.06054688f, -0.00936890f, 0.14184570f, 0.92382812f, -0.05953979f,
-0.00881958f, 0.13406372f, 0.92968750f, -0.05841064f, -0.00830078f, 0.12643433f,
0.93533325f, -0.05718994f, -0.00781250f, 0.11892700f, 0.94073486f, -0.05584717f,
-0.00732422f, 0.11160278f, 0.94589233f, -0.05441284f, -0.00683594f, 0.10443115f,
0.95083618f, -0.05285645f, -0.00637817f, 0.09741211f, 0.95550537f, -0.05114746f,
-0.00592041f, 0.09054565f, 0.95996094f, -0.04934692f, -0.00549316f, 0.08383179f,
0.96414185f, -0.04742432f, -0.00509644f, 0.07727051f, 0.96810913f, -0.04534912f,
-0.00466919f, 0.07086182f, 0.97180176f, -0.04315186f, -0.00427246f, 0.06463623f,
0.97521973f, -0.04083252f, -0.00390625f, 0.05856323f, 0.97842407f, -0.03839111f,
-0.00350952f, 0.05261230f, 0.98135376f, -0.03579712f, -0.00314331f, 0.04687500f,
0.98400879f, -0.03308105f, -0.00280762f, 0.04125977f, 0.98641968f, -0.03021240f,
-0.00244141f, 0.03582764f, 0.98855591f, -0.02719116f, -0.00210571f, 0.03051758f,
0.99041748f, -0.02404785f, -0.00177002f, 0.02539062f, 0.99203491f, -0.02075195f,
-0.00143433f, 0.02041626f, 0.99337769f, -0.01733398f, -0.00109863f, 0.01562500f,
0.99444580f, -0.01373291f, -0.00079346f, 0.01095581f, 0.99526978f, -0.01000977f,
-0.00045776f, 0.00646973f, 0.99578857f, -0.00610352f, -0.00015259f, 0.00210571f,
0.99606323f, -0.00207520f,
};
static constexpr std::array<f32, 512> lut2 = {
0.09750366f, 0.80221558f, 0.10159302f, -0.00097656f, 0.09350586f, 0.80203247f,
0.10580444f, -0.00103760f, 0.08959961f, 0.80169678f, 0.11010742f, -0.00115967f,
0.08578491f, 0.80117798f, 0.11447144f, -0.00128174f, 0.08203125f, 0.80047607f,
0.11892700f, -0.00140381f, 0.07836914f, 0.79962158f, 0.12347412f, -0.00152588f,
0.07479858f, 0.79861450f, 0.12814331f, -0.00164795f, 0.07135010f, 0.79742432f,
0.13287354f, -0.00177002f, 0.06796265f, 0.79605103f, 0.13769531f, -0.00192261f,
0.06469727f, 0.79452515f, 0.14260864f, -0.00204468f, 0.06149292f, 0.79284668f,
0.14761353f, -0.00219727f, 0.05834961f, 0.79098511f, 0.15270996f, -0.00231934f,
0.05532837f, 0.78894043f, 0.15789795f, -0.00247192f, 0.05236816f, 0.78674316f,
0.16317749f, -0.00265503f, 0.04949951f, 0.78442383f, 0.16851807f, -0.00280762f,
0.04672241f, 0.78189087f, 0.17398071f, -0.00299072f, 0.04400635f, 0.77920532f,
0.17950439f, -0.00314331f, 0.04141235f, 0.77636719f, 0.18511963f, -0.00332642f,
0.03887939f, 0.77337646f, 0.19082642f, -0.00350952f, 0.03640747f, 0.77023315f,
0.19659424f, -0.00369263f, 0.03402710f, 0.76693726f, 0.20248413f, -0.00387573f,
0.03173828f, 0.76348877f, 0.20843506f, -0.00405884f, 0.02951050f, 0.75985718f,
0.21444702f, -0.00427246f, 0.02737427f, 0.75610352f, 0.22055054f, -0.00445557f,
0.02529907f, 0.75219727f, 0.22674561f, -0.00466919f, 0.02331543f, 0.74816895f,
0.23300171f, -0.00485229f, 0.02139282f, 0.74398804f, 0.23931885f, -0.00506592f,
0.01956177f, 0.73965454f, 0.24572754f, -0.00531006f, 0.01779175f, 0.73519897f,
0.25219727f, -0.00552368f, 0.01605225f, 0.73059082f, 0.25872803f, -0.00570679f,
0.01440430f, 0.72586060f, 0.26535034f, -0.00592041f, 0.01281738f, 0.72100830f,
0.27203369f, -0.00616455f, 0.01132202f, 0.71600342f, 0.27877808f, -0.00637817f,
0.00988770f, 0.71090698f, 0.28558350f, -0.00656128f, 0.00851440f, 0.70565796f,
0.29244995f, -0.00677490f, 0.00720215f, 0.70031738f, 0.29934692f, -0.00701904f,
0.00592041f, 0.69485474f, 0.30633545f, -0.00723267f, 0.00469971f, 0.68927002f,
0.31338501f, -0.00741577f, 0.00357056f, 0.68356323f, 0.32046509f, -0.00762939f,
0.00247192f, 0.67773438f, 0.32760620f, -0.00787354f, 0.00143433f, 0.67184448f,
0.33477783f, -0.00808716f, 0.00045776f, 0.66583252f, 0.34197998f, -0.00827026f,
-0.00048828f, 0.65972900f, 0.34924316f, -0.00845337f, -0.00134277f, 0.65353394f,
0.35656738f, -0.00863647f, -0.00216675f, 0.64721680f, 0.36389160f, -0.00885010f,
-0.00296021f, 0.64083862f, 0.37127686f, -0.00903320f, -0.00369263f, 0.63433838f,
0.37869263f, -0.00921631f, -0.00436401f, 0.62777710f, 0.38613892f, -0.00933838f,
-0.00497437f, 0.62115479f, 0.39361572f, -0.00949097f, -0.00558472f, 0.61444092f,
0.40109253f, -0.00964355f, -0.00613403f, 0.60763550f, 0.40859985f, -0.00979614f,
-0.00665283f, 0.60076904f, 0.41610718f, -0.00991821f, -0.00714111f, 0.59384155f,
0.42364502f, -0.01000977f, -0.00756836f, 0.58685303f, 0.43121338f, -0.01013184f,
-0.00796509f, 0.57977295f, 0.43875122f, -0.01022339f, -0.00833130f, 0.57266235f,
0.44631958f, -0.01028442f, -0.00866699f, 0.56552124f, 0.45388794f, -0.01034546f,
-0.00897217f, 0.55831909f, 0.46145630f, -0.01040649f, -0.00921631f, 0.55105591f,
0.46902466f, -0.01040649f, -0.00946045f, 0.54373169f, 0.47659302f, -0.01040649f,
-0.00967407f, 0.53640747f, 0.48413086f, -0.01037598f, -0.00985718f, 0.52902222f,
0.49166870f, -0.01037598f, -0.01000977f, 0.52160645f, 0.49917603f, -0.01031494f,
-0.01013184f, 0.51416016f, 0.50668335f, -0.01025391f, -0.01025391f, 0.50668335f,
0.51416016f, -0.01013184f, -0.01031494f, 0.49917603f, 0.52160645f, -0.01000977f,
-0.01037598f, 0.49166870f, 0.52902222f, -0.00985718f, -0.01037598f, 0.48413086f,
0.53640747f, -0.00967407f, -0.01040649f, 0.47659302f, 0.54373169f, -0.00946045f,
-0.01040649f, 0.46902466f, 0.55105591f, -0.00921631f, -0.01040649f, 0.46145630f,
0.55831909f, -0.00897217f, -0.01034546f, 0.45388794f, 0.56552124f, -0.00866699f,
-0.01028442f, 0.44631958f, 0.57266235f, -0.00833130f, -0.01022339f, 0.43875122f,
0.57977295f, -0.00796509f, -0.01013184f, 0.43121338f, 0.58685303f, -0.00756836f,
-0.01000977f, 0.42364502f, 0.59384155f, -0.00714111f, -0.00991821f, 0.41610718f,
0.60076904f, -0.00665283f, -0.00979614f, 0.40859985f, 0.60763550f, -0.00613403f,
-0.00964355f, 0.40109253f, 0.61444092f, -0.00558472f, -0.00949097f, 0.39361572f,
0.62115479f, -0.00497437f, -0.00933838f, 0.38613892f, 0.62777710f, -0.00436401f,
-0.00921631f, 0.37869263f, 0.63433838f, -0.00369263f, -0.00903320f, 0.37127686f,
0.64083862f, -0.00296021f, -0.00885010f, 0.36389160f, 0.64721680f, -0.00216675f,
-0.00863647f, 0.35656738f, 0.65353394f, -0.00134277f, -0.00845337f, 0.34924316f,
0.65972900f, -0.00048828f, -0.00827026f, 0.34197998f, 0.66583252f, 0.00045776f,
-0.00808716f, 0.33477783f, 0.67184448f, 0.00143433f, -0.00787354f, 0.32760620f,
0.67773438f, 0.00247192f, -0.00762939f, 0.32046509f, 0.68356323f, 0.00357056f,
-0.00741577f, 0.31338501f, 0.68927002f, 0.00469971f, -0.00723267f, 0.30633545f,
0.69485474f, 0.00592041f, -0.00701904f, 0.29934692f, 0.70031738f, 0.00720215f,
-0.00677490f, 0.29244995f, 0.70565796f, 0.00851440f, -0.00656128f, 0.28558350f,
0.71090698f, 0.00988770f, -0.00637817f, 0.27877808f, 0.71600342f, 0.01132202f,
-0.00616455f, 0.27203369f, 0.72100830f, 0.01281738f, -0.00592041f, 0.26535034f,
0.72586060f, 0.01440430f, -0.00570679f, 0.25872803f, 0.73059082f, 0.01605225f,
-0.00552368f, 0.25219727f, 0.73519897f, 0.01779175f, -0.00531006f, 0.24572754f,
0.73965454f, 0.01956177f, -0.00506592f, 0.23931885f, 0.74398804f, 0.02139282f,
-0.00485229f, 0.23300171f, 0.74816895f, 0.02331543f, -0.00466919f, 0.22674561f,
0.75219727f, 0.02529907f, -0.00445557f, 0.22055054f, 0.75610352f, 0.02737427f,
-0.00427246f, 0.21444702f, 0.75985718f, 0.02951050f, -0.00405884f, 0.20843506f,
0.76348877f, 0.03173828f, -0.00387573f, 0.20248413f, 0.76693726f, 0.03402710f,
-0.00369263f, 0.19659424f, 0.77023315f, 0.03640747f, -0.00350952f, 0.19082642f,
0.77337646f, 0.03887939f, -0.00332642f, 0.18511963f, 0.77636719f, 0.04141235f,
-0.00314331f, 0.17950439f, 0.77920532f, 0.04400635f, -0.00299072f, 0.17398071f,
0.78189087f, 0.04672241f, -0.00280762f, 0.16851807f, 0.78442383f, 0.04949951f,
-0.00265503f, 0.16317749f, 0.78674316f, 0.05236816f, -0.00247192f, 0.15789795f,
0.78894043f, 0.05532837f, -0.00231934f, 0.15270996f, 0.79098511f, 0.05834961f,
-0.00219727f, 0.14761353f, 0.79284668f, 0.06149292f, -0.00204468f, 0.14260864f,
0.79452515f, 0.06469727f, -0.00192261f, 0.13769531f, 0.79605103f, 0.06796265f,
-0.00177002f, 0.13287354f, 0.79742432f, 0.07135010f, -0.00164795f, 0.12814331f,
0.79861450f, 0.07479858f, -0.00152588f, 0.12347412f, 0.79962158f, 0.07836914f,
-0.00140381f, 0.11892700f, 0.80047607f, 0.08203125f, -0.00128174f, 0.11447144f,
0.80117798f, 0.08578491f, -0.00115967f, 0.11010742f, 0.80169678f, 0.08959961f,
-0.00103760f, 0.10580444f, 0.80203247f, 0.09350586f, -0.00097656f, 0.10159302f,
0.80221558f, 0.09750366f,
};
const auto get_lut = [&]() -> std::span<const f32> {
if (sample_rate_ratio <= 1.0f) {
return std::span<const f32>(lut2.data(), lut2.size());
} else if (sample_rate_ratio < 1.3f) {
return std::span<const f32>(lut1.data(), lut1.size());
} else {
return std::span<const f32>(lut0.data(), lut0.size());
}
};
auto lut{get_lut()};
u32 read_index{0};
for (u32 i = 0; i < samples_to_write; i++) {
const auto lut_index{(fraction.get_frac() >> 8) * 4};
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
output[i] = (sample0 + sample1 + sample2 + sample3).to_int_floor();
fraction += sample_rate_ratio;
read_index += static_cast<u32>(fraction.to_int_floor());
fraction.clear_int();
}
}
static void ResampleHighQuality(std::span<s32> output, std::span<const s16> input,
const Common::FixedPoint<49, 15>& sample_rate_ratio,
Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write) {
static constexpr std::array<f32, 1024> lut0 = {
-0.01776123f, -0.00070190f, 0.26672363f, 0.50006104f, 0.26956177f, 0.00024414f,
-0.01800537f, 0.00000000f, -0.01748657f, -0.00164795f, 0.26388550f, 0.50003052f,
0.27236938f, 0.00122070f, -0.01824951f, -0.00003052f, -0.01724243f, -0.00256348f,
0.26107788f, 0.49996948f, 0.27520752f, 0.00219727f, -0.01849365f, -0.00003052f,
-0.01699829f, -0.00344849f, 0.25823975f, 0.49984741f, 0.27801514f, 0.00320435f,
-0.01873779f, -0.00006104f, -0.01675415f, -0.00433350f, 0.25543213f, 0.49972534f,
0.28085327f, 0.00424194f, -0.01898193f, -0.00006104f, -0.01651001f, -0.00518799f,
0.25259399f, 0.49954224f, 0.28366089f, 0.00527954f, -0.01922607f, -0.00009155f,
-0.01626587f, -0.00604248f, 0.24978638f, 0.49932861f, 0.28646851f, 0.00634766f,
-0.01947021f, -0.00012207f, -0.01602173f, -0.00686646f, 0.24697876f, 0.49908447f,
0.28930664f, 0.00744629f, -0.01971436f, -0.00015259f, -0.01574707f, -0.00765991f,
0.24414062f, 0.49877930f, 0.29211426f, 0.00854492f, -0.01995850f, -0.00015259f,
-0.01550293f, -0.00845337f, 0.24133301f, 0.49847412f, 0.29492188f, 0.00967407f,
-0.02020264f, -0.00018311f, -0.01525879f, -0.00921631f, 0.23852539f, 0.49810791f,
0.29772949f, 0.01083374f, -0.02044678f, -0.00021362f, -0.01501465f, -0.00997925f,
0.23571777f, 0.49774170f, 0.30050659f, 0.01199341f, -0.02069092f, -0.00024414f,
-0.01477051f, -0.01071167f, 0.23291016f, 0.49731445f, 0.30331421f, 0.01318359f,
-0.02093506f, -0.00027466f, -0.01452637f, -0.01141357f, 0.23010254f, 0.49685669f,
0.30609131f, 0.01437378f, -0.02117920f, -0.00030518f, -0.01428223f, -0.01211548f,
0.22732544f, 0.49636841f, 0.30886841f, 0.01559448f, -0.02142334f, -0.00033569f,
-0.01403809f, -0.01278687f, 0.22451782f, 0.49581909f, 0.31164551f, 0.01684570f,
-0.02163696f, -0.00039673f, -0.01379395f, -0.01345825f, 0.22174072f, 0.49526978f,
0.31442261f, 0.01809692f, -0.02188110f, -0.00042725f, -0.01358032f, -0.01409912f,
0.21896362f, 0.49465942f, 0.31719971f, 0.01937866f, -0.02209473f, -0.00045776f,
-0.01333618f, -0.01473999f, 0.21618652f, 0.49404907f, 0.31994629f, 0.02069092f,
-0.02233887f, -0.00048828f, -0.01309204f, -0.01535034f, 0.21343994f, 0.49337769f,
0.32269287f, 0.02203369f, -0.02255249f, -0.00054932f, -0.01284790f, -0.01596069f,
0.21066284f, 0.49267578f, 0.32543945f, 0.02337646f, -0.02279663f, -0.00057983f,
-0.01263428f, -0.01654053f, 0.20791626f, 0.49194336f, 0.32818604f, 0.02471924f,
-0.02301025f, -0.00064087f, -0.01239014f, -0.01708984f, 0.20516968f, 0.49118042f,
0.33090210f, 0.02612305f, -0.02322388f, -0.00067139f, -0.01214600f, -0.01763916f,
0.20242310f, 0.49035645f, 0.33361816f, 0.02752686f, -0.02343750f, -0.00073242f,
-0.01193237f, -0.01818848f, 0.19970703f, 0.48953247f, 0.33633423f, 0.02896118f,
-0.02365112f, -0.00079346f, -0.01168823f, -0.01867676f, 0.19696045f, 0.48864746f,
0.33901978f, 0.03039551f, -0.02386475f, -0.00082397f, -0.01147461f, -0.01919556f,
0.19427490f, 0.48776245f, 0.34170532f, 0.03186035f, -0.02407837f, -0.00088501f,
-0.01123047f, -0.01968384f, 0.19155884f, 0.48681641f, 0.34439087f, 0.03335571f,
-0.02429199f, -0.00094604f, -0.01101685f, -0.02014160f, 0.18887329f, 0.48583984f,
0.34704590f, 0.03485107f, -0.02447510f, -0.00100708f, -0.01080322f, -0.02059937f,
0.18615723f, 0.48483276f, 0.34970093f, 0.03637695f, -0.02468872f, -0.00106812f,
-0.01058960f, -0.02102661f, 0.18350220f, 0.48379517f, 0.35235596f, 0.03793335f,
-0.02487183f, -0.00112915f, -0.01034546f, -0.02145386f, 0.18081665f, 0.48272705f,
0.35498047f, 0.03948975f, -0.02505493f, -0.00119019f, -0.01013184f, -0.02188110f,
0.17816162f, 0.48162842f, 0.35760498f, 0.04107666f, -0.02523804f, -0.00125122f,
-0.00991821f, -0.02227783f, 0.17550659f, 0.48049927f, 0.36019897f, 0.04269409f,
-0.02542114f, -0.00131226f, -0.00970459f, -0.02264404f, 0.17288208f, 0.47933960f,
0.36279297f, 0.04431152f, -0.02560425f, -0.00140381f, -0.00952148f, -0.02301025f,
0.17025757f, 0.47814941f, 0.36538696f, 0.04595947f, -0.02578735f, -0.00146484f,
-0.00930786f, -0.02337646f, 0.16763306f, 0.47689819f, 0.36795044f, 0.04763794f,
-0.02593994f, -0.00152588f, -0.00909424f, -0.02371216f, 0.16503906f, 0.47564697f,
0.37048340f, 0.04931641f, -0.02609253f, -0.00161743f, -0.00888062f, -0.02401733f,
0.16244507f, 0.47436523f, 0.37304688f, 0.05102539f, -0.02627563f, -0.00170898f,
-0.00869751f, -0.02435303f, 0.15988159f, 0.47302246f, 0.37554932f, 0.05276489f,
-0.02642822f, -0.00177002f, -0.00848389f, -0.02462769f, 0.15731812f, 0.47167969f,
0.37805176f, 0.05450439f, -0.02658081f, -0.00186157f, -0.00830078f, -0.02493286f,
0.15475464f, 0.47027588f, 0.38055420f, 0.05627441f, -0.02670288f, -0.00195312f,
-0.00808716f, -0.02520752f, 0.15222168f, 0.46887207f, 0.38302612f, 0.05804443f,
-0.02685547f, -0.00204468f, -0.00790405f, -0.02545166f, 0.14968872f, 0.46743774f,
0.38546753f, 0.05987549f, -0.02697754f, -0.00213623f, -0.00772095f, -0.02569580f,
0.14718628f, 0.46594238f, 0.38790894f, 0.06170654f, -0.02709961f, -0.00222778f,
-0.00753784f, -0.02593994f, 0.14468384f, 0.46444702f, 0.39031982f, 0.06353760f,
-0.02722168f, -0.00231934f, -0.00735474f, -0.02615356f, 0.14218140f, 0.46289062f,
0.39273071f, 0.06539917f, -0.02734375f, -0.00241089f, -0.00717163f, -0.02636719f,
0.13970947f, 0.46133423f, 0.39511108f, 0.06729126f, -0.02743530f, -0.00250244f,
-0.00698853f, -0.02655029f, 0.13726807f, 0.45974731f, 0.39749146f, 0.06918335f,
-0.02755737f, -0.00259399f, -0.00680542f, -0.02673340f, 0.13479614f, 0.45812988f,
0.39984131f, 0.07113647f, -0.02764893f, -0.00271606f, -0.00662231f, -0.02691650f,
0.13238525f, 0.45648193f, 0.40216064f, 0.07305908f, -0.02774048f, -0.00280762f,
-0.00643921f, -0.02706909f, 0.12997437f, 0.45480347f, 0.40447998f, 0.07504272f,
-0.02780151f, -0.00292969f, -0.00628662f, -0.02722168f, 0.12756348f, 0.45309448f,
0.40676880f, 0.07699585f, -0.02789307f, -0.00305176f, -0.00610352f, -0.02734375f,
0.12518311f, 0.45135498f, 0.40902710f, 0.07901001f, -0.02795410f, -0.00314331f,
-0.00595093f, -0.02746582f, 0.12280273f, 0.44958496f, 0.41128540f, 0.08102417f,
-0.02801514f, -0.00326538f, -0.00579834f, -0.02758789f, 0.12045288f, 0.44778442f,
0.41351318f, 0.08306885f, -0.02804565f, -0.00338745f, -0.00561523f, -0.02770996f,
0.11813354f, 0.44598389f, 0.41571045f, 0.08511353f, -0.02810669f, -0.00350952f,
-0.00546265f, -0.02780151f, 0.11581421f, 0.44412231f, 0.41787720f, 0.08718872f,
-0.02813721f, -0.00363159f, -0.00531006f, -0.02786255f, 0.11349487f, 0.44226074f,
0.42004395f, 0.08929443f, -0.02816772f, -0.00375366f, -0.00515747f, -0.02795410f,
0.11120605f, 0.44036865f, 0.42218018f, 0.09140015f, -0.02816772f, -0.00387573f,
-0.00500488f, -0.02801514f, 0.10894775f, 0.43844604f, 0.42431641f, 0.09353638f,
-0.02819824f, -0.00402832f, -0.00485229f, -0.02807617f, 0.10668945f, 0.43649292f,
0.42639160f, 0.09570312f, -0.02819824f, -0.00415039f, -0.00469971f, -0.02810669f,
0.10446167f, 0.43453979f, 0.42846680f, 0.09786987f, -0.02819824f, -0.00427246f,
-0.00457764f, -0.02813721f, 0.10223389f, 0.43252563f, 0.43051147f, 0.10003662f,
-0.02816772f, -0.00442505f, -0.00442505f, -0.02816772f, 0.10003662f, 0.43051147f,
0.43252563f, 0.10223389f, -0.02813721f, -0.00457764f, -0.00427246f, -0.02819824f,
0.09786987f, 0.42846680f, 0.43453979f, 0.10446167f, -0.02810669f, -0.00469971f,
-0.00415039f, -0.02819824f, 0.09570312f, 0.42639160f, 0.43649292f, 0.10668945f,
-0.02807617f, -0.00485229f, -0.00402832f, -0.02819824f, 0.09353638f, 0.42431641f,
0.43844604f, 0.10894775f, -0.02801514f, -0.00500488f, -0.00387573f, -0.02816772f,
0.09140015f, 0.42218018f, 0.44036865f, 0.11120605f, -0.02795410f, -0.00515747f,
-0.00375366f, -0.02816772f, 0.08929443f, 0.42004395f, 0.44226074f, 0.11349487f,
-0.02786255f, -0.00531006f, -0.00363159f, -0.02813721f, 0.08718872f, 0.41787720f,
0.44412231f, 0.11581421f, -0.02780151f, -0.00546265f, -0.00350952f, -0.02810669f,
0.08511353f, 0.41571045f, 0.44598389f, 0.11813354f, -0.02770996f, -0.00561523f,
-0.00338745f, -0.02804565f, 0.08306885f, 0.41351318f, 0.44778442f, 0.12045288f,
-0.02758789f, -0.00579834f, -0.00326538f, -0.02801514f, 0.08102417f, 0.41128540f,
0.44958496f, 0.12280273f, -0.02746582f, -0.00595093f, -0.00314331f, -0.02795410f,
0.07901001f, 0.40902710f, 0.45135498f, 0.12518311f, -0.02734375f, -0.00610352f,
-0.00305176f, -0.02789307f, 0.07699585f, 0.40676880f, 0.45309448f, 0.12756348f,
-0.02722168f, -0.00628662f, -0.00292969f, -0.02780151f, 0.07504272f, 0.40447998f,
0.45480347f, 0.12997437f, -0.02706909f, -0.00643921f, -0.00280762f, -0.02774048f,
0.07305908f, 0.40216064f, 0.45648193f, 0.13238525f, -0.02691650f, -0.00662231f,
-0.00271606f, -0.02764893f, 0.07113647f, 0.39984131f, 0.45812988f, 0.13479614f,
-0.02673340f, -0.00680542f, -0.00259399f, -0.02755737f, 0.06918335f, 0.39749146f,
0.45974731f, 0.13726807f, -0.02655029f, -0.00698853f, -0.00250244f, -0.02743530f,
0.06729126f, 0.39511108f, 0.46133423f, 0.13970947f, -0.02636719f, -0.00717163f,
-0.00241089f, -0.02734375f, 0.06539917f, 0.39273071f, 0.46289062f, 0.14218140f,
-0.02615356f, -0.00735474f, -0.00231934f, -0.02722168f, 0.06353760f, 0.39031982f,
0.46444702f, 0.14468384f, -0.02593994f, -0.00753784f, -0.00222778f, -0.02709961f,
0.06170654f, 0.38790894f, 0.46594238f, 0.14718628f, -0.02569580f, -0.00772095f,
-0.00213623f, -0.02697754f, 0.05987549f, 0.38546753f, 0.46743774f, 0.14968872f,
-0.02545166f, -0.00790405f, -0.00204468f, -0.02685547f, 0.05804443f, 0.38302612f,
0.46887207f, 0.15222168f, -0.02520752f, -0.00808716f, -0.00195312f, -0.02670288f,
0.05627441f, 0.38055420f, 0.47027588f, 0.15475464f, -0.02493286f, -0.00830078f,
-0.00186157f, -0.02658081f, 0.05450439f, 0.37805176f, 0.47167969f, 0.15731812f,
-0.02462769f, -0.00848389f, -0.00177002f, -0.02642822f, 0.05276489f, 0.37554932f,
0.47302246f, 0.15988159f, -0.02435303f, -0.00869751f, -0.00170898f, -0.02627563f,
0.05102539f, 0.37304688f, 0.47436523f, 0.16244507f, -0.02401733f, -0.00888062f,
-0.00161743f, -0.02609253f, 0.04931641f, 0.37048340f, 0.47564697f, 0.16503906f,
-0.02371216f, -0.00909424f, -0.00152588f, -0.02593994f, 0.04763794f, 0.36795044f,
0.47689819f, 0.16763306f, -0.02337646f, -0.00930786f, -0.00146484f, -0.02578735f,
0.04595947f, 0.36538696f, 0.47814941f, 0.17025757f, -0.02301025f, -0.00952148f,
-0.00140381f, -0.02560425f, 0.04431152f, 0.36279297f, 0.47933960f, 0.17288208f,
-0.02264404f, -0.00970459f, -0.00131226f, -0.02542114f, 0.04269409f, 0.36019897f,
0.48049927f, 0.17550659f, -0.02227783f, -0.00991821f, -0.00125122f, -0.02523804f,
0.04107666f, 0.35760498f, 0.48162842f, 0.17816162f, -0.02188110f, -0.01013184f,
-0.00119019f, -0.02505493f, 0.03948975f, 0.35498047f, 0.48272705f, 0.18081665f,
-0.02145386f, -0.01034546f, -0.00112915f, -0.02487183f, 0.03793335f, 0.35235596f,
0.48379517f, 0.18350220f, -0.02102661f, -0.01058960f, -0.00106812f, -0.02468872f,
0.03637695f, 0.34970093f, 0.48483276f, 0.18615723f, -0.02059937f, -0.01080322f,
-0.00100708f, -0.02447510f, 0.03485107f, 0.34704590f, 0.48583984f, 0.18887329f,
-0.02014160f, -0.01101685f, -0.00094604f, -0.02429199f, 0.03335571f, 0.34439087f,
0.48681641f, 0.19155884f, -0.01968384f, -0.01123047f, -0.00088501f, -0.02407837f,
0.03186035f, 0.34170532f, 0.48776245f, 0.19427490f, -0.01919556f, -0.01147461f,
-0.00082397f, -0.02386475f, 0.03039551f, 0.33901978f, 0.48864746f, 0.19696045f,
-0.01867676f, -0.01168823f, -0.00079346f, -0.02365112f, 0.02896118f, 0.33633423f,
0.48953247f, 0.19970703f, -0.01818848f, -0.01193237f, -0.00073242f, -0.02343750f,
0.02752686f, 0.33361816f, 0.49035645f, 0.20242310f, -0.01763916f, -0.01214600f,
-0.00067139f, -0.02322388f, 0.02612305f, 0.33090210f, 0.49118042f, 0.20516968f,
-0.01708984f, -0.01239014f, -0.00064087f, -0.02301025f, 0.02471924f, 0.32818604f,
0.49194336f, 0.20791626f, -0.01654053f, -0.01263428f, -0.00057983f, -0.02279663f,
0.02337646f, 0.32543945f, 0.49267578f, 0.21066284f, -0.01596069f, -0.01284790f,
-0.00054932f, -0.02255249f, 0.02203369f, 0.32269287f, 0.49337769f, 0.21343994f,
-0.01535034f, -0.01309204f, -0.00048828f, -0.02233887f, 0.02069092f, 0.31994629f,
0.49404907f, 0.21618652f, -0.01473999f, -0.01333618f, -0.00045776f, -0.02209473f,
0.01937866f, 0.31719971f, 0.49465942f, 0.21896362f, -0.01409912f, -0.01358032f,
-0.00042725f, -0.02188110f, 0.01809692f, 0.31442261f, 0.49526978f, 0.22174072f,
-0.01345825f, -0.01379395f, -0.00039673f, -0.02163696f, 0.01684570f, 0.31164551f,
0.49581909f, 0.22451782f, -0.01278687f, -0.01403809f, -0.00033569f, -0.02142334f,
0.01559448f, 0.30886841f, 0.49636841f, 0.22732544f, -0.01211548f, -0.01428223f,
-0.00030518f, -0.02117920f, 0.01437378f, 0.30609131f, 0.49685669f, 0.23010254f,
-0.01141357f, -0.01452637f, -0.00027466f, -0.02093506f, 0.01318359f, 0.30331421f,
0.49731445f, 0.23291016f, -0.01071167f, -0.01477051f, -0.00024414f, -0.02069092f,
0.01199341f, 0.30050659f, 0.49774170f, 0.23571777f, -0.00997925f, -0.01501465f,
-0.00021362f, -0.02044678f, 0.01083374f, 0.29772949f, 0.49810791f, 0.23852539f,
-0.00921631f, -0.01525879f, -0.00018311f, -0.02020264f, 0.00967407f, 0.29492188f,
0.49847412f, 0.24133301f, -0.00845337f, -0.01550293f, -0.00015259f, -0.01995850f,
0.00854492f, 0.29211426f, 0.49877930f, 0.24414062f, -0.00765991f, -0.01574707f,
-0.00015259f, -0.01971436f, 0.00744629f, 0.28930664f, 0.49908447f, 0.24697876f,
-0.00686646f, -0.01602173f, -0.00012207f, -0.01947021f, 0.00634766f, 0.28646851f,
0.49932861f, 0.24978638f, -0.00604248f, -0.01626587f, -0.00009155f, -0.01922607f,
0.00527954f, 0.28366089f, 0.49954224f, 0.25259399f, -0.00518799f, -0.01651001f,
-0.00006104f, -0.01898193f, 0.00424194f, 0.28085327f, 0.49972534f, 0.25543213f,
-0.00433350f, -0.01675415f, -0.00006104f, -0.01873779f, 0.00320435f, 0.27801514f,
0.49984741f, 0.25823975f, -0.00344849f, -0.01699829f, -0.00003052f, -0.01849365f,
0.00219727f, 0.27520752f, 0.49996948f, 0.26107788f, -0.00256348f, -0.01724243f,
-0.00003052f, -0.01824951f, 0.00122070f, 0.27236938f, 0.50003052f, 0.26388550f,
-0.00164795f, -0.01748657f, 0.00000000f, -0.01800537f, 0.00024414f, 0.26956177f,
0.50006104f, 0.26672363f, -0.00070190f, -0.01776123f,
};
static constexpr std::array<f32, 1024> lut1 = {
0.01275635f, -0.07745361f, 0.18670654f, 0.75119019f, 0.19219971f, -0.07821655f,
0.01272583f, 0.00000000f, 0.01281738f, -0.07666016f, 0.18124390f, 0.75106812f,
0.19772339f, -0.07897949f, 0.01266479f, 0.00003052f, 0.01284790f, -0.07583618f,
0.17581177f, 0.75088501f, 0.20330811f, -0.07971191f, 0.01257324f, 0.00006104f,
0.01287842f, -0.07501221f, 0.17044067f, 0.75057983f, 0.20892334f, -0.08041382f,
0.01248169f, 0.00009155f, 0.01290894f, -0.07415771f, 0.16510010f, 0.75018311f,
0.21453857f, -0.08111572f, 0.01239014f, 0.00012207f, 0.01290894f, -0.07330322f,
0.15979004f, 0.74966431f, 0.22021484f, -0.08178711f, 0.01229858f, 0.00015259f,
0.01290894f, -0.07241821f, 0.15454102f, 0.74908447f, 0.22592163f, -0.08242798f,
0.01217651f, 0.00018311f, 0.01290894f, -0.07150269f, 0.14932251f, 0.74838257f,
0.23165894f, -0.08303833f, 0.01205444f, 0.00021362f, 0.01290894f, -0.07058716f,
0.14416504f, 0.74755859f, 0.23742676f, -0.08364868f, 0.01193237f, 0.00024414f,
0.01287842f, -0.06967163f, 0.13903809f, 0.74667358f, 0.24322510f, -0.08419800f,
0.01177979f, 0.00027466f, 0.01284790f, -0.06872559f, 0.13397217f, 0.74566650f,
0.24905396f, -0.08474731f, 0.01162720f, 0.00033569f, 0.01281738f, -0.06777954f,
0.12893677f, 0.74456787f, 0.25491333f, -0.08526611f, 0.01147461f, 0.00036621f,
0.01278687f, -0.06683350f, 0.12396240f, 0.74337769f, 0.26077271f, -0.08575439f,
0.01129150f, 0.00042725f, 0.01275635f, -0.06585693f, 0.11901855f, 0.74206543f,
0.26669312f, -0.08621216f, 0.01110840f, 0.00045776f, 0.01269531f, -0.06488037f,
0.11413574f, 0.74069214f, 0.27261353f, -0.08663940f, 0.01092529f, 0.00051880f,
0.01263428f, -0.06387329f, 0.10931396f, 0.73919678f, 0.27853394f, -0.08700562f,
0.01071167f, 0.00057983f, 0.01257324f, -0.06286621f, 0.10452271f, 0.73760986f,
0.28451538f, -0.08737183f, 0.01049805f, 0.00064087f, 0.01251221f, -0.06185913f,
0.09979248f, 0.73593140f, 0.29049683f, -0.08770752f, 0.01025391f, 0.00067139f,
0.01242065f, -0.06082153f, 0.09512329f, 0.73413086f, 0.29647827f, -0.08801270f,
0.01000977f, 0.00073242f, 0.01232910f, -0.05981445f, 0.09051514f, 0.73226929f,
0.30249023f, -0.08828735f, 0.00973511f, 0.00079346f, 0.01226807f, -0.05877686f,
0.08593750f, 0.73028564f, 0.30853271f, -0.08850098f, 0.00949097f, 0.00088501f,
0.01214600f, -0.05773926f, 0.08142090f, 0.72824097f, 0.31457520f, -0.08871460f,
0.00918579f, 0.00094604f, 0.01205444f, -0.05670166f, 0.07696533f, 0.72607422f,
0.32061768f, -0.08886719f, 0.00891113f, 0.00100708f, 0.01196289f, -0.05563354f,
0.07257080f, 0.72381592f, 0.32669067f, -0.08898926f, 0.00860596f, 0.00106812f,
0.01187134f, -0.05459595f, 0.06820679f, 0.72146606f, 0.33276367f, -0.08908081f,
0.00827026f, 0.00115967f, 0.01174927f, -0.05352783f, 0.06393433f, 0.71902466f,
0.33883667f, -0.08911133f, 0.00796509f, 0.00122070f, 0.01162720f, -0.05245972f,
0.05969238f, 0.71649170f, 0.34494019f, -0.08914185f, 0.00759888f, 0.00131226f,
0.01150513f, -0.05139160f, 0.05551147f, 0.71389771f, 0.35101318f, -0.08911133f,
0.00726318f, 0.00137329f, 0.01138306f, -0.05032349f, 0.05139160f, 0.71118164f,
0.35711670f, -0.08901978f, 0.00686646f, 0.00146484f, 0.01126099f, -0.04928589f,
0.04733276f, 0.70837402f, 0.36322021f, -0.08892822f, 0.00650024f, 0.00155640f,
0.01113892f, -0.04821777f, 0.04333496f, 0.70550537f, 0.36932373f, -0.08877563f,
0.00610352f, 0.00164795f, 0.01101685f, -0.04714966f, 0.03939819f, 0.70251465f,
0.37542725f, -0.08856201f, 0.00567627f, 0.00173950f, 0.01086426f, -0.04608154f,
0.03549194f, 0.69946289f, 0.38153076f, -0.08834839f, 0.00527954f, 0.00183105f,
0.01074219f, -0.04501343f, 0.03167725f, 0.69631958f, 0.38763428f, -0.08804321f,
0.00482178f, 0.00192261f, 0.01058960f, -0.04394531f, 0.02792358f, 0.69308472f,
0.39370728f, -0.08773804f, 0.00436401f, 0.00201416f, 0.01043701f, -0.04287720f,
0.02420044f, 0.68975830f, 0.39981079f, -0.08737183f, 0.00390625f, 0.00210571f,
0.01031494f, -0.04180908f, 0.02056885f, 0.68637085f, 0.40588379f, -0.08694458f,
0.00344849f, 0.00222778f, 0.01016235f, -0.04074097f, 0.01699829f, 0.68289185f,
0.41195679f, -0.08648682f, 0.00296021f, 0.00231934f, 0.01000977f, -0.03970337f,
0.01345825f, 0.67932129f, 0.41802979f, -0.08596802f, 0.00244141f, 0.00244141f,
0.00985718f, -0.03863525f, 0.01000977f, 0.67568970f, 0.42407227f, -0.08541870f,
0.00192261f, 0.00253296f, 0.00970459f, -0.03759766f, 0.00662231f, 0.67196655f,
0.43011475f, -0.08480835f, 0.00140381f, 0.00265503f, 0.00955200f, -0.03652954f,
0.00326538f, 0.66815186f, 0.43612671f, -0.08416748f, 0.00085449f, 0.00277710f,
0.00936890f, -0.03549194f, 0.00000000f, 0.66427612f, 0.44213867f, -0.08346558f,
0.00027466f, 0.00289917f, 0.00921631f, -0.03445435f, -0.00320435f, 0.66030884f,
0.44812012f, -0.08270264f, -0.00027466f, 0.00299072f, 0.00906372f, -0.03344727f,
-0.00634766f, 0.65631104f, 0.45407104f, -0.08190918f, -0.00088501f, 0.00311279f,
0.00891113f, -0.03240967f, -0.00946045f, 0.65219116f, 0.46002197f, -0.08105469f,
-0.00146484f, 0.00323486f, 0.00872803f, -0.03140259f, -0.01248169f, 0.64801025f,
0.46594238f, -0.08013916f, -0.00210571f, 0.00338745f, 0.00857544f, -0.03039551f,
-0.01544189f, 0.64376831f, 0.47183228f, -0.07919312f, -0.00271606f, 0.00350952f,
0.00842285f, -0.02938843f, -0.01834106f, 0.63946533f, 0.47772217f, -0.07818604f,
-0.00335693f, 0.00363159f, 0.00823975f, -0.02838135f, -0.02117920f, 0.63507080f,
0.48358154f, -0.07711792f, -0.00402832f, 0.00375366f, 0.00808716f, -0.02740479f,
-0.02395630f, 0.63061523f, 0.48937988f, -0.07598877f, -0.00469971f, 0.00390625f,
0.00793457f, -0.02642822f, -0.02667236f, 0.62609863f, 0.49517822f, -0.07482910f,
-0.00537109f, 0.00402832f, 0.00775146f, -0.02545166f, -0.02932739f, 0.62152100f,
0.50094604f, -0.07357788f, -0.00607300f, 0.00418091f, 0.00759888f, -0.02450562f,
-0.03192139f, 0.61685181f, 0.50665283f, -0.07229614f, -0.00677490f, 0.00430298f,
0.00741577f, -0.02352905f, -0.03445435f, 0.61215210f, 0.51235962f, -0.07098389f,
-0.00750732f, 0.00445557f, 0.00726318f, -0.02258301f, -0.03689575f, 0.60736084f,
0.51800537f, -0.06958008f, -0.00823975f, 0.00460815f, 0.00711060f, -0.02166748f,
-0.03930664f, 0.60253906f, 0.52362061f, -0.06811523f, -0.00897217f, 0.00476074f,
0.00692749f, -0.02075195f, -0.04165649f, 0.59762573f, 0.52920532f, -0.06661987f,
-0.00973511f, 0.00488281f, 0.00677490f, -0.01983643f, -0.04394531f, 0.59268188f,
0.53475952f, -0.06506348f, -0.01052856f, 0.00503540f, 0.00662231f, -0.01892090f,
-0.04617310f, 0.58767700f, 0.54025269f, -0.06344604f, -0.01129150f, 0.00518799f,
0.00643921f, -0.01803589f, -0.04830933f, 0.58261108f, 0.54571533f, -0.06173706f,
-0.01208496f, 0.00534058f, 0.00628662f, -0.01715088f, -0.05041504f, 0.57748413f,
0.55111694f, -0.05999756f, -0.01290894f, 0.00549316f, 0.00613403f, -0.01626587f,
-0.05245972f, 0.57232666f, 0.55648804f, -0.05819702f, -0.01373291f, 0.00564575f,
0.00598145f, -0.01541138f, -0.05444336f, 0.56707764f, 0.56182861f, -0.05636597f,
-0.01455688f, 0.00582886f, 0.00582886f, -0.01455688f, -0.05636597f, 0.56182861f,
0.56707764f, -0.05444336f, -0.01541138f, 0.00598145f, 0.00564575f, -0.01373291f,
-0.05819702f, 0.55648804f, 0.57232666f, -0.05245972f, -0.01626587f, 0.00613403f,
0.00549316f, -0.01290894f, -0.05999756f, 0.55111694f, 0.57748413f, -0.05041504f,
-0.01715088f, 0.00628662f, 0.00534058f, -0.01208496f, -0.06173706f, 0.54571533f,
0.58261108f, -0.04830933f, -0.01803589f, 0.00643921f, 0.00518799f, -0.01129150f,
-0.06344604f, 0.54025269f, 0.58767700f, -0.04617310f, -0.01892090f, 0.00662231f,
0.00503540f, -0.01052856f, -0.06506348f, 0.53475952f, 0.59268188f, -0.04394531f,
-0.01983643f, 0.00677490f, 0.00488281f, -0.00973511f, -0.06661987f, 0.52920532f,
0.59762573f, -0.04165649f, -0.02075195f, 0.00692749f, 0.00476074f, -0.00897217f,
-0.06811523f, 0.52362061f, 0.60253906f, -0.03930664f, -0.02166748f, 0.00711060f,
0.00460815f, -0.00823975f, -0.06958008f, 0.51800537f, 0.60736084f, -0.03689575f,
-0.02258301f, 0.00726318f, 0.00445557f, -0.00750732f, -0.07098389f, 0.51235962f,
0.61215210f, -0.03445435f, -0.02352905f, 0.00741577f, 0.00430298f, -0.00677490f,
-0.07229614f, 0.50665283f, 0.61685181f, -0.03192139f, -0.02450562f, 0.00759888f,
0.00418091f, -0.00607300f, -0.07357788f, 0.50094604f, 0.62152100f, -0.02932739f,
-0.02545166f, 0.00775146f, 0.00402832f, -0.00537109f, -0.07482910f, 0.49517822f,
0.62609863f, -0.02667236f, -0.02642822f, 0.00793457f, 0.00390625f, -0.00469971f,
-0.07598877f, 0.48937988f, 0.63061523f, -0.02395630f, -0.02740479f, 0.00808716f,
0.00375366f, -0.00402832f, -0.07711792f, 0.48358154f, 0.63507080f, -0.02117920f,
-0.02838135f, 0.00823975f, 0.00363159f, -0.00335693f, -0.07818604f, 0.47772217f,
0.63946533f, -0.01834106f, -0.02938843f, 0.00842285f, 0.00350952f, -0.00271606f,
-0.07919312f, 0.47183228f, 0.64376831f, -0.01544189f, -0.03039551f, 0.00857544f,
0.00338745f, -0.00210571f, -0.08013916f, 0.46594238f, 0.64801025f, -0.01248169f,
-0.03140259f, 0.00872803f, 0.00323486f, -0.00146484f, -0.08105469f, 0.46002197f,
0.65219116f, -0.00946045f, -0.03240967f, 0.00891113f, 0.00311279f, -0.00088501f,
-0.08190918f, 0.45407104f, 0.65631104f, -0.00634766f, -0.03344727f, 0.00906372f,
0.00299072f, -0.00027466f, -0.08270264f, 0.44812012f, 0.66030884f, -0.00320435f,
-0.03445435f, 0.00921631f, 0.00289917f, 0.00027466f, -0.08346558f, 0.44213867f,
0.66427612f, 0.00000000f, -0.03549194f, 0.00936890f, 0.00277710f, 0.00085449f,
-0.08416748f, 0.43612671f, 0.66815186f, 0.00326538f, -0.03652954f, 0.00955200f,
0.00265503f, 0.00140381f, -0.08480835f, 0.43011475f, 0.67196655f, 0.00662231f,
-0.03759766f, 0.00970459f, 0.00253296f, 0.00192261f, -0.08541870f, 0.42407227f,
0.67568970f, 0.01000977f, -0.03863525f, 0.00985718f, 0.00244141f, 0.00244141f,
-0.08596802f, 0.41802979f, 0.67932129f, 0.01345825f, -0.03970337f, 0.01000977f,
0.00231934f, 0.00296021f, -0.08648682f, 0.41195679f, 0.68289185f, 0.01699829f,
-0.04074097f, 0.01016235f, 0.00222778f, 0.00344849f, -0.08694458f, 0.40588379f,
0.68637085f, 0.02056885f, -0.04180908f, 0.01031494f, 0.00210571f, 0.00390625f,
-0.08737183f, 0.39981079f, 0.68975830f, 0.02420044f, -0.04287720f, 0.01043701f,
0.00201416f, 0.00436401f, -0.08773804f, 0.39370728f, 0.69308472f, 0.02792358f,
-0.04394531f, 0.01058960f, 0.00192261f, 0.00482178f, -0.08804321f, 0.38763428f,
0.69631958f, 0.03167725f, -0.04501343f, 0.01074219f, 0.00183105f, 0.00527954f,
-0.08834839f, 0.38153076f, 0.69946289f, 0.03549194f, -0.04608154f, 0.01086426f,
0.00173950f, 0.00567627f, -0.08856201f, 0.37542725f, 0.70251465f, 0.03939819f,
-0.04714966f, 0.01101685f, 0.00164795f, 0.00610352f, -0.08877563f, 0.36932373f,
0.70550537f, 0.04333496f, -0.04821777f, 0.01113892f, 0.00155640f, 0.00650024f,
-0.08892822f, 0.36322021f, 0.70837402f, 0.04733276f, -0.04928589f, 0.01126099f,
0.00146484f, 0.00686646f, -0.08901978f, 0.35711670f, 0.71118164f, 0.05139160f,
-0.05032349f, 0.01138306f, 0.00137329f, 0.00726318f, -0.08911133f, 0.35101318f,
0.71389771f, 0.05551147f, -0.05139160f, 0.01150513f, 0.00131226f, 0.00759888f,
-0.08914185f, 0.34494019f, 0.71649170f, 0.05969238f, -0.05245972f, 0.01162720f,
0.00122070f, 0.00796509f, -0.08911133f, 0.33883667f, 0.71902466f, 0.06393433f,
-0.05352783f, 0.01174927f, 0.00115967f, 0.00827026f, -0.08908081f, 0.33276367f,
0.72146606f, 0.06820679f, -0.05459595f, 0.01187134f, 0.00106812f, 0.00860596f,
-0.08898926f, 0.32669067f, 0.72381592f, 0.07257080f, -0.05563354f, 0.01196289f,
0.00100708f, 0.00891113f, -0.08886719f, 0.32061768f, 0.72607422f, 0.07696533f,
-0.05670166f, 0.01205444f, 0.00094604f, 0.00918579f, -0.08871460f, 0.31457520f,
0.72824097f, 0.08142090f, -0.05773926f, 0.01214600f, 0.00088501f, 0.00949097f,
-0.08850098f, 0.30853271f, 0.73028564f, 0.08593750f, -0.05877686f, 0.01226807f,
0.00079346f, 0.00973511f, -0.08828735f, 0.30249023f, 0.73226929f, 0.09051514f,
-0.05981445f, 0.01232910f, 0.00073242f, 0.01000977f, -0.08801270f, 0.29647827f,
0.73413086f, 0.09512329f, -0.06082153f, 0.01242065f, 0.00067139f, 0.01025391f,
-0.08770752f, 0.29049683f, 0.73593140f, 0.09979248f, -0.06185913f, 0.01251221f,
0.00064087f, 0.01049805f, -0.08737183f, 0.28451538f, 0.73760986f, 0.10452271f,
-0.06286621f, 0.01257324f, 0.00057983f, 0.01071167f, -0.08700562f, 0.27853394f,
0.73919678f, 0.10931396f, -0.06387329f, 0.01263428f, 0.00051880f, 0.01092529f,
-0.08663940f, 0.27261353f, 0.74069214f, 0.11413574f, -0.06488037f, 0.01269531f,
0.00045776f, 0.01110840f, -0.08621216f, 0.26669312f, 0.74206543f, 0.11901855f,
-0.06585693f, 0.01275635f, 0.00042725f, 0.01129150f, -0.08575439f, 0.26077271f,
0.74337769f, 0.12396240f, -0.06683350f, 0.01278687f, 0.00036621f, 0.01147461f,
-0.08526611f, 0.25491333f, 0.74456787f, 0.12893677f, -0.06777954f, 0.01281738f,
0.00033569f, 0.01162720f, -0.08474731f, 0.24905396f, 0.74566650f, 0.13397217f,
-0.06872559f, 0.01284790f, 0.00027466f, 0.01177979f, -0.08419800f, 0.24322510f,
0.74667358f, 0.13903809f, -0.06967163f, 0.01287842f, 0.00024414f, 0.01193237f,
-0.08364868f, 0.23742676f, 0.74755859f, 0.14416504f, -0.07058716f, 0.01290894f,
0.00021362f, 0.01205444f, -0.08303833f, 0.23165894f, 0.74838257f, 0.14932251f,
-0.07150269f, 0.01290894f, 0.00018311f, 0.01217651f, -0.08242798f, 0.22592163f,
0.74908447f, 0.15454102f, -0.07241821f, 0.01290894f, 0.00015259f, 0.01229858f,
-0.08178711f, 0.22021484f, 0.74966431f, 0.15979004f, -0.07330322f, 0.01290894f,
0.00012207f, 0.01239014f, -0.08111572f, 0.21453857f, 0.75018311f, 0.16510010f,
-0.07415771f, 0.01290894f, 0.00009155f, 0.01248169f, -0.08041382f, 0.20892334f,
0.75057983f, 0.17044067f, -0.07501221f, 0.01287842f, 0.00006104f, 0.01257324f,
-0.07971191f, 0.20330811f, 0.75088501f, 0.17581177f, -0.07583618f, 0.01284790f,
0.00003052f, 0.01266479f, -0.07897949f, 0.19772339f, 0.75106812f, 0.18124390f,
-0.07666016f, 0.01281738f, 0.00000000f, 0.01272583f, -0.07821655f, 0.19219971f,
0.75119019f, 0.18670654f, -0.07745361f, 0.01275635f,
};
static constexpr std::array<f32, 1024> lut2 = {
-0.00036621f, 0.00143433f, -0.00408936f, 0.99996948f, 0.00247192f, -0.00048828f,
0.00006104f, 0.00000000f, -0.00079346f, 0.00329590f, -0.01052856f, 0.99975586f,
0.00918579f, -0.00241089f, 0.00051880f, -0.00003052f, -0.00122070f, 0.00512695f,
-0.01684570f, 0.99929810f, 0.01605225f, -0.00439453f, 0.00097656f, -0.00006104f,
-0.00161743f, 0.00689697f, -0.02297974f, 0.99862671f, 0.02304077f, -0.00640869f,
0.00143433f, -0.00009155f, -0.00201416f, 0.00866699f, -0.02899170f, 0.99774170f,
0.03018188f, -0.00845337f, 0.00192261f, -0.00015259f, -0.00238037f, 0.01037598f,
-0.03488159f, 0.99664307f, 0.03741455f, -0.01055908f, 0.00241089f, -0.00018311f,
-0.00274658f, 0.01202393f, -0.04061890f, 0.99533081f, 0.04483032f, -0.01266479f,
0.00292969f, -0.00024414f, -0.00308228f, 0.01364136f, -0.04620361f, 0.99377441f,
0.05233765f, -0.01483154f, 0.00344849f, -0.00027466f, -0.00341797f, 0.01522827f,
-0.05163574f, 0.99200439f, 0.05999756f, -0.01699829f, 0.00396729f, -0.00033569f,
-0.00375366f, 0.01678467f, -0.05691528f, 0.99002075f, 0.06777954f, -0.01922607f,
0.00451660f, -0.00039673f, -0.00405884f, 0.01828003f, -0.06207275f, 0.98782349f,
0.07568359f, -0.02145386f, 0.00506592f, -0.00042725f, -0.00436401f, 0.01971436f,
-0.06707764f, 0.98541260f, 0.08370972f, -0.02374268f, 0.00564575f, -0.00048828f,
-0.00463867f, 0.02114868f, -0.07192993f, 0.98278809f, 0.09185791f, -0.02603149f,
0.00622559f, -0.00054932f, -0.00494385f, 0.02252197f, -0.07666016f, 0.97991943f,
0.10012817f, -0.02835083f, 0.00680542f, -0.00061035f, -0.00518799f, 0.02383423f,
-0.08123779f, 0.97686768f, 0.10848999f, -0.03073120f, 0.00738525f, -0.00070190f,
-0.00543213f, 0.02511597f, -0.08566284f, 0.97360229f, 0.11700439f, -0.03308105f,
0.00799561f, -0.00076294f, -0.00567627f, 0.02636719f, -0.08993530f, 0.97012329f,
0.12561035f, -0.03549194f, 0.00860596f, -0.00082397f, -0.00592041f, 0.02755737f,
-0.09405518f, 0.96643066f, 0.13436890f, -0.03790283f, 0.00924683f, -0.00091553f,
-0.00613403f, 0.02868652f, -0.09805298f, 0.96252441f, 0.14318848f, -0.04034424f,
0.00985718f, -0.00097656f, -0.00631714f, 0.02981567f, -0.10189819f, 0.95843506f,
0.15213013f, -0.04281616f, 0.01049805f, -0.00106812f, -0.00653076f, 0.03085327f,
-0.10559082f, 0.95413208f, 0.16119385f, -0.04528809f, 0.01113892f, -0.00112915f,
-0.00671387f, 0.03189087f, -0.10916138f, 0.94961548f, 0.17034912f, -0.04779053f,
0.01181030f, -0.00122070f, -0.00686646f, 0.03286743f, -0.11254883f, 0.94491577f,
0.17959595f, -0.05029297f, 0.01248169f, -0.00131226f, -0.00701904f, 0.03378296f,
-0.11584473f, 0.94000244f, 0.18893433f, -0.05279541f, 0.01315308f, -0.00140381f,
-0.00717163f, 0.03466797f, -0.11895752f, 0.93490601f, 0.19839478f, -0.05532837f,
0.01382446f, -0.00149536f, -0.00732422f, 0.03552246f, -0.12194824f, 0.92962646f,
0.20791626f, -0.05786133f, 0.01449585f, -0.00158691f, -0.00744629f, 0.03631592f,
-0.12478638f, 0.92413330f, 0.21752930f, -0.06042480f, 0.01519775f, -0.00167847f,
-0.00753784f, 0.03707886f, -0.12750244f, 0.91848755f, 0.22723389f, -0.06298828f,
0.01586914f, -0.00177002f, -0.00765991f, 0.03781128f, -0.13006592f, 0.91262817f,
0.23703003f, -0.06555176f, 0.01657104f, -0.00189209f, -0.00775146f, 0.03848267f,
-0.13250732f, 0.90658569f, 0.24691772f, -0.06808472f, 0.01727295f, -0.00198364f,
-0.00784302f, 0.03909302f, -0.13479614f, 0.90036011f, 0.25683594f, -0.07064819f,
0.01797485f, -0.00210571f, -0.00790405f, 0.03970337f, -0.13696289f, 0.89395142f,
0.26687622f, -0.07321167f, 0.01870728f, -0.00219727f, -0.00796509f, 0.04025269f,
-0.13900757f, 0.88739014f, 0.27694702f, -0.07577515f, 0.01940918f, -0.00231934f,
-0.00802612f, 0.04077148f, -0.14089966f, 0.88064575f, 0.28710938f, -0.07833862f,
0.02011108f, -0.00244141f, -0.00808716f, 0.04122925f, -0.14263916f, 0.87374878f,
0.29733276f, -0.08090210f, 0.02084351f, -0.00253296f, -0.00811768f, 0.04165649f,
-0.14428711f, 0.86666870f, 0.30761719f, -0.08343506f, 0.02154541f, -0.00265503f,
-0.00814819f, 0.04205322f, -0.14578247f, 0.85940552f, 0.31793213f, -0.08596802f,
0.02227783f, -0.00277710f, -0.00814819f, 0.04238892f, -0.14715576f, 0.85202026f,
0.32833862f, -0.08847046f, 0.02297974f, -0.00289917f, -0.00817871f, 0.04272461f,
-0.14840698f, 0.84445190f, 0.33874512f, -0.09097290f, 0.02371216f, -0.00302124f,
-0.00817871f, 0.04299927f, -0.14953613f, 0.83673096f, 0.34924316f, -0.09347534f,
0.02441406f, -0.00314331f, -0.00817871f, 0.04321289f, -0.15054321f, 0.82888794f,
0.35977173f, -0.09594727f, 0.02514648f, -0.00326538f, -0.00814819f, 0.04342651f,
-0.15142822f, 0.82086182f, 0.37033081f, -0.09838867f, 0.02584839f, -0.00341797f,
-0.00814819f, 0.04357910f, -0.15219116f, 0.81271362f, 0.38092041f, -0.10079956f,
0.02655029f, -0.00354004f, -0.00811768f, 0.04373169f, -0.15283203f, 0.80441284f,
0.39154053f, -0.10321045f, 0.02725220f, -0.00366211f, -0.00808716f, 0.04382324f,
-0.15338135f, 0.79598999f, 0.40219116f, -0.10559082f, 0.02795410f, -0.00381470f,
-0.00805664f, 0.04388428f, -0.15377808f, 0.78741455f, 0.41287231f, -0.10794067f,
0.02865601f, -0.00393677f, -0.00799561f, 0.04388428f, -0.15408325f, 0.77871704f,
0.42358398f, -0.11026001f, 0.02935791f, -0.00405884f, -0.00793457f, 0.04388428f,
-0.15426636f, 0.76989746f, 0.43429565f, -0.11251831f, 0.03002930f, -0.00421143f,
-0.00787354f, 0.04385376f, -0.15435791f, 0.76095581f, 0.44500732f, -0.11477661f,
0.03070068f, -0.00433350f, -0.00781250f, 0.04379272f, -0.15435791f, 0.75192261f,
0.45574951f, -0.11697388f, 0.03137207f, -0.00448608f, -0.00775146f, 0.04367065f,
-0.15420532f, 0.74273682f, 0.46649170f, -0.11914062f, 0.03201294f, -0.00460815f,
-0.00769043f, 0.04354858f, -0.15399170f, 0.73345947f, 0.47723389f, -0.12127686f,
0.03268433f, -0.00473022f, -0.00759888f, 0.04339600f, -0.15365601f, 0.72406006f,
0.48794556f, -0.12335205f, 0.03329468f, -0.00488281f, -0.00750732f, 0.04321289f,
-0.15322876f, 0.71456909f, 0.49868774f, -0.12539673f, 0.03393555f, -0.00500488f,
-0.00741577f, 0.04296875f, -0.15270996f, 0.70498657f, 0.50936890f, -0.12738037f,
0.03454590f, -0.00515747f, -0.00732422f, 0.04272461f, -0.15209961f, 0.69528198f,
0.52008057f, -0.12930298f, 0.03515625f, -0.00527954f, -0.00723267f, 0.04248047f,
-0.15136719f, 0.68551636f, 0.53076172f, -0.13119507f, 0.03573608f, -0.00543213f,
-0.00714111f, 0.04217529f, -0.15057373f, 0.67565918f, 0.54138184f, -0.13299561f,
0.03631592f, -0.00555420f, -0.00701904f, 0.04183960f, -0.14968872f, 0.66571045f,
0.55200195f, -0.13476562f, 0.03689575f, -0.00567627f, -0.00692749f, 0.04150391f,
-0.14871216f, 0.65567017f, 0.56259155f, -0.13647461f, 0.03741455f, -0.00582886f,
-0.00680542f, 0.04113770f, -0.14767456f, 0.64556885f, 0.57315063f, -0.13812256f,
0.03796387f, -0.00595093f, -0.00668335f, 0.04074097f, -0.14651489f, 0.63540649f,
0.58364868f, -0.13970947f, 0.03845215f, -0.00607300f, -0.00656128f, 0.04031372f,
-0.14529419f, 0.62518311f, 0.59411621f, -0.14120483f, 0.03897095f, -0.00619507f,
-0.00643921f, 0.03988647f, -0.14401245f, 0.61486816f, 0.60452271f, -0.14263916f,
0.03942871f, -0.00631714f, -0.00631714f, 0.03942871f, -0.14263916f, 0.60452271f,
0.61486816f, -0.14401245f, 0.03988647f, -0.00643921f, -0.00619507f, 0.03897095f,
-0.14120483f, 0.59411621f, 0.62518311f, -0.14529419f, 0.04031372f, -0.00656128f,
-0.00607300f, 0.03845215f, -0.13970947f, 0.58364868f, 0.63540649f, -0.14651489f,
0.04074097f, -0.00668335f, -0.00595093f, 0.03796387f, -0.13812256f, 0.57315063f,
0.64556885f, -0.14767456f, 0.04113770f, -0.00680542f, -0.00582886f, 0.03741455f,
-0.13647461f, 0.56259155f, 0.65567017f, -0.14871216f, 0.04150391f, -0.00692749f,
-0.00567627f, 0.03689575f, -0.13476562f, 0.55200195f, 0.66571045f, -0.14968872f,
0.04183960f, -0.00701904f, -0.00555420f, 0.03631592f, -0.13299561f, 0.54138184f,
0.67565918f, -0.15057373f, 0.04217529f, -0.00714111f, -0.00543213f, 0.03573608f,
-0.13119507f, 0.53076172f, 0.68551636f, -0.15136719f, 0.04248047f, -0.00723267f,
-0.00527954f, 0.03515625f, -0.12930298f, 0.52008057f, 0.69528198f, -0.15209961f,
0.04272461f, -0.00732422f, -0.00515747f, 0.03454590f, -0.12738037f, 0.50936890f,
0.70498657f, -0.15270996f, 0.04296875f, -0.00741577f, -0.00500488f, 0.03393555f,
-0.12539673f, 0.49868774f, 0.71456909f, -0.15322876f, 0.04321289f, -0.00750732f,
-0.00488281f, 0.03329468f, -0.12335205f, 0.48794556f, 0.72406006f, -0.15365601f,
0.04339600f, -0.00759888f, -0.00473022f, 0.03268433f, -0.12127686f, 0.47723389f,
0.73345947f, -0.15399170f, 0.04354858f, -0.00769043f, -0.00460815f, 0.03201294f,
-0.11914062f, 0.46649170f, 0.74273682f, -0.15420532f, 0.04367065f, -0.00775146f,
-0.00448608f, 0.03137207f, -0.11697388f, 0.45574951f, 0.75192261f, -0.15435791f,
0.04379272f, -0.00781250f, -0.00433350f, 0.03070068f, -0.11477661f, 0.44500732f,
0.76095581f, -0.15435791f, 0.04385376f, -0.00787354f, -0.00421143f, 0.03002930f,
-0.11251831f, 0.43429565f, 0.76989746f, -0.15426636f, 0.04388428f, -0.00793457f,
-0.00405884f, 0.02935791f, -0.11026001f, 0.42358398f, 0.77871704f, -0.15408325f,
0.04388428f, -0.00799561f, -0.00393677f, 0.02865601f, -0.10794067f, 0.41287231f,
0.78741455f, -0.15377808f, 0.04388428f, -0.00805664f, -0.00381470f, 0.02795410f,
-0.10559082f, 0.40219116f, 0.79598999f, -0.15338135f, 0.04382324f, -0.00808716f,
-0.00366211f, 0.02725220f, -0.10321045f, 0.39154053f, 0.80441284f, -0.15283203f,
0.04373169f, -0.00811768f, -0.00354004f, 0.02655029f, -0.10079956f, 0.38092041f,
0.81271362f, -0.15219116f, 0.04357910f, -0.00814819f, -0.00341797f, 0.02584839f,
-0.09838867f, 0.37033081f, 0.82086182f, -0.15142822f, 0.04342651f, -0.00814819f,
-0.00326538f, 0.02514648f, -0.09594727f, 0.35977173f, 0.82888794f, -0.15054321f,
0.04321289f, -0.00817871f, -0.00314331f, 0.02441406f, -0.09347534f, 0.34924316f,
0.83673096f, -0.14953613f, 0.04299927f, -0.00817871f, -0.00302124f, 0.02371216f,
-0.09097290f, 0.33874512f, 0.84445190f, -0.14840698f, 0.04272461f, -0.00817871f,
-0.00289917f, 0.02297974f, -0.08847046f, 0.32833862f, 0.85202026f, -0.14715576f,
0.04238892f, -0.00814819f, -0.00277710f, 0.02227783f, -0.08596802f, 0.31793213f,
0.85940552f, -0.14578247f, 0.04205322f, -0.00814819f, -0.00265503f, 0.02154541f,
-0.08343506f, 0.30761719f, 0.86666870f, -0.14428711f, 0.04165649f, -0.00811768f,
-0.00253296f, 0.02084351f, -0.08090210f, 0.29733276f, 0.87374878f, -0.14263916f,
0.04122925f, -0.00808716f, -0.00244141f, 0.02011108f, -0.07833862f, 0.28710938f,
0.88064575f, -0.14089966f, 0.04077148f, -0.00802612f, -0.00231934f, 0.01940918f,
-0.07577515f, 0.27694702f, 0.88739014f, -0.13900757f, 0.04025269f, -0.00796509f,
-0.00219727f, 0.01870728f, -0.07321167f, 0.26687622f, 0.89395142f, -0.13696289f,
0.03970337f, -0.00790405f, -0.00210571f, 0.01797485f, -0.07064819f, 0.25683594f,
0.90036011f, -0.13479614f, 0.03909302f, -0.00784302f, -0.00198364f, 0.01727295f,
-0.06808472f, 0.24691772f, 0.90658569f, -0.13250732f, 0.03848267f, -0.00775146f,
-0.00189209f, 0.01657104f, -0.06555176f, 0.23703003f, 0.91262817f, -0.13006592f,
0.03781128f, -0.00765991f, -0.00177002f, 0.01586914f, -0.06298828f, 0.22723389f,
0.91848755f, -0.12750244f, 0.03707886f, -0.00753784f, -0.00167847f, 0.01519775f,
-0.06042480f, 0.21752930f, 0.92413330f, -0.12478638f, 0.03631592f, -0.00744629f,
-0.00158691f, 0.01449585f, -0.05786133f, 0.20791626f, 0.92962646f, -0.12194824f,
0.03552246f, -0.00732422f, -0.00149536f, 0.01382446f, -0.05532837f, 0.19839478f,
0.93490601f, -0.11895752f, 0.03466797f, -0.00717163f, -0.00140381f, 0.01315308f,
-0.05279541f, 0.18893433f, 0.94000244f, -0.11584473f, 0.03378296f, -0.00701904f,
-0.00131226f, 0.01248169f, -0.05029297f, 0.17959595f, 0.94491577f, -0.11254883f,
0.03286743f, -0.00686646f, -0.00122070f, 0.01181030f, -0.04779053f, 0.17034912f,
0.94961548f, -0.10916138f, 0.03189087f, -0.00671387f, -0.00112915f, 0.01113892f,
-0.04528809f, 0.16119385f, 0.95413208f, -0.10559082f, 0.03085327f, -0.00653076f,
-0.00106812f, 0.01049805f, -0.04281616f, 0.15213013f, 0.95843506f, -0.10189819f,
0.02981567f, -0.00631714f, -0.00097656f, 0.00985718f, -0.04034424f, 0.14318848f,
0.96252441f, -0.09805298f, 0.02868652f, -0.00613403f, -0.00091553f, 0.00924683f,
-0.03790283f, 0.13436890f, 0.96643066f, -0.09405518f, 0.02755737f, -0.00592041f,
-0.00082397f, 0.00860596f, -0.03549194f, 0.12561035f, 0.97012329f, -0.08993530f,
0.02636719f, -0.00567627f, -0.00076294f, 0.00799561f, -0.03308105f, 0.11700439f,
0.97360229f, -0.08566284f, 0.02511597f, -0.00543213f, -0.00070190f, 0.00738525f,
-0.03073120f, 0.10848999f, 0.97686768f, -0.08123779f, 0.02383423f, -0.00518799f,
-0.00061035f, 0.00680542f, -0.02835083f, 0.10012817f, 0.97991943f, -0.07666016f,
0.02252197f, -0.00494385f, -0.00054932f, 0.00622559f, -0.02603149f, 0.09185791f,
0.98278809f, -0.07192993f, 0.02114868f, -0.00463867f, -0.00048828f, 0.00564575f,
-0.02374268f, 0.08370972f, 0.98541260f, -0.06707764f, 0.01971436f, -0.00436401f,
-0.00042725f, 0.00506592f, -0.02145386f, 0.07568359f, 0.98782349f, -0.06207275f,
0.01828003f, -0.00405884f, -0.00039673f, 0.00451660f, -0.01922607f, 0.06777954f,
0.99002075f, -0.05691528f, 0.01678467f, -0.00375366f, -0.00033569f, 0.00396729f,
-0.01699829f, 0.05999756f, 0.99200439f, -0.05163574f, 0.01522827f, -0.00341797f,
-0.00027466f, 0.00344849f, -0.01483154f, 0.05233765f, 0.99377441f, -0.04620361f,
0.01364136f, -0.00308228f, -0.00024414f, 0.00292969f, -0.01266479f, 0.04483032f,
0.99533081f, -0.04061890f, 0.01202393f, -0.00274658f, -0.00018311f, 0.00241089f,
-0.01055908f, 0.03741455f, 0.99664307f, -0.03488159f, 0.01037598f, -0.00238037f,
-0.00015259f, 0.00192261f, -0.00845337f, 0.03018188f, 0.99774170f, -0.02899170f,
0.00866699f, -0.00201416f, -0.00009155f, 0.00143433f, -0.00640869f, 0.02304077f,
0.99862671f, -0.02297974f, 0.00689697f, -0.00161743f, -0.00006104f, 0.00097656f,
-0.00439453f, 0.01605225f, 0.99929810f, -0.01684570f, 0.00512695f, -0.00122070f,
-0.00003052f, 0.00051880f, -0.00241089f, 0.00918579f, 0.99975586f, -0.01052856f,
0.00329590f, -0.00079346f, 0.00000000f, 0.00006104f, -0.00048828f, 0.00247192f,
0.99996948f, -0.00408936f, 0.00143433f, -0.00036621f,
};
const auto get_lut = [&]() -> std::span<const f32> {
if (sample_rate_ratio <= 1.0f) {
return std::span<const f32>(lut2.data(), lut2.size());
} else if (sample_rate_ratio < 1.3f) {
return std::span<const f32>(lut1.data(), lut1.size());
} else {
return std::span<const f32>(lut0.data(), lut0.size());
}
};
auto lut{get_lut()};
u32 read_index{0};
for (u32 i = 0; i < samples_to_write; i++) {
const auto lut_index{(fraction.get_frac() >> 8) * 8};
const Common::FixedPoint<56, 8> sample0{input[read_index + 0] * lut[lut_index + 0]};
const Common::FixedPoint<56, 8> sample1{input[read_index + 1] * lut[lut_index + 1]};
const Common::FixedPoint<56, 8> sample2{input[read_index + 2] * lut[lut_index + 2]};
const Common::FixedPoint<56, 8> sample3{input[read_index + 3] * lut[lut_index + 3]};
const Common::FixedPoint<56, 8> sample4{input[read_index + 4] * lut[lut_index + 4]};
const Common::FixedPoint<56, 8> sample5{input[read_index + 5] * lut[lut_index + 5]};
const Common::FixedPoint<56, 8> sample6{input[read_index + 6] * lut[lut_index + 6]};
const Common::FixedPoint<56, 8> sample7{input[read_index + 7] * lut[lut_index + 7]};
output[i] = (sample0 + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7)
.to_int_floor();
fraction += sample_rate_ratio;
read_index += static_cast<u32>(fraction.to_int_floor());
fraction.clear_int();
}
}
void Resample(std::span<s32> output, std::span<const s16> input,
const Common::FixedPoint<49, 15>& sample_rate_ratio,
Common::FixedPoint<49, 15>& fraction, const u32 samples_to_write,
const SrcQuality src_quality) {
switch (src_quality) {
case SrcQuality::Low:
ResampleLowQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
break;
case SrcQuality::Medium:
ResampleNormalQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
break;
case SrcQuality::High:
ResampleHighQuality(output, input, sample_rate_ratio, fraction, samples_to_write);
break;
}
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,29 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <span>
#include "audio_core/common/common.h"
#include "common/common_types.h"
#include "common/fixed_point.h"
namespace AudioCore::AudioRenderer {
/**
* Resample an input buffer into an output buffer, according to the sample_rate_ratio.
*
* @param output - Output buffer.
* @param input - Input buffer.
* @param sample_rate_ratio - Ratio for resampling.
e.g 32000/48000 = 0.666 input samples read per output.
* @param fraction - Current read fraction, written to and should be passed back in for
* multiple calls.
* @param samples_to_write - Number of samples to write.
* @param src_quality - Resampling quality.
*/
void Resample(std::span<s32> output, std::span<const s16> input,
const Common::FixedPoint<49, 15>& sample_rate_ratio,
Common::FixedPoint<49, 15>& fraction, u32 samples_to_write, SrcQuality src_quality);
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,262 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/resample/upsample.h"
#include "audio_core/renderer/upsampler/upsampler_info.h"
namespace AudioCore::AudioRenderer {
/**
* Upsampling impl. Input must be 8K, 16K or 32K, output is 48K.
*
* @param output - Output buffer.
* @param input - Input buffer.
* @param target_sample_count - Number of samples for output.
* @param state - Upsampler state, updated each call.
*/
static void SrcProcessFrame(std::span<s32> output, std::span<const s32> input,
const u32 target_sample_count, const u32 source_sample_count,
UpsamplerState* state) {
constexpr u32 WindowSize = 10;
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow1{
51.93359375f, -18.80078125f, 9.73046875f, -5.33203125f, 2.84375f,
-1.41015625f, 0.62109375f, -0.2265625f, 0.0625f, -0.00390625f,
};
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow2{
105.35546875f, -24.52734375f, 11.9609375f, -6.515625f, 3.52734375f,
-1.796875f, 0.828125f, -0.32421875f, 0.1015625f, -0.015625f,
};
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow3{
122.08203125f, -16.47656250f, 7.68359375f, -4.15625000f, 2.26171875f,
-1.16796875f, 0.54687500f, -0.22265625f, 0.07421875f, -0.01171875f,
};
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow4{
23.73437500f, -9.62109375f, 5.07812500f, -2.78125000f, 1.46875000f,
-0.71484375f, 0.30859375f, -0.10546875f, 0.02734375f, 0.00000000f,
};
constexpr std::array<Common::FixedPoint<24, 8>, WindowSize> SincWindow5{
80.62500000f, -24.67187500f, 12.44921875f, -6.80859375f, 3.66406250f,
-1.83984375f, 0.83203125f, -0.31640625f, 0.09375000f, -0.01171875f,
};
if (!state->initialized) {
switch (source_sample_count) {
case 40:
state->window_size = WindowSize;
state->ratio = 6.0f;
state->history.fill(0);
break;
case 80:
state->window_size = WindowSize;
state->ratio = 3.0f;
state->history.fill(0);
break;
case 160:
state->window_size = WindowSize;
state->ratio = 1.5f;
state->history.fill(0);
break;
default:
LOG_ERROR(Service_Audio, "Invalid upsampling source count {}!", source_sample_count);
// This continues anyway, but let's assume 160 for sanity
state->window_size = WindowSize;
state->ratio = 1.5f;
state->history.fill(0);
break;
}
state->history_input_index = 0;
state->history_output_index = 9;
state->history_start_index = 0;
state->history_end_index = UpsamplerState::HistorySize - 1;
state->initialized = true;
}
if (target_sample_count == 0) {
return;
}
u32 read_index{0};
auto increment = [&]() -> void {
state->history[state->history_input_index] = input[read_index++];
state->history_input_index =
static_cast<u16>((state->history_input_index + 1) % UpsamplerState::HistorySize);
state->history_output_index =
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
};
auto calculate_sample = [&state](std::span<const Common::FixedPoint<24, 8>> coeffs1,
std::span<const Common::FixedPoint<24, 8>> coeffs2) -> s32 {
auto output_index{state->history_output_index};
auto start_pos{output_index - state->history_start_index + 1U};
auto end_pos{10U};
if (start_pos < 10) {
end_pos = start_pos;
}
u64 prev_contrib{0};
u32 coeff_index{0};
for (; coeff_index < end_pos; coeff_index++, output_index--) {
prev_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
coeffs1[coeff_index].to_raw();
}
auto end_index{state->history_end_index};
for (; start_pos < 9; start_pos++, coeff_index++, end_index--) {
prev_contrib += static_cast<u64>(state->history[end_index].to_raw()) *
coeffs1[coeff_index].to_raw();
}
output_index =
static_cast<u16>((state->history_output_index + 1) % UpsamplerState::HistorySize);
start_pos = state->history_end_index - output_index + 1U;
end_pos = 10U;
if (start_pos < 10) {
end_pos = start_pos;
}
u64 next_contrib{0};
coeff_index = 0;
for (; coeff_index < end_pos; coeff_index++, output_index++) {
next_contrib += static_cast<u64>(state->history[output_index].to_raw()) *
coeffs2[coeff_index].to_raw();
}
auto start_index{state->history_start_index};
for (; start_pos < 9; start_pos++, start_index++, coeff_index++) {
next_contrib += static_cast<u64>(state->history[start_index].to_raw()) *
coeffs2[coeff_index].to_raw();
}
return static_cast<s32>(((prev_contrib >> 15) + (next_contrib >> 15)) >> 8);
};
switch (state->ratio.to_int_floor()) {
// 40 -> 240
case 6:
for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
switch (state->sample_index) {
case 0:
increment();
output[write_index] = state->history[state->history_output_index].to_int_floor();
break;
case 1:
output[write_index] = calculate_sample(SincWindow3, SincWindow4);
break;
case 2:
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
break;
case 3:
output[write_index] = calculate_sample(SincWindow5, SincWindow5);
break;
case 4:
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
break;
case 5:
output[write_index] = calculate_sample(SincWindow4, SincWindow3);
break;
}
state->sample_index = static_cast<u8>((state->sample_index + 1) % 6);
}
break;
// 80 -> 240
case 3:
for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
switch (state->sample_index) {
case 0:
increment();
output[write_index] = state->history[state->history_output_index].to_int_floor();
break;
case 1:
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
break;
case 2:
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
break;
}
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
}
break;
// 160 -> 240
default:
for (u32 write_index = 0; write_index < target_sample_count; write_index++) {
switch (state->sample_index) {
case 0:
increment();
output[write_index] = state->history[state->history_output_index].to_int_floor();
break;
case 1:
output[write_index] = calculate_sample(SincWindow1, SincWindow2);
break;
case 2:
increment();
output[write_index] = calculate_sample(SincWindow2, SincWindow1);
break;
}
state->sample_index = static_cast<u8>((state->sample_index + 1) % 3);
}
break;
}
}
auto UpsampleCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) -> void {
string += fmt::format("UpsampleCommand\n\tsource_sample_count {} source_sample_rate {}",
source_sample_count, source_sample_rate);
const auto upsampler{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
if (upsampler != nullptr) {
string += fmt::format("\n\tUpsampler\n\t\tenabled {} sample count {}\n\tinputs: ",
upsampler->enabled, upsampler->sample_count);
for (u32 i = 0; i < upsampler->input_count; i++) {
string += fmt::format("{:02X}, ", upsampler->inputs[i]);
}
}
string += "\n";
}
void UpsampleCommand::Process(const ADSP::CommandListProcessor& processor) {
const auto info{reinterpret_cast<UpsamplerInfo*>(upsampler_info)};
const auto input_count{std::min(info->input_count, buffer_count)};
const std::span<const s16> inputs_{reinterpret_cast<const s16*>(inputs), input_count};
for (u32 i = 0; i < input_count; i++) {
const auto channel{inputs_[i]};
if (channel >= 0 && channel < static_cast<s16>(processor.buffer_count)) {
auto state{&info->states[i]};
std::span<s32> output{
reinterpret_cast<s32*>(samples_buffer + info->sample_count * channel * sizeof(s32)),
info->sample_count};
auto input{processor.mix_buffers.subspan(channel * processor.sample_count,
processor.sample_count)};
SrcProcessFrame(output, input, info->sample_count, source_sample_count, state);
}
}
}
bool UpsampleCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,60 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for upsampling a mix buffer to 48Khz.
* Input must be 8Khz, 16Khz or 32Khz, and output will be 48Khz.
*/
struct UpsampleCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Pointer to the output samples buffer.
CpuAddr samples_buffer;
/// Pointer to input mix buffer indexes.
CpuAddr inputs;
/// Number of input mix buffers.
u32 buffer_count;
/// Unknown, unused.
u32 unk_20;
/// Source data sample count.
u32 source_sample_count;
/// Source data sample rate.
u32 source_sample_rate;
/// Pointer to the upsampler info for this command.
CpuAddr upsampler_info;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <vector>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/sink/circular_buffer.h"
#include "core/memory.h"
namespace AudioCore::AudioRenderer {
void CircularBufferSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format(
"CircularBufferSinkCommand\n\tinput_count {} ring size {:04X} ring pos {:04X}\n\tinputs: ",
input_count, size, pos);
for (u32 i = 0; i < input_count; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n";
}
void CircularBufferSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
constexpr s32 min{std::numeric_limits<s16>::min()};
constexpr s32 max{std::numeric_limits<s16>::max()};
std::vector<s16> output(processor.sample_count);
for (u32 channel = 0; channel < input_count; channel++) {
auto input{processor.mix_buffers.subspan(inputs[channel] * processor.sample_count,
processor.sample_count)};
for (u32 sample_index = 0; sample_index < processor.sample_count; sample_index++) {
output[sample_index] = static_cast<s16>(std::clamp(input[sample_index], min, max));
}
processor.memory->WriteBlockUnsafe(address + pos, output.data(),
output.size() * sizeof(s16));
pos += static_cast<u32>(processor.sample_count * sizeof(s16));
if (pos >= size) {
pos = 0;
}
}
}
bool CircularBufferSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for sinking samples to a circular buffer.
*/
struct CircularBufferSinkCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Number of input mix buffers
u32 input_count;
/// Input mix buffer indexes
std::array<s16, MaxChannels> inputs;
/// Circular buffer address
CpuAddr address;
/// Circular buffer size
u32 size;
/// Current buffer offset
u32 pos;
};
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include "audio_core/renderer/adsp/command_list_processor.h"
#include "audio_core/renderer/command/sink/device.h"
#include "audio_core/sink/sink.h"
namespace AudioCore::AudioRenderer {
void DeviceSinkCommand::Dump([[maybe_unused]] const ADSP::CommandListProcessor& processor,
std::string& string) {
string += fmt::format("DeviceSinkCommand\n\t{} session {} input_count {}\n\tinputs: ",
std::string_view(name), session_id, input_count);
for (u32 i = 0; i < input_count; i++) {
string += fmt::format("{:02X}, ", inputs[i]);
}
string += "\n";
}
void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) {
constexpr s32 min = std::numeric_limits<s16>::min();
constexpr s32 max = std::numeric_limits<s16>::max();
auto stream{processor.GetOutputSinkStream()};
stream->SetSystemChannels(input_count);
Sink::SinkBuffer out_buffer{
.frames{TargetSampleCount},
.frames_played{0},
.tag{0},
.consumed{false},
};
std::vector<s16> samples(out_buffer.frames * input_count);
for (u32 channel = 0; channel < input_count; channel++) {
const auto offset{inputs[channel] * out_buffer.frames};
for (u32 index = 0; index < out_buffer.frames; index++) {
samples[index * input_count + channel] =
static_cast<s16>(std::clamp(sample_buffer[offset + index], min, max));
}
}
out_buffer.tag = reinterpret_cast<u64>(samples.data());
stream->AppendBuffer(out_buffer, samples);
}
bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) {
return true;
}
} // namespace AudioCore::AudioRenderer

View file

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include <span>
#include <string>
#include "audio_core/renderer/command/icommand.h"
#include "common/common_types.h"
namespace AudioCore::AudioRenderer {
namespace ADSP {
class CommandListProcessor;
}
/**
* AudioRenderer command for sinking samples to an output device.
*/
struct DeviceSinkCommand : ICommand {
/**
* Print this command's information to a string.
*
* @param processor - The CommandListProcessor processing this command.
* @param string - The string to print into.
*/
void Dump(const ADSP::CommandListProcessor& processor, std::string& string) override;
/**
* Process this command.
*
* @param processor - The CommandListProcessor processing this command.
*/
void Process(const ADSP::CommandListProcessor& processor) override;
/**
* Verify this command's data is valid.
*
* @param processor - The CommandListProcessor processing this command.
* @return True if the command is valid, otherwise false.
*/
bool Verify(const ADSP::CommandListProcessor& processor) override;
/// Device name
char name[0x100];
/// System session id (unused)
s32 session_id;
/// Sample buffer to sink
std::span<s32> sample_buffer;
/// Number of input channels
u32 input_count;
/// Mix buffer indexes for each channel
std::array<s16, MaxChannels> inputs;
};
} // namespace AudioCore::AudioRenderer