From 7636fefb715492960545f5b921b0714ae261f2f6 Mon Sep 17 00:00:00 2001 From: Kelebek1 Date: Wed, 7 Jul 2021 14:56:23 +0100 Subject: [PATCH] audio_core: Preserve front channel volume after 6 to 2 downmix Many games report 6 channel output while only providing data for 2. We only output 2-channel audio regardless, and in the downmixing, front left/right only provide 36% of their volume. This is done assuming all of the other channels also contain valid data, but in many games they don't. This PR alters the downmixing to preserve front left/right, so volume is not lost. This improves volume in Link's Awakening, New Super Mario Bros U, Disgaea 6, Super Kirby Clash. --- src/audio_core/audio_renderer.cpp | 30 ++++------ src/audio_core/command_generator.cpp | 86 ++++++++++++++++------------ src/audio_core/command_generator.h | 23 ++++---- src/audio_core/sink_context.cpp | 15 +++-- src/audio_core/sink_context.h | 2 - 5 files changed, 81 insertions(+), 75 deletions(-) diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index ccd5ca6cc9..7dba739b45 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -29,10 +29,9 @@ namespace { (static_cast(r_channel) * r_mix_amount))); } -[[nodiscard]] static constexpr std::tuple Mix6To2(s16 fl_channel, s16 fr_channel, - s16 fc_channel, - [[maybe_unused]] s16 lf_channel, - s16 bl_channel, s16 br_channel) { +[[maybe_unused, nodiscard]] static constexpr std::tuple Mix6To2( + s16 fl_channel, s16 fr_channel, s16 fc_channel, [[maybe_unused]] s16 lf_channel, s16 bl_channel, + s16 br_channel) { // Front channels are mixed 36.94%, Center channels are mixed to be 26.12% & the back channels // are mixed to be 36.94% @@ -57,11 +56,11 @@ namespace { const std::array& coeff) { const auto left = static_cast(fl_channel) * coeff[0] + static_cast(fc_channel) * coeff[1] + - static_cast(lf_channel) * coeff[2] + static_cast(bl_channel) * coeff[0]; + static_cast(lf_channel) * coeff[2] + static_cast(bl_channel) * coeff[3]; const auto right = static_cast(fr_channel) * coeff[0] + static_cast(fc_channel) * coeff[1] + - static_cast(lf_channel) * coeff[2] + static_cast(br_channel) * coeff[0]; + static_cast(lf_channel) * coeff[2] + static_cast(br_channel) * coeff[3]; return {ClampToS16(static_cast(left)), ClampToS16(static_cast(right))}; } @@ -241,7 +240,7 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { const auto channel_count = buffer_offsets.size(); const auto& final_mix = mix_context.GetFinalMixInfo(); const auto& in_params = final_mix.GetInParams(); - std::vector mix_buffers(channel_count); + std::vector> mix_buffers(channel_count); for (std::size_t i = 0; i < channel_count; i++) { mix_buffers[i] = command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]); @@ -294,18 +293,11 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) { buffer[i * stream_channel_count + 0] = Mix2To1(fl_sample, fr_sample); } else if (stream_channel_count == 2) { // Mix all channels into 2 channels - if (sink_context.HasDownMixingCoefficients()) { - const auto [left, right] = Mix6To2WithCoefficients( - fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample, - sink_context.GetDownmixCoefficients()); - buffer[i * stream_channel_count + 0] = left; - buffer[i * stream_channel_count + 1] = right; - } else { - const auto [left, right] = Mix6To2(fl_sample, fr_sample, fc_sample, - lf_sample, bl_sample, br_sample); - buffer[i * stream_channel_count + 0] = left; - buffer[i * stream_channel_count + 1] = right; - } + const auto [left, right] = Mix6To2WithCoefficients( + fl_sample, fr_sample, fc_sample, lf_sample, bl_sample, br_sample, + sink_context.GetDownmixCoefficients()); + buffer[i * stream_channel_count + 0] = left; + buffer[i * stream_channel_count + 1] = right; } else if (stream_channel_count == 6) { // Pass through buffer[i * stream_channel_count + 0] = fl_sample; diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 27437f1ea4..1402ff280c 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -31,7 +31,7 @@ constexpr std::array EARLY_GAIN{ 0.72867f, 0.69794f, 0.5464f, 0.24563f, 0.45214f, 0.44042f}; template -void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { +void ApplyMix(std::span output, std::span input, s32 gain, s32 sample_count) { for (std::size_t i = 0; i < static_cast(sample_count); i += N) { for (std::size_t j = 0; j < N; j++) { output[i + j] += @@ -40,7 +40,8 @@ void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { } } -s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sample_count) { +s32 ApplyMixRamp(std::span output, std::span input, float gain, float delta, + s32 sample_count) { s32 x = 0; for (s32 i = 0; i < sample_count; i++) { x = static_cast(static_cast(input[i]) * gain); @@ -50,20 +51,22 @@ s32 ApplyMixRamp(s32* output, const s32* input, float gain, float delta, s32 sam return x; } -void ApplyGain(s32* output, const s32* input, s32 gain, s32 delta, s32 sample_count) { +void ApplyGain(std::span output, std::span input, s32 gain, s32 delta, + s32 sample_count) { for (s32 i = 0; i < sample_count; i++) { output[i] = static_cast((static_cast(input[i]) * gain + 0x4000) >> 15); gain += delta; } } -void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_count) { +void ApplyGainWithoutDelta(std::span output, std::span input, s32 gain, + s32 sample_count) { for (s32 i = 0; i < sample_count; i++) { output[i] = static_cast((static_cast(input[i]) * gain + 0x4000) >> 15); } } -s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) { +s32 ApplyMixDepop(std::span output, s32 first_sample, s32 delta, s32 sample_count) { const bool positive = first_sample > 0; auto final_sample = std::abs(first_sample); for (s32 i = 0; i < sample_count; i++) { @@ -128,10 +131,10 @@ constexpr std::array REVERB_TAP_INDEX_6CH{4, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 3, 3, 3}; template -void ApplyReverbGeneric(I3dl2ReverbState& state, - const std::array& input, - const std::array& output, - s32 sample_count) { +void ApplyReverbGeneric( + I3dl2ReverbState& state, + const std::array, AudioCommon::MAX_CHANNEL_COUNT>& input, + const std::array, AudioCommon::MAX_CHANNEL_COUNT>& output, s32 sample_count) { auto GetTapLookup = []() { if constexpr (CHANNEL_COUNT == 1) { @@ -454,8 +457,8 @@ void CommandGenerator::GenerateBiquadFilterCommand([[maybe_unused]] s32 mix_buff "input_mix_buffer={}, output_mix_buffer={}", node_id, input_offset, output_offset); } - const auto* input = GetMixBuffer(input_offset); - auto* output = GetMixBuffer(output_offset); + std::span input = GetMixBuffer(input_offset); + std::span output = GetMixBuffer(output_offset); // Biquad filter parameters const auto [n0, n1, n2] = params.numerator; @@ -548,8 +551,8 @@ void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, E return; } - std::array input{}; - std::array output{}; + std::array, AudioCommon::MAX_CHANNEL_COUNT> input{}; + std::array, AudioCommon::MAX_CHANNEL_COUNT> output{}; const auto status = params.status; for (s32 i = 0; i < channel_count; i++) { @@ -584,7 +587,8 @@ void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, E for (s32 i = 0; i < channel_count; i++) { // Only copy if the buffer input and output do not match! if ((mix_buffer_offset + params.input[i]) != (mix_buffer_offset + params.output[i])) { - std::memcpy(output[i], input[i], worker_params.sample_count * sizeof(s32)); + std::memcpy(output[i].data(), input[i].data(), + worker_params.sample_count * sizeof(s32)); } } } @@ -600,8 +604,8 @@ void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, for (s32 i = 0; i < channel_count; i++) { // TODO(ogniK): Actually implement biquad filter if (params.input[i] != params.output[i]) { - const auto* input = GetMixBuffer(mix_buffer_offset + params.input[i]); - auto* output = GetMixBuffer(mix_buffer_offset + params.output[i]); + std::span input = GetMixBuffer(mix_buffer_offset + params.input[i]); + std::span output = GetMixBuffer(mix_buffer_offset + params.output[i]); ApplyMix<1>(output, input, 32768, worker_params.sample_count); } } @@ -640,14 +644,15 @@ void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* inf if (samples_read != static_cast(worker_params.sample_count) && samples_read <= params.sample_count) { - std::memset(GetMixBuffer(output_index), 0, params.sample_count - samples_read); + std::memset(GetMixBuffer(output_index).data(), 0, + params.sample_count - samples_read); } } else { AuxInfoDSP empty{}; memory.WriteBlock(aux->GetSendInfo(), &empty, sizeof(AuxInfoDSP)); memory.WriteBlock(aux->GetRecvInfo(), &empty, sizeof(AuxInfoDSP)); if (output_index != input_index) { - std::memcpy(GetMixBuffer(output_index), GetMixBuffer(input_index), + std::memcpy(GetMixBuffer(output_index).data(), GetMixBuffer(input_index).data(), worker_params.sample_count * sizeof(s32)); } } @@ -665,7 +670,7 @@ ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter } s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, - const s32* data, u32 sample_count, u32 write_offset, + std::span data, u32 sample_count, u32 write_offset, u32 write_count) { if (max_samples == 0) { return 0; @@ -675,14 +680,14 @@ s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u3 return 0; } - std::size_t data_offset{}; + s32 data_offset{}; u32 remaining = sample_count; while (remaining > 0) { // Get position in buffer const auto base = send_buffer + (offset * sizeof(u32)); const auto samples_to_grab = std::min(max_samples - offset, remaining); // Write to output - memory.WriteBlock(base, (data + data_offset), samples_to_grab * sizeof(u32)); + memory.WriteBlock(base, (data.data() + data_offset), samples_to_grab * sizeof(u32)); offset = (offset + samples_to_grab) % max_samples; remaining -= samples_to_grab; data_offset += samples_to_grab; @@ -695,7 +700,7 @@ s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u3 } s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, - s32* out_data, u32 sample_count, u32 read_offset, + std::span out_data, u32 sample_count, u32 read_offset, u32 read_count) { if (max_samples == 0) { return 0; @@ -707,15 +712,16 @@ s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u3 } u32 remaining = sample_count; + s32 data_offset{}; while (remaining > 0) { const auto base = recv_buffer + (offset * sizeof(u32)); const auto samples_to_grab = std::min(max_samples - offset, remaining); std::vector buffer(samples_to_grab); memory.ReadBlock(base, buffer.data(), buffer.size() * sizeof(u32)); - std::memcpy(out_data, buffer.data(), buffer.size() * sizeof(u32)); - out_data += samples_to_grab; + std::memcpy(out_data.data() + data_offset, buffer.data(), buffer.size() * sizeof(u32)); offset = (offset + samples_to_grab) % max_samples; remaining -= samples_to_grab; + data_offset += samples_to_grab; } if (read_count != 0) { @@ -962,8 +968,8 @@ void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t node_id, input_offset, output_offset, volume); } - auto* output = GetMixBuffer(output_offset); - const auto* input = GetMixBuffer(input_offset); + std::span output = GetMixBuffer(output_offset); + std::span input = GetMixBuffer(input_offset); const s32 gain = static_cast(volume * 32768.0f); // Mix with loop unrolling @@ -1155,12 +1161,14 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s return samples_processed; } -s32* CommandGenerator::GetMixBuffer(std::size_t index) { - return mix_buffer.data() + (index * worker_params.sample_count); +std::span CommandGenerator::GetMixBuffer(std::size_t index) { + return std::span(mix_buffer.data() + (index * worker_params.sample_count), + worker_params.sample_count); } -const s32* CommandGenerator::GetMixBuffer(std::size_t index) const { - return mix_buffer.data() + (index * worker_params.sample_count); +std::span CommandGenerator::GetMixBuffer(std::size_t index) const { + return std::span(mix_buffer.data() + (index * worker_params.sample_count), + worker_params.sample_count); } std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { @@ -1171,15 +1179,15 @@ std::size_t CommandGenerator::GetTotalMixBufferCount() const { return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT; } -s32* CommandGenerator::GetChannelMixBuffer(s32 channel) { +std::span CommandGenerator::GetChannelMixBuffer(s32 channel) { return GetMixBuffer(worker_params.mix_buffer_count + channel); } -const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const { +std::span CommandGenerator::GetChannelMixBuffer(s32 channel) const { return GetMixBuffer(worker_params.mix_buffer_count + channel); } -void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, +void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span output, VoiceState& dsp_state, s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id) { @@ -1191,7 +1199,7 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate, in_params.mix_id, in_params.splitter_info_id); } - ASSERT_OR_EXECUTE(output != nullptr, { return; }); + ASSERT_OR_EXECUTE(output.data() != nullptr, { return; }); const auto resample_rate = static_cast( static_cast(in_params.sample_rate) / static_cast(target_sample_rate) * @@ -1208,6 +1216,7 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o } std::size_t temp_mix_offset{}; + s32 samples_output{}; auto samples_remaining = sample_count; while (samples_remaining > 0) { const auto samples_to_output = std::min(samples_remaining, min_required_samples); @@ -1296,20 +1305,21 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) { // No need to resample - std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32)); + std::memcpy(output.data() + samples_output, sample_buffer.data(), + samples_read * sizeof(s32)); } else { std::fill(sample_buffer.begin() + temp_mix_offset, sample_buffer.begin() + temp_mix_offset + (samples_to_read - samples_read), 0); - AudioCore::Resample(output, sample_buffer.data(), resample_rate, dsp_state.fraction, - samples_to_output); + AudioCore::Resample(output.data() + samples_output, sample_buffer.data(), resample_rate, + dsp_state.fraction, samples_to_output); // Resample for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { dsp_state.sample_history[i] = sample_buffer[samples_to_read + i]; } } - output += samples_to_output; samples_remaining -= samples_to_output; + samples_output += samples_to_output; } } diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index 673e4fbef9..ac034b0a5f 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "audio_core/common.h" #include "audio_core/voice_context.h" #include "common/common_types.h" @@ -41,10 +42,10 @@ public: void PreCommand(); void PostCommand(); - [[nodiscard]] s32* GetChannelMixBuffer(s32 channel); - [[nodiscard]] const s32* GetChannelMixBuffer(s32 channel) const; - [[nodiscard]] s32* GetMixBuffer(std::size_t index); - [[nodiscard]] const s32* GetMixBuffer(std::size_t index) const; + [[nodiscard]] std::span GetChannelMixBuffer(s32 channel); + [[nodiscard]] std::span GetChannelMixBuffer(s32 channel) const; + [[nodiscard]] std::span GetMixBuffer(std::size_t index); + [[nodiscard]] std::span GetMixBuffer(std::size_t index) const; [[nodiscard]] std::size_t GetMixChannelBufferOffset(s32 channel) const; [[nodiscard]] std::size_t GetTotalMixBufferCount() const; @@ -77,10 +78,11 @@ private: void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); [[nodiscard]] ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index); - s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, const s32* data, - u32 sample_count, u32 write_offset, u32 write_count); - s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, s32* out_data, - u32 sample_count, u32 read_offset, u32 read_count); + s32 WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, + std::span data, u32 sample_count, u32 write_offset, + u32 write_count); + s32 ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, + std::span out_data, u32 sample_count, u32 read_offset, u32 read_count); void InitializeI3dl2Reverb(I3dl2ReverbParams& info, I3dl2ReverbState& state, std::vector& work_buffer); @@ -90,8 +92,9 @@ private: s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_start_offset, s32 sample_end_offset, s32 sample_count, s32 channel, std::size_t mix_offset); - void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state, - s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id); + void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, std::span output, + VoiceState& dsp_state, s32 channel, s32 target_sample_rate, + s32 sample_count, s32 node_id); AudioCommon::AudioRendererParameter& worker_params; VoiceContext& voice_context; diff --git a/src/audio_core/sink_context.cpp b/src/audio_core/sink_context.cpp index a69543696a..cc55b290c8 100644 --- a/src/audio_core/sink_context.cpp +++ b/src/audio_core/sink_context.cpp @@ -15,10 +15,17 @@ std::size_t SinkContext::GetCount() const { void SinkContext::UpdateMainSink(const SinkInfo::InParams& in) { ASSERT(in.type == SinkTypes::Device); - has_downmix_coefs = in.device.down_matrix_enabled; - if (has_downmix_coefs) { + if (in.device.down_matrix_enabled) { downmix_coefficients = in.device.down_matrix_coef; + } else { + downmix_coefficients = { + 1.0f, // front + 0.707f, // center + 0.0f, // lfe + 0.707f, // back + }; } + in_use = in.in_use; use_count = in.device.input_count; buffers = in.device.input; @@ -34,10 +41,6 @@ std::vector SinkContext::OutputBuffers() const { return buffer_ret; } -bool SinkContext::HasDownMixingCoefficients() const { - return has_downmix_coefs; -} - const DownmixCoefficients& SinkContext::GetDownmixCoefficients() const { return downmix_coefficients; } diff --git a/src/audio_core/sink_context.h b/src/audio_core/sink_context.h index 9e2b69785b..254961fe29 100644 --- a/src/audio_core/sink_context.h +++ b/src/audio_core/sink_context.h @@ -84,7 +84,6 @@ public: [[nodiscard]] bool InUse() const; [[nodiscard]] std::vector OutputBuffers() const; - [[nodiscard]] bool HasDownMixingCoefficients() const; [[nodiscard]] const DownmixCoefficients& GetDownmixCoefficients() const; private: @@ -92,7 +91,6 @@ private: s32 use_count{}; std::array buffers{}; std::size_t sink_count{}; - bool has_downmix_coefs{false}; DownmixCoefficients downmix_coefficients{}; }; } // namespace AudioCore