From 81b2b4fbe9cc9a7386f95a9fa87975860c9a1deb Mon Sep 17 00:00:00 2001 From: David Marcec Date: Sun, 12 Jul 2020 21:59:14 +1000 Subject: audio_core: Apollo Part 1, AudioRenderer refactor --- src/audio_core/command_generator.cpp | 668 +++++++++++++++++++++++++++++++++++ 1 file changed, 668 insertions(+) create mode 100644 src/audio_core/command_generator.cpp (limited to 'src/audio_core/command_generator.cpp') diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp new file mode 100644 index 0000000000..722f9b6c5e --- /dev/null +++ b/src/audio_core/command_generator.cpp @@ -0,0 +1,668 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "audio_core/algorithm/interpolate.h" +#include "audio_core/command_generator.h" +#include "audio_core/mix_context.h" +#include "audio_core/voice_context.h" +#include "core/memory.h" + +namespace AudioCore { +namespace { +static constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; +static constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; + +template +void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { + for (s32 i = 0; i < sample_count; i += N) { + for (std::size_t j = 0; j < N; j++) { + output[i + j] += + static_cast((static_cast(input[i + j]) * gain + 0x4000) >> 15); + } + } +} + +s32 ApplyMixRamp(s32* output, const s32* 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); + output[i] += x; + gain += delta; + } + return x; +} + +void ApplyGain(s32* output, const s32* 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) { + for (s32 i = 0; i < sample_count; i++) { + output[i] = static_cast((static_cast(input[i]) * gain + 0x4000) >> 15); + } +} + +} // namespace + +CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params, + VoiceContext& voice_context, MixContext& mix_context, + SplitterContext& splitter_context, Core::Memory::Memory& memory) + : worker_params(worker_params), voice_context(voice_context), mix_context(mix_context), + splitter_context(splitter_context), memory(memory), + mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * + worker_params.sample_count), + sample_buffer(MIX_BUFFER_SIZE) {} +CommandGenerator::~CommandGenerator() = default; + +void CommandGenerator::ClearMixBuffers() { + std::memset(mix_buffer.data(), 0, mix_buffer.size() * sizeof(s32)); + std::memset(sample_buffer.data(), 0, sample_buffer.size() * sizeof(s32)); +} + +void CommandGenerator::GenerateVoiceCommands() { + if (dumping_frame) { + LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateVoiceCommands"); + } + // Grab all our voices + const auto voice_count = voice_context.GetVoiceCount(); + for (std::size_t i = 0; i < voice_count; i++) { + auto& voice_info = voice_context.GetSortedInfo(i); + // Update voices and check if we should queue them + if (voice_info.ShouldSkip() || !voice_info.UpdateForCommandGeneration(voice_context)) { + continue; + } + + // Queue our voice + GenerateVoiceCommand(voice_info); + } + // Update our splitters + splitter_context.UpdateInternalState(); +} + +void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) { + auto& in_params = voice_info.GetInParams(); + const auto channel_count = in_params.channel_count; + + for (s32 channel = 0; channel < channel_count; channel++) { + const auto resource_id = in_params.voice_channel_resource_id[channel]; + auto& dsp_state = voice_context.GetDspSharedState(resource_id); + auto& channel_resource = voice_context.GetChannelResource(resource_id); + + // Decode our samples for our channel + GenerateDataSourceCommand(voice_info, dsp_state, channel); + + if (in_params.should_depop) { + in_params.last_volume = 0.0f; + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER || + in_params.mix_id != AudioCommon::NO_MIX) { + // Apply a biquad filter if needed + GenerateBiquadFilterCommandForVoice(voice_info, dsp_state, + worker_params.mix_buffer_count, channel); + // Base voice volume ramping + GenerateVolumeRampCommand(in_params.last_volume, in_params.volume, channel, + in_params.node_id); + in_params.last_volume = in_params.volume; + + if (in_params.mix_id != AudioCommon::NO_MIX) { + // If we're using a mix id + auto& mix_info = mix_context.GetInfo(in_params.mix_id); + const auto& dest_mix_params = mix_info.GetInParams(); + + // Voice Mixing + GenerateVoiceMixCommand( + channel_resource.GetCurrentMixVolume(), channel_resource.GetLastMixVolume(), + dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, + worker_params.mix_buffer_count + channel, in_params.node_id); + + // Update last mix volumes + channel_resource.UpdateLastMixVolumes(); + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { + s32 base = channel; + while (auto* destination_data = + GetDestinationData(in_params.splitter_info_id, base)) { + base += channel_count; + + if (!destination_data->IsConfigured()) { + continue; + } + if (destination_data->GetMixId() >= mix_context.GetCount()) { + continue; + } + + const auto& mix_info = mix_context.GetInfo(destination_data->GetMixId()); + const auto& dest_mix_params = mix_info.GetInParams(); + GenerateVoiceMixCommand( + destination_data->CurrentMixVolumes(), destination_data->LastMixVolumes(), + dsp_state, dest_mix_params.buffer_offset, dest_mix_params.buffer_count, + worker_params.mix_buffer_count + channel, in_params.node_id); + destination_data->MarkDirty(); + } + } + } + + // Update biquad filter enabled states + for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { + in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled; + } + } +} + +void CommandGenerator::GenerateSubMixCommands() { + const auto mix_count = mix_context.GetCount(); + for (std::size_t i = 0; i < mix_count; i++) { + auto& mix_info = mix_context.GetSortedInfo(i); + const auto& in_params = mix_info.GetInParams(); + if (!in_params.in_use || in_params.mix_id == AudioCommon::FINAL_MIX) { + continue; + } + GenerateSubMixCommand(mix_info); + } +} + +void CommandGenerator::GenerateFinalMixCommands() { + GenerateFinalMixCommand(); +} + +void CommandGenerator::PreCommand() { + if (dumping_frame) { + for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) { + const auto& base = splitter_context.GetInfo(i); + std::string graph = fmt::format("b[{}]", i); + auto* head = base.GetHead(); + while (head != nullptr) { + graph += fmt::format("->{}", head->GetMixId()); + head = head->GetNextDestination(); + } + LOG_CRITICAL(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph); + } + } +} + +void CommandGenerator::PostCommand() { + if (dumping_frame) { + dumping_frame = false; + } +} + +void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 channel) { + auto& in_params = voice_info.GetInParams(); + const auto depop = in_params.should_depop; + + if (in_params.mix_id != AudioCommon::NO_MIX) { + auto& mix_info = mix_context.GetInfo(in_params.mix_id); + // mix_info. + // TODO(ogniK): Depop to destination mix + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { + // TODO(ogniK): Depop to splitter + } + + if (!depop) { + switch (in_params.sample_format) { + case SampleFormat::Pcm16: + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + case SampleFormat::Adpcm: + ASSERT(channel == 0 && in_params.channel_count == 1); + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + default: + UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + } + } +} + +void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, + VoiceState& dsp_state, + s32 mix_buffer_count, s32 channel) { + for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { + const auto& in_params = voice_info.GetInParams(); + auto& biquad_filter = in_params.biquad_filter[i]; + // Check if biquad filter is actually used + if (!biquad_filter.enabled) { + continue; + } + + // Reinitialize our biquad filter state if it was enabled previously + if (!in_params.was_biquad_filter_enabled[i]) { + std::memset(dsp_state.biquad_filter_state.data(), 0, + dsp_state.biquad_filter_state.size() * sizeof(s64)); + } + + // Generate biquad filter + GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, dsp_state.biquad_filter_state, + mix_buffer_count + channel, mix_buffer_count + channel, + worker_params.sample_count, voice_info.GetInParams().node_id); + } +} + +void AudioCore::CommandGenerator::GenerateBiquadFilterCommand( + s32 mix_buffer, const BiquadFilterParameter& params, std::array& state, + std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) { + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, " + "input_mix_buffer={}, output_mix_buffer={}", + node_id, input_offset, output_offset); + } + const auto* input = GetMixBuffer(input_offset); + auto* output = GetMixBuffer(output_offset); + + // Biquad filter parameters + const auto n0 = params.numerator[0]; + const auto n1 = params.numerator[1]; + const auto n2 = params.numerator[2]; + const auto d0 = params.denominator[0]; + const auto d1 = params.denominator[1]; + + // Biquad filter states + auto s0 = state[0]; + auto s1 = state[1]; + + constexpr s64 MIN = std::numeric_limits::min(); + constexpr s64 MAX = std::numeric_limits::max(); + + for (int i = 0; i < sample_count; ++i) { + const auto sample = static_cast(input[i]); + const auto f = (sample * n0 + s0 + 0x4000) >> 15; + const auto y = std::clamp(f, MIN, MAX); + s0 = sample * n1 + y * d0 + s1; + s1 = sample * n2 + y * d1; + output[i] = static_cast(y); + } + + state[0] = s0; + state[1] = s1; +} + +ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) { + if (splitter_id == AudioCommon::NO_SPLITTER) { + return nullptr; + } + return splitter_context.GetDestinationData(splitter_id, index); +} + +void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, + s32 channel, s32 node_id) { + const auto last = static_cast(last_volume * 32768.0f); + const auto current = static_cast(current_volume * 32768.0f); + const auto delta = static_cast((static_cast(current) - static_cast(last)) / + static_cast(worker_params.sample_count)); + + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, " + "last_volume={}, current_volume={}", + node_id, GetMixChannelBufferOffset(channel), + GetMixChannelBufferOffset(channel), last_volume, current_volume); + } + // Apply generic gain on samples + ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta, + worker_params.sample_count); +} + +void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volumes, + const MixVolumeBuffer& last_mix_volumes, + VoiceState& dsp_state, s32 mix_buffer_offset, + s32 mix_buffer_count, s32 voice_index, s32 node_id) { + // Loop all our mix buffers + for (s32 i = 0; i < mix_buffer_count; i++) { + if (last_mix_volumes[i] != 0.0f || mix_volumes[i] != 0.0f) { + const auto delta = static_cast((mix_volumes[i] - last_mix_volumes[i])) / + static_cast(worker_params.sample_count); + + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, " + "output={}, last_volume={}, current_volume={}", + node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i], + mix_volumes[i]); + } + + dsp_state.previous_samples[i] = + ApplyMixRamp(GetMixBuffer(mix_buffer_offset + i), GetMixBuffer(voice_index), + last_mix_volumes[i], delta, worker_params.sample_count); + } else { + dsp_state.previous_samples[i] = 0; + } + } +} + +void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) { + if (dumping_frame) { + LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateSubMixCommand"); + } + // TODO(ogniK): Depop + // TODO(ogniK): Effects + GenerateMixCommands(mix_info); +} + +void CommandGenerator::GenerateMixCommands(ServerMixInfo& mix_info) { + if (!mix_info.HasAnyConnection()) { + return; + } + const auto& in_params = mix_info.GetInParams(); + if (in_params.dest_mix_id != AudioCommon::NO_MIX) { + const auto& dest_mix = mix_context.GetInfo(in_params.dest_mix_id); + const auto& dest_in_params = dest_mix.GetInParams(); + + const auto buffer_count = in_params.buffer_count; + + for (s32 i = 0; i < buffer_count; i++) { + for (s32 j = 0; j < dest_in_params.buffer_count; j++) { + const auto mixed_volume = in_params.volume * in_params.mix_volume[i][j]; + if (mixed_volume != 0.0f) { + GenerateMixCommand(dest_in_params.buffer_offset + j, + in_params.buffer_offset + i, mixed_volume, + in_params.node_id); + } + } + } + } else if (in_params.splitter_id != AudioCommon::NO_SPLITTER) { + s32 base{}; + while (const auto* destination_data = GetDestinationData(in_params.splitter_id, base++)) { + if (!destination_data->IsConfigured()) { + continue; + } + + const auto& dest_mix = mix_context.GetInfo(destination_data->GetMixId()); + const auto& dest_in_params = dest_mix.GetInParams(); + const auto mix_index = (base - 1) % in_params.buffer_count + in_params.buffer_offset; + for (std::size_t i = 0; i < dest_in_params.buffer_count; i++) { + const auto mixed_volume = in_params.volume * destination_data->GetMixVolume(i); + if (mixed_volume != 0.0f) { + GenerateMixCommand(dest_in_params.buffer_offset + i, mix_index, mixed_volume, + in_params.node_id); + } + } + } + } +} + +void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, + float volume, s32 node_id) { + + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}", + node_id, input_offset, output_offset, volume); + } + + auto* output = GetMixBuffer(output_offset); + const auto* input = GetMixBuffer(input_offset); + + const s32 gain = static_cast(volume * 32768.0f); + // Mix with loop unrolling + if (worker_params.sample_count % 4 == 0) { + ApplyMix<4>(output, input, gain, worker_params.sample_count); + } else if (worker_params.sample_count % 2 == 0) { + ApplyMix<2>(output, input, gain, worker_params.sample_count); + } else { + ApplyMix<1>(output, input, gain, worker_params.sample_count); + } +} + +void CommandGenerator::GenerateFinalMixCommand() { + if (dumping_frame) { + LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); + } + // TODO(ogniK): Depop + // TODO(ogniK): Effects + auto& mix_info = mix_context.GetFinalMixInfo(); + const auto in_params = mix_info.GetInParams(); + for (s32 i = 0; i < in_params.buffer_count; i++) { + const s32 gain = static_cast(in_params.volume * 32768.0f); + if (dumping_frame) { + LOG_CRITICAL( + Audio, + "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}", + in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i, + in_params.volume); + } + ApplyGainWithoutDelta(GetMixBuffer(in_params.buffer_offset + i), + GetMixBuffer(in_params.buffer_offset + i), gain, + worker_params.sample_count); + } +} + +s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 sample_count, s32 channel, std::size_t mix_offset) { + auto& in_params = voice_info.GetInParams(); + const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; + if (wave_buffer.buffer_address == 0) { + return 0; + } + if (wave_buffer.buffer_size == 0) { + return 0; + } + if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { + return 0; + } + const auto samples_remaining = + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto start_offset = + ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count) * + sizeof(s16); + const auto buffer_pos = wave_buffer.buffer_address + start_offset; + const auto samples_processed = std::min(sample_count, samples_remaining); + + if (in_params.channel_count == 1) { + std::vector buffer(samples_processed); + memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16)); + for (std::size_t i = 0; i < buffer.size(); i++) { + sample_buffer[mix_offset + i] = buffer[i]; + } + } else { + const auto channel_count = in_params.channel_count; + std::vector buffer(samples_processed * channel_count); + memory.ReadBlock(buffer_pos, buffer.data(), buffer.size() * sizeof(s16)); + + for (std::size_t i = 0; i < samples_processed; i++) { + sample_buffer[mix_offset + i] = buffer[i * channel_count + channel]; + } + } + + return samples_processed; +} +s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, + s32 sample_count, s32 channel, std::size_t mix_offset) { + auto& in_params = voice_info.GetInParams(); + const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; + if (wave_buffer.buffer_address == 0) { + return 0; + } + if (wave_buffer.buffer_size == 0) { + return 0; + } + if (wave_buffer.end_sample_offset < wave_buffer.start_sample_offset) { + return 0; + } + + const auto samples_remaining = + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto start_offset = + ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count); + const auto buffer_pos = wave_buffer.buffer_address + start_offset; + + const auto samples_processed = std::min(sample_count, samples_remaining); + + if (start_offset > dsp_state.adpcm_samples.size()) { + dsp_state.adpcm_samples.clear(); + } + + // TODO(ogniK): Proper ADPCM streaming + if (dsp_state.adpcm_samples.empty()) { + Codec::ADPCM_Coeff coeffs; + memory.ReadBlock(in_params.additional_params_address, coeffs.data(), + sizeof(Codec::ADPCM_Coeff)); + std::vector buffer(wave_buffer.buffer_size); + memory.ReadBlock(wave_buffer.buffer_address, buffer.data(), buffer.size()); + dsp_state.adpcm_samples = + std::move(Codec::DecodeADPCM(buffer.data(), buffer.size(), coeffs, dsp_state.context)); + } + + for (std::size_t i = 0; i < samples_processed; i++) { + const auto sample_offset = i + start_offset; + sample_buffer[mix_offset + i] = + dsp_state.adpcm_samples[sample_offset * in_params.channel_count + channel]; + } + + return samples_processed; +} + +s32* CommandGenerator::GetMixBuffer(std::size_t index) { + return mix_buffer.data() + (index * worker_params.sample_count); +} + +const s32* CommandGenerator::GetMixBuffer(std::size_t index) const { + return mix_buffer.data() + (index * worker_params.sample_count); +} + +std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { + return worker_params.mix_buffer_count + channel; +} + +s32* CommandGenerator::GetChannelMixBuffer(s32 channel) { + return GetMixBuffer(worker_params.mix_buffer_count + channel); +} + +const s32* CommandGenerator::GetChannelMixBuffer(s32 channel) const { + return GetMixBuffer(worker_params.mix_buffer_count + channel); +} + +void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, + VoiceState& dsp_state, s32 channel, + s32 target_sample_rate, s32 sample_count, + s32 node_id) { + auto& in_params = voice_info.GetInParams(); + if (dumping_frame) { + LOG_CRITICAL(Audio, + "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, " + "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}", + 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; }); + + const auto resample_rate = static_cast( + static_cast(in_params.sample_rate) / static_cast(target_sample_rate) * + static_cast(static_cast(in_params.pitch * 32768.0f))); + auto* output_base = output; + if ((dsp_state.fraction + sample_count * resample_rate) > (SCALED_MIX_BUFFER_SIZE - 4ULL)) { + return; + } + + auto min_required_samples = + std::min(static_cast(SCALED_MIX_BUFFER_SIZE) - dsp_state.fraction, resample_rate); + if (min_required_samples >= sample_count) { + min_required_samples = sample_count; + } + + std::size_t temp_mix_offset{}; + bool is_buffer_completed{false}; + auto samples_remaining = sample_count; + while (samples_remaining > 0 && !is_buffer_completed) { + const auto samples_to_output = std::min(samples_remaining, min_required_samples); + const auto samples_to_read = (samples_to_output * resample_rate + dsp_state.fraction) >> 15; + + if (!in_params.behavior_flags.is_pitch_and_src_skipped) { + // Append sample histtory for resampler + for (std::size_t i = 0; i < AudioCommon::MAX_SAMPLE_HISTORY; i++) { + sample_buffer[temp_mix_offset + i] = dsp_state.sample_history[i]; + } + temp_mix_offset += 4; + } + + s32 samples_read{}; + while (samples_read < samples_to_read) { + const auto& wave_buffer = in_params.wave_buffer[dsp_state.wave_buffer_index]; + // No more data can be read + if (!dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index]) { + is_buffer_completed = true; + break; + } + + if (in_params.sample_format == SampleFormat::Adpcm && dsp_state.offset == 0 && + wave_buffer.context_address != 0 && wave_buffer.context_size != 0) { + // TODO(ogniK): ADPCM loop context + } + + s32 samples_decoded{0}; + switch (in_params.sample_format) { + case SampleFormat::Pcm16: + samples_decoded = DecodePcm16(voice_info, dsp_state, samples_to_read - samples_read, + channel, temp_mix_offset); + break; + case SampleFormat::Adpcm: + samples_decoded = DecodeAdpcm(voice_info, dsp_state, samples_to_read - samples_read, + channel, temp_mix_offset); + break; + default: + UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + } + + temp_mix_offset += samples_decoded; + samples_read += samples_decoded; + dsp_state.offset += samples_decoded; + dsp_state.played_sample_count += samples_decoded; + + if (dsp_state.offset >= + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) || + samples_decoded == 0) { + // Reset our sample offset + dsp_state.offset = 0; + if (wave_buffer.is_looping) { + if (samples_decoded == 0) { + // End of our buffer + is_buffer_completed = true; + break; + } + + if (in_params.behavior_flags.is_played_samples_reset_at_loop_point.Value()) { + dsp_state.played_sample_count = 0; + } + } else { + if (in_params.sample_format == SampleFormat::Adpcm) { + // TODO(ogniK): Remove this when ADPCM streaming implemented + dsp_state.adpcm_samples.clear(); + } + + // Update our wave buffer states + dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; + dsp_state.wave_buffer_consumed++; + dsp_state.wave_buffer_index = + (dsp_state.wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS; + if (wave_buffer.end_of_stream) { + dsp_state.played_sample_count = 0; + } + } + } + } + + if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) { + // No need to resample + memcpy(output, sample_buffer.data(), samples_read * sizeof(s32)); + } else { + std::memset(sample_buffer.data() + temp_mix_offset, 0, + sizeof(s32) * (samples_to_read - samples_read)); + AudioCore::Resample(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; + } +} + +} // namespace AudioCore -- cgit v1.2.3 From 7cdad5d1fb25f7979386c7696c82b6c5b74214c9 Mon Sep 17 00:00:00 2001 From: David Marcec Date: Sat, 25 Jul 2020 12:32:05 +1000 Subject: Address issues --- src/audio_core/behavior_info.cpp | 2 +- src/audio_core/command_generator.cpp | 155 +++++++++++++++++------------------ src/audio_core/command_generator.h | 2 + src/audio_core/effect_context.cpp | 11 ++- src/audio_core/effect_context.h | 1 + src/audio_core/mix_context.cpp | 1 + src/audio_core/splitter_context.cpp | 10 +-- src/audio_core/voice_context.cpp | 23 ++---- 8 files changed, 104 insertions(+), 101 deletions(-) (limited to 'src/audio_core/command_generator.cpp') diff --git a/src/audio_core/behavior_info.cpp b/src/audio_core/behavior_info.cpp index b7cd8f17d8..5d62adb0b1 100644 --- a/src/audio_core/behavior_info.cpp +++ b/src/audio_core/behavior_info.cpp @@ -99,7 +99,7 @@ bool BehaviorInfo::IsSplitterBugFixed() const { void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) { dst.error_count = static_cast(error_count); - std::memcpy(dst.errors.data(), errors.data(), sizeof(ErrorInfo) * dst.errors.size()); + std::copy(errors.begin(), errors.begin() + error_count, dst.errors.begin()); } } // namespace AudioCore diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 722f9b6c5e..440bfc1406 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -10,12 +10,12 @@ namespace AudioCore { namespace { -static constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; -static constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; +constexpr std::size_t MIX_BUFFER_SIZE = 0x3f00; +constexpr std::size_t SCALED_MIX_BUFFER_SIZE = MIX_BUFFER_SIZE << 15ULL; template void ApplyMix(s32* output, const s32* input, s32 gain, s32 sample_count) { - for (s32 i = 0; i < sample_count; i += N) { + 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] += static_cast((static_cast(input[i + j]) * gain + 0x4000) >> 15); @@ -59,13 +59,13 @@ CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_p CommandGenerator::~CommandGenerator() = default; void CommandGenerator::ClearMixBuffers() { - std::memset(mix_buffer.data(), 0, mix_buffer.size() * sizeof(s32)); - std::memset(sample_buffer.data(), 0, sample_buffer.size() * sizeof(s32)); + std::fill(mix_buffer.begin(), mix_buffer.end(), 0); + std::fill(sample_buffer.begin(), sample_buffer.end(), 0); } void CommandGenerator::GenerateVoiceCommands() { if (dumping_frame) { - LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateVoiceCommands"); + LOG_DEBUG(Audio, "(DSP_TRACE) GenerateVoiceCommands"); } // Grab all our voices const auto voice_count = voice_context.GetVoiceCount(); @@ -168,24 +168,26 @@ void CommandGenerator::GenerateFinalMixCommands() { } void CommandGenerator::PreCommand() { - if (dumping_frame) { - for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) { - const auto& base = splitter_context.GetInfo(i); - std::string graph = fmt::format("b[{}]", i); - auto* head = base.GetHead(); - while (head != nullptr) { - graph += fmt::format("->{}", head->GetMixId()); - head = head->GetNextDestination(); - } - LOG_CRITICAL(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph); + if (!dumping_frame) { + return; + } + for (std::size_t i = 0; i < splitter_context.GetInfoCount(); i++) { + const auto& base = splitter_context.GetInfo(i); + std::string graph = fmt::format("b[{}]", i); + auto* head = base.GetHead(); + while (head != nullptr) { + graph += fmt::format("->{}", head->GetMixId()); + head = head->GetNextDestination(); } + LOG_DEBUG(Audio, "(DSP_TRACE) SplitterGraph splitter_info={}, {}", i, graph); } } void CommandGenerator::PostCommand() { - if (dumping_frame) { - dumping_frame = false; + if (!dumping_frame) { + return; } + dumping_frame = false; } void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, @@ -194,29 +196,31 @@ void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, Vo const auto depop = in_params.should_depop; if (in_params.mix_id != AudioCommon::NO_MIX) { - auto& mix_info = mix_context.GetInfo(in_params.mix_id); + [[maybe_unused]] auto& mix_info = mix_context.GetInfo(in_params.mix_id); // mix_info. // TODO(ogniK): Depop to destination mix } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { // TODO(ogniK): Depop to splitter } - if (!depop) { - switch (in_params.sample_format) { - case SampleFormat::Pcm16: - DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, - worker_params.sample_rate, worker_params.sample_count, - in_params.node_id); - break; - case SampleFormat::Adpcm: - ASSERT(channel == 0 && in_params.channel_count == 1); - DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, - worker_params.sample_rate, worker_params.sample_count, - in_params.node_id); - break; - default: - UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); - } + if (depop) { + return; + } + + switch (in_params.sample_format) { + case SampleFormat::Pcm16: + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + case SampleFormat::Adpcm: + ASSERT(channel == 0 && in_params.channel_count == 1); + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + default: + UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); } } @@ -233,8 +237,7 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic // Reinitialize our biquad filter state if it was enabled previously if (!in_params.was_biquad_filter_enabled[i]) { - std::memset(dsp_state.biquad_filter_state.data(), 0, - dsp_state.biquad_filter_state.size() * sizeof(s64)); + dsp_state.biquad_filter_state.fill(0); } // Generate biquad filter @@ -248,39 +251,34 @@ void AudioCore::CommandGenerator::GenerateBiquadFilterCommand( s32 mix_buffer, const BiquadFilterParameter& params, std::array& state, std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id) { if (dumping_frame) { - LOG_CRITICAL(Audio, - "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, " - "input_mix_buffer={}, output_mix_buffer={}", - node_id, input_offset, output_offset); + LOG_DEBUG(Audio, + "(DSP_TRACE) GenerateBiquadFilterCommand node_id={}, " + "input_mix_buffer={}, output_mix_buffer={}", + node_id, input_offset, output_offset); } const auto* input = GetMixBuffer(input_offset); auto* output = GetMixBuffer(output_offset); // Biquad filter parameters - const auto n0 = params.numerator[0]; - const auto n1 = params.numerator[1]; - const auto n2 = params.numerator[2]; - const auto d0 = params.denominator[0]; - const auto d1 = params.denominator[1]; + const auto [n0, n1, n2] = params.numerator; + const auto [d0, d1] = params.denominator; // Biquad filter states - auto s0 = state[0]; - auto s1 = state[1]; + auto [s0, s1] = state; - constexpr s64 MIN = std::numeric_limits::min(); - constexpr s64 MAX = std::numeric_limits::max(); + constexpr s64 int32_min = std::numeric_limits::min(); + constexpr s64 int32_max = std::numeric_limits::max(); for (int i = 0; i < sample_count; ++i) { - const auto sample = static_cast(input[i]); + const auto sample = static_cast(input[i]); const auto f = (sample * n0 + s0 + 0x4000) >> 15; - const auto y = std::clamp(f, MIN, MAX); + const auto y = std::clamp(f, int32_min, int32_max); s0 = sample * n1 + y * d0 + s1; s1 = sample * n2 + y * d1; output[i] = static_cast(y); } - state[0] = s0; - state[1] = s1; + state = {s0, s1}; } ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) { @@ -298,11 +296,11 @@ void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float curren static_cast(worker_params.sample_count)); if (dumping_frame) { - LOG_CRITICAL(Audio, - "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, " - "last_volume={}, current_volume={}", - node_id, GetMixChannelBufferOffset(channel), - GetMixChannelBufferOffset(channel), last_volume, current_volume); + LOG_DEBUG(Audio, + "(DSP_TRACE) GenerateVolumeRampCommand node_id={}, input={}, output={}, " + "last_volume={}, current_volume={}", + node_id, GetMixChannelBufferOffset(channel), GetMixChannelBufferOffset(channel), + last_volume, current_volume); } // Apply generic gain on samples ApplyGain(GetChannelMixBuffer(channel), GetChannelMixBuffer(channel), last, delta, @@ -320,11 +318,11 @@ void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volume static_cast(worker_params.sample_count); if (dumping_frame) { - LOG_CRITICAL(Audio, - "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, " - "output={}, last_volume={}, current_volume={}", - node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i], - mix_volumes[i]); + LOG_DEBUG(Audio, + "(DSP_TRACE) GenerateVoiceMixCommand node_id={}, input={}, " + "output={}, last_volume={}, current_volume={}", + node_id, voice_index, mix_buffer_offset + i, last_mix_volumes[i], + mix_volumes[i]); } dsp_state.previous_samples[i] = @@ -338,7 +336,7 @@ void CommandGenerator::GenerateVoiceMixCommand(const MixVolumeBuffer& mix_volume void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) { if (dumping_frame) { - LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateSubMixCommand"); + LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand"); } // TODO(ogniK): Depop // TODO(ogniK): Effects @@ -391,9 +389,9 @@ void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t float volume, s32 node_id) { if (dumping_frame) { - LOG_CRITICAL(Audio, - "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}", - node_id, input_offset, output_offset, volume); + LOG_DEBUG(Audio, + "(DSP_TRACE) GenerateMixCommand node_id={}, input={}, output={}, volume={}", + node_id, input_offset, output_offset, volume); } auto* output = GetMixBuffer(output_offset); @@ -412,7 +410,7 @@ void CommandGenerator::GenerateMixCommand(std::size_t output_offset, std::size_t void CommandGenerator::GenerateFinalMixCommand() { if (dumping_frame) { - LOG_CRITICAL(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); + LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); } // TODO(ogniK): Depop // TODO(ogniK): Effects @@ -421,7 +419,7 @@ void CommandGenerator::GenerateFinalMixCommand() { for (s32 i = 0; i < in_params.buffer_count; i++) { const s32 gain = static_cast(in_params.volume * 32768.0f); if (dumping_frame) { - LOG_CRITICAL( + LOG_DEBUG( Audio, "(DSP_TRACE) ApplyGainWithoutDelta node_id={}, input={}, output={}, volume={}", in_params.node_id, in_params.buffer_offset + i, in_params.buffer_offset + i, @@ -544,11 +542,11 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o s32 node_id) { auto& in_params = voice_info.GetInParams(); if (dumping_frame) { - LOG_CRITICAL(Audio, - "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, " - "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}", - node_id, channel, in_params.sample_format, sample_count, in_params.sample_rate, - in_params.mix_id, in_params.splitter_info_id); + LOG_DEBUG(Audio, + "(DSP_TRACE) DecodeFromWaveBuffers, node_id={}, channel={}, " + "format={}, sample_count={}, sample_rate={}, mix_id={}, splitter_id={}", + 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; }); @@ -649,10 +647,11 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o if (in_params.behavior_flags.is_pitch_and_src_skipped.Value()) { // No need to resample - memcpy(output, sample_buffer.data(), samples_read * sizeof(s32)); + std::memcpy(output, sample_buffer.data(), samples_read * sizeof(s32)); } else { - std::memset(sample_buffer.data() + temp_mix_offset, 0, - sizeof(s32) * (samples_to_read - samples_read)); + 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); // Resample diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index e0d7510fc1..3ad8973b5b 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h @@ -19,7 +19,9 @@ class MixContext; class SplitterContext; class ServerSplitterDestinationData; class ServerMixInfo; + using MixVolumeBuffer = std::array; + class CommandGenerator { public: explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params, diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp index c42e71c1c7..2497d2f323 100644 --- a/src/audio_core/effect_context.cpp +++ b/src/audio_core/effect_context.cpp @@ -2,13 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include #include "audio_core/effect_context.h" namespace AudioCore { EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) { - for (std::size_t i = 0; i < effect_count; i++) { - effects.push_back(std::make_unique()); - } + effects.reserve(effect_count); + std::generate_n(std::back_inserter(effects), effect_count, + [] { return std::make_unique(); }); } EffectContext::~EffectContext() = default; @@ -20,6 +21,10 @@ EffectBase* EffectContext::GetInfo(std::size_t i) { return effects.at(i).get(); } +const EffectBase* EffectContext::GetInfo(std::size_t i) const { + return effects.at(i).get(); +} + EffectStubbed::EffectStubbed() : EffectBase::EffectBase() {} EffectStubbed::~EffectStubbed() = default; diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h index 09aedf385a..e3c3672966 100644 --- a/src/audio_core/effect_context.h +++ b/src/audio_core/effect_context.h @@ -106,6 +106,7 @@ public: std::size_t GetCount() const; EffectBase* GetInfo(std::size_t i); + const EffectBase* GetInfo(std::size_t i) const; private: std::size_t effect_count{}; diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp index 8e150db03b..f6f119a113 100644 --- a/src/audio_core/mix_context.cpp +++ b/src/audio_core/mix_context.cpp @@ -16,6 +16,7 @@ void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_c infos.resize(info_count); auto& final_mix = GetInfo(AudioCommon::FINAL_MIX); final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX; + sorted_info.reserve(infos.size()); for (auto& info : infos) { sorted_info.push_back(&info); } diff --git a/src/audio_core/splitter_context.cpp b/src/audio_core/splitter_context.cpp index c0be26be12..79bb2f516d 100644 --- a/src/audio_core/splitter_context.cpp +++ b/src/audio_core/splitter_context.cpp @@ -173,7 +173,7 @@ void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_ bool SplitterContext::Update(const std::vector& input, std::size_t& input_offset, std::size_t& bytes_read) { - auto UpdateOffsets = [&](std::size_t read) { + const auto UpdateOffsets = [&](std::size_t read) { input_offset += read; bytes_read += read; }; @@ -286,7 +286,7 @@ void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count, bool SplitterContext::UpdateInfo(const std::vector& input, std::size_t& input_offset, std::size_t& bytes_read, s32 in_splitter_count) { - auto UpdateOffsets = [&](std::size_t read) { + const auto UpdateOffsets = [&](std::size_t read) { input_offset += read; bytes_read += read; }; @@ -326,7 +326,7 @@ bool SplitterContext::UpdateInfo(const std::vector& input, std::size_t& inpu bool SplitterContext::UpdateData(const std::vector& input, std::size_t& input_offset, std::size_t& bytes_read, s32 in_data_count) { - auto UpdateOffsets = [&](std::size_t read) { + const auto UpdateOffsets = [&](std::size_t read) { input_offset += read; bytes_read += read; }; @@ -412,9 +412,9 @@ bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info, NodeStates::NodeStates() = default; NodeStates::~NodeStates() = default; -void NodeStates::Initialize(std::size_t _node_count) { +void NodeStates::Initialize(std::size_t node_count_) { // Setup our work parameters - node_count = _node_count; + node_count = node_count_; was_node_found.resize(node_count); was_node_completed.resize(node_count); index_list.resize(node_count); diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp index 038595ae04..1d8f69844f 100644 --- a/src/audio_core/voice_context.cpp +++ b/src/audio_core/voice_context.cpp @@ -29,12 +29,12 @@ void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_param in_use = in_params.in_use; // Update our mix volumes only if it's in use if (in_params.in_use) { - std::copy(in_params.mix_volume.begin(), in_params.mix_volume.end(), mix_volume.begin()); + mix_volume = in_params.mix_volume; } } void ServerVoiceChannelResource::UpdateLastMixVolumes() { - std::copy(mix_volume.begin(), mix_volume.end(), last_mix_volume.begin()); + last_mix_volume = mix_volume; } const std::array& @@ -64,8 +64,7 @@ void ServerVoiceInfo::Initialize() { in_params.pitch = 0.0f; in_params.volume = 0.0f; in_params.last_volume = 0.0f; - std::memset(in_params.biquad_filter.data(), 0, - sizeof(BiquadFilterParameter) * in_params.biquad_filter.size()); + in_params.biquad_filter.fill({}); in_params.wave_buffer_count = 0; in_params.wave_bufffer_head = 0; in_params.mix_id = AudioCommon::NO_MIX; @@ -78,8 +77,7 @@ void ServerVoiceInfo::Initialize() { in_params.voice_drop_flag = false; in_params.buffer_mapped = false; in_params.wave_buffer_flush_request_count = 0; - std::fill(in_params.was_biquad_filter_enabled.begin(), - in_params.was_biquad_filter_enabled.end(), false); + in_params.was_biquad_filter_enabled.fill(false); for (auto& wave_buffer : in_params.wave_buffer) { wave_buffer.start_sample_offset = 0; @@ -126,8 +124,7 @@ void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in, in_params.channel_count = voice_in.channel_count; in_params.pitch = voice_in.pitch; in_params.volume = voice_in.volume; - std::memcpy(in_params.biquad_filter.data(), voice_in.biquad_filter.data(), - sizeof(BiquadFilterParameter) * voice_in.biquad_filter.size()); + in_params.biquad_filter = voice_in.biquad_filter; in_params.wave_buffer_count = voice_in.wave_buffer_count; in_params.wave_bufffer_head = voice_in.wave_buffer_head; if (behavior_info.IsFlushVoiceWaveBuffersSupported()) { @@ -308,7 +305,7 @@ void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) { const auto channel_resource = in_params.voice_channel_resource_id[i]; auto& dsp_state = voice_context.GetDspSharedState(static_cast(channel_resource)); - std::memset(&dsp_state, 0, sizeof(VoiceState)); + dsp_state = {}; voice_context.GetChannelResource(static_cast(channel_resource)) .UpdateLastMixVolumes(); } @@ -362,9 +359,8 @@ bool ServerVoiceInfo::UpdateParametersForCommandGeneration( dsp_state->offset = 0; dsp_state->played_sample_count = 0; dsp_state->fraction = 0; - std::memset(dsp_state->sample_history.data(), 0, - sizeof(s32) * dsp_state->sample_history.size()); - std::memset(&dsp_state->context, 0, sizeof(dsp_state->context)); + dsp_state->sample_history.fill(0); + dsp_state->context = {}; } in_params.current_playstate = ServerPlayState::Stop; @@ -524,8 +520,7 @@ void VoiceContext::SortInfo() { } void VoiceContext::UpdateStateByDspShared() { - std::memcpy(voice_states.data(), dsp_voice_states.data(), - sizeof(VoiceState) * dsp_voice_states.size()); + voice_states = dsp_voice_states; } } // namespace AudioCore -- cgit v1.2.3 From 6368b02526b5e695b174e4179d470a285e03eebb Mon Sep 17 00:00:00 2001 From: David Marcec Date: Thu, 30 Jul 2020 18:16:57 +1000 Subject: adpcm streaming --- src/audio_core/command_generator.cpp | 53 +++++++++++++++++++++--------------- src/audio_core/command_generator.h | 1 - src/audio_core/voice_context.cpp | 3 +- src/audio_core/voice_context.h | 2 -- 4 files changed, 32 insertions(+), 27 deletions(-) (limited to 'src/audio_core/command_generator.cpp') diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 440bfc1406..0c3b278ea3 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -470,6 +470,7 @@ s32 CommandGenerator::DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_s return samples_processed; } + s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, s32 channel, std::size_t mix_offset) { auto& in_params = voice_info.GetInParams(); @@ -486,33 +487,45 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s const auto samples_remaining = (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto samples_processed = std::min(sample_count, samples_remaining); const auto start_offset = ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count); - const auto buffer_pos = wave_buffer.buffer_address + start_offset; + const auto end_offset = start_offset + samples_processed; - const auto samples_processed = std::min(sample_count, samples_remaining); + constexpr std::size_t FRAME_LEN = 8; + constexpr std::size_t SAMPLES_PER_FRAME = 14; - if (start_offset > dsp_state.adpcm_samples.size()) { - dsp_state.adpcm_samples.clear(); - } + // Base buffer position + const auto start_frame_index = start_offset / SAMPLES_PER_FRAME; + const auto start_frame_buffer = start_frame_index * FRAME_LEN; - // TODO(ogniK): Proper ADPCM streaming - if (dsp_state.adpcm_samples.empty()) { - Codec::ADPCM_Coeff coeffs; - memory.ReadBlock(in_params.additional_params_address, coeffs.data(), - sizeof(Codec::ADPCM_Coeff)); - std::vector buffer(wave_buffer.buffer_size); - memory.ReadBlock(wave_buffer.buffer_address, buffer.data(), buffer.size()); - dsp_state.adpcm_samples = - std::move(Codec::DecodeADPCM(buffer.data(), buffer.size(), coeffs, dsp_state.context)); - } + const auto end_frame_index = end_offset / SAMPLES_PER_FRAME; + const auto end_frame_buffer = end_frame_index * FRAME_LEN; + + const auto position_in_frame = start_offset % SAMPLES_PER_FRAME; + + const auto buffer_size = (1 + (end_frame_index - start_frame_index)) * FRAME_LEN; + + Codec::ADPCM_Coeff coeffs; + memory.ReadBlock(in_params.additional_params_address, coeffs.data(), + sizeof(Codec::ADPCM_Coeff)); + std::vector buffer(buffer_size); + memory.ReadBlock(wave_buffer.buffer_address + start_frame_buffer, buffer.data(), buffer.size()); + const auto adpcm_samples = + std::move(Codec::DecodeADPCM(buffer.data(), buffer.size(), coeffs, dsp_state.context)); for (std::size_t i = 0; i < samples_processed; i++) { - const auto sample_offset = i + start_offset; - sample_buffer[mix_offset + i] = - dsp_state.adpcm_samples[sample_offset * in_params.channel_count + channel]; + const auto sample_offset = position_in_frame + i * in_params.channel_count + channel; + const auto sample = adpcm_samples[sample_offset]; + sample_buffer[mix_offset + i] = sample; } + // Manually set our context + const auto frame_before_final = (end_frame_index - start_frame_index) - 1; + const auto frame_before_final_off = frame_before_final * SAMPLES_PER_FRAME; + dsp_state.context.yn2 = adpcm_samples[frame_before_final_off + 12]; + dsp_state.context.yn1 = adpcm_samples[frame_before_final_off + 13]; + return samples_processed; } @@ -628,10 +641,6 @@ void CommandGenerator::DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* o dsp_state.played_sample_count = 0; } } else { - if (in_params.sample_format == SampleFormat::Adpcm) { - // TODO(ogniK): Remove this when ADPCM streaming implemented - dsp_state.adpcm_samples.clear(); - } // Update our wave buffer states dsp_state.is_wave_buffer_valid[dsp_state.wave_buffer_index] = false; diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index 3ad8973b5b..3f49c13030 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h @@ -71,7 +71,6 @@ private: 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 Resample(s32* output, s32* input, s32 pitch, s32& fraction, s32 sample_count); AudioCommon::AudioRendererParameter& worker_params; VoiceContext& voice_context; diff --git a/src/audio_core/voice_context.cpp b/src/audio_core/voice_context.cpp index 16ae1afe84..1d8f69844f 100644 --- a/src/audio_core/voice_context.cpp +++ b/src/audio_core/voice_context.cpp @@ -520,8 +520,7 @@ void VoiceContext::SortInfo() { } void VoiceContext::UpdateStateByDspShared() { - std::memcpy(voice_states.data(), dsp_voice_states.data(), - sizeof(VoiceState) * dsp_voice_states.size()); + voice_states = dsp_voice_states; } } // namespace AudioCore diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h index b1d5547664..13b0a7f0f1 100644 --- a/src/audio_core/voice_context.h +++ b/src/audio_core/voice_context.h @@ -101,8 +101,6 @@ struct VoiceState { u32 external_context_size{}; bool is_external_context_used{}; bool voice_dropped{}; - // TODO(ogniK): Hack until ADPCM streaming is implemented - std::vector adpcm_samples{}; }; class VoiceChannelResource { -- cgit v1.2.3 From 0cd3851934cb44aabd22ea323c3edd9ae12070ce Mon Sep 17 00:00:00 2001 From: David Marcec Date: Sat, 1 Aug 2020 16:25:08 +1000 Subject: mix buffer depopping --- src/audio_core/command_generator.cpp | 123 ++++++++++++++++++++++++++--------- src/audio_core/command_generator.h | 8 ++- 2 files changed, 101 insertions(+), 30 deletions(-) (limited to 'src/audio_core/command_generator.cpp') diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 0c3b278ea3..8f89292be1 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -46,6 +46,24 @@ void ApplyGainWithoutDelta(s32* output, const s32* input, s32 gain, s32 sample_c } } +s32 ApplyMixDepop(s32* 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++) { + final_sample = static_cast((static_cast(final_sample) * delta) >> 15); + if (positive) { + output[i] += final_sample; + } else { + output[i] -= final_sample; + } + } + if (positive) { + return final_sample; + } else { + return -final_sample; + } +} + } // namespace CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params, @@ -55,12 +73,15 @@ CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_p splitter_context(splitter_context), memory(memory), mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * worker_params.sample_count), - sample_buffer(MIX_BUFFER_SIZE) {} + sample_buffer(MIX_BUFFER_SIZE), + depop_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * + worker_params.sample_count) {} CommandGenerator::~CommandGenerator() = default; void CommandGenerator::ClearMixBuffers() { std::fill(mix_buffer.begin(), mix_buffer.end(), 0); std::fill(sample_buffer.begin(), sample_buffer.end(), 0); + // std::fill(depop_buffer.begin(), depop_buffer.end(), 0); } void CommandGenerator::GenerateVoiceCommands() { @@ -195,32 +216,39 @@ void CommandGenerator::GenerateDataSourceCommand(ServerVoiceInfo& voice_info, Vo auto& in_params = voice_info.GetInParams(); const auto depop = in_params.should_depop; - if (in_params.mix_id != AudioCommon::NO_MIX) { - [[maybe_unused]] auto& mix_info = mix_context.GetInfo(in_params.mix_id); - // mix_info. - // TODO(ogniK): Depop to destination mix - } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { - // TODO(ogniK): Depop to splitter - } - if (depop) { - return; - } - - switch (in_params.sample_format) { - case SampleFormat::Pcm16: - DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, - worker_params.sample_rate, worker_params.sample_count, - in_params.node_id); - break; - case SampleFormat::Adpcm: - ASSERT(channel == 0 && in_params.channel_count == 1); - DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, - worker_params.sample_rate, worker_params.sample_count, - in_params.node_id); - break; - default: - UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + if (in_params.mix_id != AudioCommon::NO_MIX) { + auto& mix_info = mix_context.GetInfo(in_params.mix_id); + const auto& mix_in = mix_info.GetInParams(); + GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); + } else if (in_params.splitter_info_id != AudioCommon::NO_SPLITTER) { + s32 index{}; + while (const auto* destination = + GetDestinationData(in_params.splitter_info_id, index++)) { + if (!destination->IsConfigured()) { + continue; + } + auto& mix_info = mix_context.GetInfo(destination->GetMixId()); + const auto& mix_in = mix_info.GetInParams(); + GenerateDepopPrepareCommand(dsp_state, mix_in.buffer_count, mix_in.buffer_offset); + } + } + } else { + switch (in_params.sample_format) { + case SampleFormat::Pcm16: + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(channel), dsp_state, channel, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + case SampleFormat::Adpcm: + ASSERT(channel == 0 && in_params.channel_count == 1); + DecodeFromWaveBuffers(voice_info, GetChannelMixBuffer(0), dsp_state, 0, + worker_params.sample_rate, worker_params.sample_count, + in_params.node_id); + break; + default: + UNREACHABLE_MSG("Unimplemented sample format={}", in_params.sample_format); + } } } @@ -281,6 +309,34 @@ void AudioCore::CommandGenerator::GenerateBiquadFilterCommand( state = {s0, s1}; } +void CommandGenerator::GenerateDepopPrepareCommand(VoiceState& dsp_state, + std::size_t mix_buffer_count, + std::size_t mix_buffer_offset) { + for (std::size_t i = 0; i < mix_buffer_count; i++) { + auto& sample = dsp_state.previous_samples[i]; + if (sample != 0) { + depop_buffer[mix_buffer_offset + i] += sample; + sample = 0; + } + } +} + +void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, + std::size_t mix_buffer_offset, + s32 sample_rate) { + const std::size_t end_offset = + std::min(mix_buffer_offset + mix_buffer_count, GetTotalMixBufferCount()); + const s32 delta = sample_rate == 48000 ? 0x7B29 : 0x78CB; + for (std::size_t i = mix_buffer_offset; i < end_offset; i++) { + if (depop_buffer[i] == 0) { + continue; + } + + depop_buffer[i] = + ApplyMixDepop(GetMixBuffer(i), depop_buffer[i], delta, worker_params.sample_count); + } +} + ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) { if (splitter_id == AudioCommon::NO_SPLITTER) { return nullptr; @@ -338,7 +394,9 @@ void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) { if (dumping_frame) { LOG_DEBUG(Audio, "(DSP_TRACE) GenerateSubMixCommand"); } - // TODO(ogniK): Depop + auto& in_params = mix_info.GetInParams(); + GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, + in_params.sample_rate); // TODO(ogniK): Effects GenerateMixCommands(mix_info); } @@ -412,10 +470,13 @@ void CommandGenerator::GenerateFinalMixCommand() { if (dumping_frame) { LOG_DEBUG(Audio, "(DSP_TRACE) GenerateFinalMixCommand"); } - // TODO(ogniK): Depop - // TODO(ogniK): Effects auto& mix_info = mix_context.GetFinalMixInfo(); const auto in_params = mix_info.GetInParams(); + + GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, + in_params.sample_rate); + // TODO(ogniK): Effects + for (s32 i = 0; i < in_params.buffer_count; i++) { const s32 gain = static_cast(in_params.volume * 32768.0f); if (dumping_frame) { @@ -541,6 +602,10 @@ std::size_t CommandGenerator::GetMixChannelBufferOffset(s32 channel) const { return worker_params.mix_buffer_count + channel; } +std::size_t CommandGenerator::GetTotalMixBufferCount() const { + return worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT; +} + s32* CommandGenerator::GetChannelMixBuffer(s32 channel) { return GetMixBuffer(worker_params.mix_buffer_count + channel); } diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index 3f49c13030..656ad81438 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h @@ -43,6 +43,8 @@ public: const s32* GetMixBuffer(std::size_t index) const; std::size_t GetMixChannelBufferOffset(s32 channel) const; + std::size_t GetTotalMixBufferCount() const; + private: void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel); void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state, @@ -61,7 +63,10 @@ private: void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params, std::array& state, std::size_t input_offset, std::size_t output_offset, s32 sample_count, s32 node_id); - void GenerateDepopPrepareCommand(VoiceState& dsp_state); + void GenerateDepopPrepareCommand(VoiceState& dsp_state, std::size_t mix_buffer_count, + std::size_t mix_buffer_offset); + void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, + std::size_t mix_buffer_offset, s32 sample_rate); ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index); // DSP Code @@ -79,6 +84,7 @@ private: Core::Memory::Memory& memory; std::vector mix_buffer{}; std::vector sample_buffer{}; + std::vector depop_buffer{}; bool dumping_frame{false}; }; } // namespace AudioCore -- cgit v1.2.3 From 2b0185e2a00cad8050279ba99d87bf3a3cd3bef0 Mon Sep 17 00:00:00 2001 From: David Marcec Date: Fri, 14 Aug 2020 21:04:28 +1000 Subject: Reworked ADPCM decoder to allow better streaming --- src/audio_core/command_generator.cpp | 119 +++++++++++++++++++++++++---------- src/audio_core/voice_context.h | 9 ++- 2 files changed, 95 insertions(+), 33 deletions(-) (limited to 'src/audio_core/command_generator.cpp') diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 8f89292be1..157140d687 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -546,46 +546,101 @@ s32 CommandGenerator::DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_s return 0; } - const auto samples_remaining = - (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; - const auto samples_processed = std::min(sample_count, samples_remaining); - const auto start_offset = - ((wave_buffer.start_sample_offset + dsp_state.offset) * in_params.channel_count); - const auto end_offset = start_offset + samples_processed; + constexpr std::array SIGNED_NIBBLES = { + {0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1}}; constexpr std::size_t FRAME_LEN = 8; + constexpr std::size_t NIBBLES_PER_SAMPLE = 16; constexpr std::size_t SAMPLES_PER_FRAME = 14; - // Base buffer position - const auto start_frame_index = start_offset / SAMPLES_PER_FRAME; - const auto start_frame_buffer = start_frame_index * FRAME_LEN; - - const auto end_frame_index = end_offset / SAMPLES_PER_FRAME; - const auto end_frame_buffer = end_frame_index * FRAME_LEN; - - const auto position_in_frame = start_offset % SAMPLES_PER_FRAME; - - const auto buffer_size = (1 + (end_frame_index - start_frame_index)) * FRAME_LEN; + auto frame_header = dsp_state.context.header; + s32 idx = (frame_header >> 4) & 0xf; + s32 scale = frame_header & 0xf; + s16 yn1 = dsp_state.context.yn1; + s16 yn2 = dsp_state.context.yn2; Codec::ADPCM_Coeff coeffs; memory.ReadBlock(in_params.additional_params_address, coeffs.data(), sizeof(Codec::ADPCM_Coeff)); - std::vector buffer(buffer_size); - memory.ReadBlock(wave_buffer.buffer_address + start_frame_buffer, buffer.data(), buffer.size()); - const auto adpcm_samples = - std::move(Codec::DecodeADPCM(buffer.data(), buffer.size(), coeffs, dsp_state.context)); - - for (std::size_t i = 0; i < samples_processed; i++) { - const auto sample_offset = position_in_frame + i * in_params.channel_count + channel; - const auto sample = adpcm_samples[sample_offset]; - sample_buffer[mix_offset + i] = sample; - } - - // Manually set our context - const auto frame_before_final = (end_frame_index - start_frame_index) - 1; - const auto frame_before_final_off = frame_before_final * SAMPLES_PER_FRAME; - dsp_state.context.yn2 = adpcm_samples[frame_before_final_off + 12]; - dsp_state.context.yn1 = adpcm_samples[frame_before_final_off + 13]; + + s32 coef1 = coeffs[idx * 2]; + s32 coef2 = coeffs[idx * 2 + 1]; + + const auto samples_remaining = + (wave_buffer.end_sample_offset - wave_buffer.start_sample_offset) - dsp_state.offset; + const auto samples_processed = std::min(sample_count, samples_remaining); + const auto sample_pos = wave_buffer.start_sample_offset + dsp_state.offset; + + const auto samples_remaining_in_frame = sample_pos % SAMPLES_PER_FRAME; + auto position_in_frame = ((sample_pos / SAMPLES_PER_FRAME) * NIBBLES_PER_SAMPLE) + + samples_remaining_in_frame + (samples_remaining_in_frame != 0 ? 2 : 0); + + const auto decode_sample = [&](const int nibble) -> s16 { + const int xn = nibble * (1 << scale); + // We first transform everything into 11 bit fixed point, perform the second order + // digital filter, then transform back. + // 0x400 == 0.5 in 11 bit fixed point. + // Filter: y[n] = x[n] + 0.5 + c1 * y[n-1] + c2 * y[n-2] + int val = ((xn << 11) + 0x400 + coef1 * yn1 + coef2 * yn2) >> 11; + // Clamp to output range. + val = std::clamp(val, -32768, 32767); + // Advance output feedback. + yn2 = yn1; + yn1 = val; + return static_cast(val); + }; + + std::size_t buffer_offset{}; + std::vector buffer( + std::max((samples_processed / FRAME_LEN) * SAMPLES_PER_FRAME, FRAME_LEN)); + memory.ReadBlock(wave_buffer.buffer_address + (position_in_frame / 2), buffer.data(), + buffer.size()); + std::size_t cur_mix_offset = mix_offset; + + auto remaining_samples = samples_processed; + while (remaining_samples > 0) { + if (position_in_frame % NIBBLES_PER_SAMPLE == 0) { + // Read header + frame_header = buffer[buffer_offset++]; + idx = (frame_header >> 4) & 0xf; + scale = frame_header & 0xf; + coef1 = coeffs[idx * 2]; + coef2 = coeffs[idx * 2 + 1]; + position_in_frame += 2; + + // Decode entire frame + if (remaining_samples >= SAMPLES_PER_FRAME) { + for (std::size_t i = 0; i < SAMPLES_PER_FRAME / 2; i++) { + + // Sample 1 + const s32 s0 = SIGNED_NIBBLES[buffer[buffer_offset] >> 4]; + const s32 s1 = SIGNED_NIBBLES[buffer[buffer_offset++] & 0xf]; + const s16 sample_1 = decode_sample(s0); + const s16 sample_2 = decode_sample(s1); + sample_buffer[cur_mix_offset++] = sample_1; + sample_buffer[cur_mix_offset++] = sample_2; + } + remaining_samples -= SAMPLES_PER_FRAME; + position_in_frame += SAMPLES_PER_FRAME; + continue; + } + } + // Decode mid frame + s32 current_nibble = buffer[buffer_offset]; + if (position_in_frame++ & 0x1) { + current_nibble &= 0xf; + buffer_offset++; + } else { + current_nibble >>= 4; + } + const s16 sample = decode_sample(SIGNED_NIBBLES[current_nibble]); + sample_buffer[cur_mix_offset++] = sample; + remaining_samples--; + } + + dsp_state.context.header = frame_header; + dsp_state.context.yn1 = yn1; + dsp_state.context.yn2 = yn2; return samples_processed; } diff --git a/src/audio_core/voice_context.h b/src/audio_core/voice_context.h index 13b0a7f0f1..59d3d7dfbe 100644 --- a/src/audio_core/voice_context.h +++ b/src/audio_core/voice_context.h @@ -85,6 +85,13 @@ struct BehaviorFlags { }; static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size"); +struct ADPCMContext { + u16 header{}; + s16 yn1{}; + s16 yn2{}; +}; +static_assert(sizeof(ADPCMContext) == 0x6, "ADPCMContext is an invalid size"); + struct VoiceState { s64 played_sample_count{}; s32 offset{}; @@ -95,7 +102,7 @@ struct VoiceState { s32 fraction{}; VAddr context_address{}; Codec::ADPCM_Coeff coeff{}; - Codec::ADPCMState context{}; + ADPCMContext context{}; std::array biquad_filter_state{}; std::array previous_samples{}; u32 external_context_size{}; -- cgit v1.2.3 From ac9d36c1dc9018c22a774b7a661d6f0505fc7756 Mon Sep 17 00:00:00 2001 From: David Marcec Date: Fri, 14 Aug 2020 23:20:20 +1000 Subject: Disable biquad filter --- src/audio_core/command_generator.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) (limited to 'src/audio_core/command_generator.cpp') diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 157140d687..73608c9ed7 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -163,11 +163,10 @@ void CommandGenerator::GenerateVoiceCommand(ServerVoiceInfo& voice_info) { destination_data->MarkDirty(); } } - } - - // Update biquad filter enabled states - for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { - in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled; + // Update biquad filter enabled states + for (std::size_t i = 0; i < AudioCommon::MAX_BIQUAD_FILTERS; i++) { + in_params.was_biquad_filter_enabled[i] = in_params.biquad_filter[i].enabled; + } } } } @@ -269,9 +268,11 @@ void CommandGenerator::GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voic } // Generate biquad filter - GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, dsp_state.biquad_filter_state, - mix_buffer_count + channel, mix_buffer_count + channel, - worker_params.sample_count, voice_info.GetInParams().node_id); + // GenerateBiquadFilterCommand(mix_buffer_count, biquad_filter, + // dsp_state.biquad_filter_state, + // mix_buffer_count + channel, mix_buffer_count + + // channel, worker_params.sample_count, + // voice_info.GetInParams().node_id); } } -- cgit v1.2.3 From f17f347e0371c1b58d62a70f3516fe8c325dcf38 Mon Sep 17 00:00:00 2001 From: David Marcec Date: Mon, 17 Aug 2020 01:23:55 +1000 Subject: Preliminary effects --- src/audio_core/audio_renderer.cpp | 9 +- src/audio_core/command_generator.cpp | 187 +++++++++++++++++++++++- src/audio_core/command_generator.h | 17 ++- src/audio_core/common.h | 1 + src/audio_core/effect_context.cpp | 271 +++++++++++++++++++++++++++++++++-- src/audio_core/effect_context.h | 215 ++++++++++++++++++++++++++- src/audio_core/info_updater.cpp | 18 +-- src/audio_core/info_updater.h | 2 +- src/audio_core/mix_context.cpp | 35 ++++- src/audio_core/mix_context.h | 11 +- 10 files changed, 731 insertions(+), 35 deletions(-) (limited to 'src/audio_core/command_generator.cpp') diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp index a3ff819e11..56dc892b14 100644 --- a/src/audio_core/audio_renderer.cpp +++ b/src/audio_core/audio_renderer.cpp @@ -27,12 +27,13 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory voice_context(params.voice_count), effect_context(params.effect_count), mix_context(), sink_context(params.sink_count), splitter_context(), voices(params.voice_count), memory{memory_}, - command_generator(worker_params, voice_context, mix_context, splitter_context, memory), + command_generator(worker_params, voice_context, mix_context, splitter_context, effect_context, + memory), temp_mix_buffer(AudioCommon::TOTAL_TEMP_MIX_SIZE) { behavior_info.SetUserRevision(params.revision); splitter_context.Initialize(behavior_info, params.splitter_count, params.num_splitter_send_channels); - mix_context.Initialize(behavior_info, params.submix_count + 1); + mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count); audio_out = std::make_unique(); stream = audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, @@ -106,8 +107,8 @@ ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector& input_param } } - auto mix_result = - info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, splitter_context); + auto mix_result = info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, + splitter_context, effect_context); if (mix_result.IsError()) { LOG_ERROR(Audio, "Failed to update mix parameters"); diff --git a/src/audio_core/command_generator.cpp b/src/audio_core/command_generator.cpp index 73608c9ed7..84782cde6e 100644 --- a/src/audio_core/command_generator.cpp +++ b/src/audio_core/command_generator.cpp @@ -4,6 +4,7 @@ #include "audio_core/algorithm/interpolate.h" #include "audio_core/command_generator.h" +#include "audio_core/effect_context.h" #include "audio_core/mix_context.h" #include "audio_core/voice_context.h" #include "core/memory.h" @@ -68,9 +69,10 @@ s32 ApplyMixDepop(s32* output, s32 first_sample, s32 delta, s32 sample_count) { CommandGenerator::CommandGenerator(AudioCommon::AudioRendererParameter& worker_params, VoiceContext& voice_context, MixContext& mix_context, - SplitterContext& splitter_context, Core::Memory::Memory& memory) + SplitterContext& splitter_context, EffectContext& effect_context, + Core::Memory::Memory& memory) : worker_params(worker_params), voice_context(voice_context), mix_context(mix_context), - splitter_context(splitter_context), memory(memory), + splitter_context(splitter_context), effect_context(effect_context), memory(memory), mix_buffer((worker_params.mix_buffer_count + AudioCommon::MAX_CHANNEL_COUNT) * worker_params.sample_count), sample_buffer(MIX_BUFFER_SIZE), @@ -338,6 +340,120 @@ void CommandGenerator::GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_ } } +void CommandGenerator::GenerateEffectCommand(ServerMixInfo& mix_info) { + const std::size_t effect_count = effect_context.GetCount(); + const auto buffer_offset = mix_info.GetInParams().buffer_offset; + for (std::size_t i = 0; i < effect_count; i++) { + const auto index = mix_info.GetEffectOrder(i); + if (index == AudioCommon::NO_EFFECT_ORDER) { + break; + } + auto* info = effect_context.GetInfo(index); + const auto type = info->GetType(); + + // TODO(ogniK): Finish remaining effects + switch (type) { + case EffectType::Aux: + GenerateAuxCommand(buffer_offset, info, info->IsEnabled()); + break; + case EffectType::I3dl2Reverb: + GenerateI3dl2ReverbEffectCommand(buffer_offset, info, info->IsEnabled()); + break; + case EffectType::BiquadFilter: + GenerateBiquadFilterEffectCommand(buffer_offset, info, info->IsEnabled()); + break; + default: + break; + } + + info->UpdateForCommandGeneration(); + } +} + +void CommandGenerator::GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, + bool enabled) { + if (!enabled) { + return; + } + const auto& params = dynamic_cast(info)->GetParams(); + const auto channel_count = params.channel_count; + for (s32 i = 0; i < channel_count; i++) { + // TODO(ogniK): Actually implement reverb + 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]); + ApplyMix<1>(output, input, 32768, worker_params.sample_count); + } + } +} + +void CommandGenerator::GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, + bool enabled) { + if (!enabled) { + return; + } + const auto& params = dynamic_cast(info)->GetParams(); + const auto channel_count = params.channel_count; + 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]); + ApplyMix<1>(output, input, 32768, worker_params.sample_count); + } + } +} + +void CommandGenerator::GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled) { + auto aux = dynamic_cast(info); + const auto& params = aux->GetParams(); + if (aux->GetSendBuffer() != 0 && aux->GetRecvBuffer() != 0) { + const auto max_channels = params.count; + u32 offset{}; + for (u32 channel = 0; channel < max_channels; channel++) { + u32 write_count = 0; + if (channel == (max_channels - 1)) { + write_count = offset + worker_params.sample_count; + } + + const auto input_index = params.input_mix_buffers[channel] + mix_buffer_offset; + const auto output_index = params.output_mix_buffers[channel] + mix_buffer_offset; + + if (enabled) { + AuxInfoDSP send_info{}; + AuxInfoDSP recv_info{}; + memory.ReadBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); + memory.ReadBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); + + WriteAuxBuffer(send_info, aux->GetSendBuffer(), params.sample_count, + GetMixBuffer(input_index), worker_params.sample_count, offset, + write_count); + memory.WriteBlock(aux->GetSendInfo(), &send_info, sizeof(AuxInfoDSP)); + + const auto samples_read = ReadAuxBuffer( + recv_info, aux->GetRecvBuffer(), params.sample_count, + GetMixBuffer(output_index), worker_params.sample_count, offset, write_count); + memory.WriteBlock(aux->GetRecvInfo(), &recv_info, sizeof(AuxInfoDSP)); + + if (samples_read != worker_params.sample_count && + samples_read <= params.sample_count) { + std::memset(GetMixBuffer(output_index), 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), + worker_params.sample_count * sizeof(s32)); + } + } + + offset += worker_params.sample_count; + } + } +} + ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter_id, s32 index) { if (splitter_id == AudioCommon::NO_SPLITTER) { return nullptr; @@ -345,6 +461,66 @@ ServerSplitterDestinationData* CommandGenerator::GetDestinationData(s32 splitter return splitter_context.GetDestinationData(splitter_id, index); } +s32 CommandGenerator::WriteAuxBuffer(AuxInfoDSP& dsp_info, VAddr send_buffer, u32 max_samples, + const s32* data, u32 sample_count, u32 write_offset, + u32 write_count) { + if (max_samples == 0) { + return 0; + } + u32 offset = dsp_info.write_offset + write_offset; + if (send_buffer == 0 || offset > max_samples) { + return 0; + } + + std::size_t 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)); + offset = (offset + samples_to_grab) % max_samples; + remaining -= samples_to_grab; + data_offset += samples_to_grab; + } + + if (write_count != 0) { + dsp_info.write_offset = (dsp_info.write_offset + write_count) % max_samples; + } + return sample_count; +} + +s32 CommandGenerator::ReadAuxBuffer(AuxInfoDSP& recv_info, VAddr recv_buffer, u32 max_samples, + s32* out_data, u32 sample_count, u32 read_offset, + u32 read_count) { + if (max_samples == 0) { + return 0; + } + + u32 offset = recv_info.read_offset + read_offset; + if (recv_buffer == 0 || offset > max_samples) { + return 0; + } + + u32 remaining = sample_count; + 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; + offset = (offset + samples_to_grab) % max_samples; + remaining -= samples_to_grab; + } + + if (read_count != 0) { + recv_info.read_offset = (recv_info.read_offset + read_count) % max_samples; + } + return sample_count; +} + void CommandGenerator::GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel, s32 node_id) { const auto last = static_cast(last_volume * 32768.0f); @@ -398,7 +574,9 @@ void CommandGenerator::GenerateSubMixCommand(ServerMixInfo& mix_info) { auto& in_params = mix_info.GetInParams(); GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, in_params.sample_rate); - // TODO(ogniK): Effects + + GenerateEffectCommand(mix_info); + GenerateMixCommands(mix_info); } @@ -476,7 +654,8 @@ void CommandGenerator::GenerateFinalMixCommand() { GenerateDepopForMixBuffersCommand(in_params.buffer_count, in_params.buffer_offset, in_params.sample_rate); - // TODO(ogniK): Effects + + GenerateEffectCommand(mix_info); for (s32 i = 0; i < in_params.buffer_count; i++) { const s32 gain = static_cast(in_params.volume * 32768.0f); diff --git a/src/audio_core/command_generator.h b/src/audio_core/command_generator.h index 656ad81438..967d24078e 100644 --- a/src/audio_core/command_generator.h +++ b/src/audio_core/command_generator.h @@ -19,14 +19,17 @@ class MixContext; class SplitterContext; class ServerSplitterDestinationData; class ServerMixInfo; - +class EffectContext; +class EffectBase; +struct AuxInfoDSP; using MixVolumeBuffer = std::array; class CommandGenerator { public: explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params, VoiceContext& voice_context, MixContext& mix_context, - SplitterContext& splitter_context, Core::Memory::Memory& memory); + SplitterContext& splitter_context, EffectContext& effect_context, + Core::Memory::Memory& memory); ~CommandGenerator(); void ClearMixBuffers(); @@ -67,8 +70,17 @@ private: std::size_t mix_buffer_offset); void GenerateDepopForMixBuffersCommand(std::size_t mix_buffer_count, std::size_t mix_buffer_offset, s32 sample_rate); + void GenerateEffectCommand(ServerMixInfo& mix_info); + void GenerateI3dl2ReverbEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); + void GenerateBiquadFilterEffectCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); + void GenerateAuxCommand(s32 mix_buffer_offset, EffectBase* info, bool enabled); 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); + // DSP Code s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count, s32 channel, std::size_t mix_offset); @@ -81,6 +93,7 @@ private: VoiceContext& voice_context; MixContext& mix_context; SplitterContext& splitter_context; + EffectContext& effect_context; Core::Memory::Memory& memory; std::vector mix_buffer{}; std::vector sample_buffer{}; diff --git a/src/audio_core/common.h b/src/audio_core/common.h index 0731d3eb3e..72ebce2218 100644 --- a/src/audio_core/common.h +++ b/src/audio_core/common.h @@ -26,6 +26,7 @@ constexpr s32 NO_SPLITTER = -1; constexpr s32 NO_MIX = 0x7fffffff; constexpr s32 NO_FINAL_MIX = std::numeric_limits::min(); constexpr s32 FINAL_MIX = 0; +constexpr s32 NO_EFFECT_ORDER = -1; constexpr std::size_t TEMP_MIX_BASE_SIZE = 0x3f00; // TODO(ogniK): Work out this constant // Any size checks seem to take the sample history into account // and our const ends up being 0x3f04, the 4 bytes are most diff --git a/src/audio_core/effect_context.cpp b/src/audio_core/effect_context.cpp index 2497d2f323..adfec3df55 100644 --- a/src/audio_core/effect_context.cpp +++ b/src/audio_core/effect_context.cpp @@ -6,6 +6,12 @@ #include "audio_core/effect_context.h" namespace AudioCore { +namespace { +bool ValidChannelCountForEffect(s32 channel_count) { + return channel_count == 1 || channel_count == 2 || channel_count == 4 || channel_count == 6; +} +} // namespace + EffectContext::EffectContext(std::size_t effect_count) : effect_count(effect_count) { effects.reserve(effect_count); std::generate_n(std::back_inserter(effects), effect_count, @@ -21,24 +27,273 @@ EffectBase* EffectContext::GetInfo(std::size_t i) { return effects.at(i).get(); } +EffectBase* EffectContext::RetargetEffect(std::size_t i, EffectType effect) { + switch (effect) { + case EffectType::Invalid: + effects[i] = std::make_unique(); + break; + case EffectType::BufferMixer: + effects[i] = std::make_unique(); + break; + case EffectType::Aux: + effects[i] = std::make_unique(); + break; + case EffectType::Delay: + effects[i] = std::make_unique(); + break; + case EffectType::Reverb: + effects[i] = std::make_unique(); + break; + case EffectType::I3dl2Reverb: + effects[i] = std::make_unique(); + break; + case EffectType::BiquadFilter: + effects[i] = std::make_unique(); + break; + default: + UNREACHABLE_MSG("Unimplemented effect {}", effect); + effects[i] = std::make_unique(); + } + return GetInfo(i); +} + const EffectBase* EffectContext::GetInfo(std::size_t i) const { return effects.at(i).get(); } -EffectStubbed::EffectStubbed() : EffectBase::EffectBase() {} +EffectStubbed::EffectStubbed() : EffectBase::EffectBase(EffectType::Invalid) {} EffectStubbed::~EffectStubbed() = default; -void EffectStubbed::Update(EffectInfo::InParams& in_params) { - if (in_params.is_new) { - usage = UsageStatus::New; - } -} +void EffectStubbed::Update(EffectInfo::InParams& in_params) {} +void EffectStubbed::UpdateForCommandGeneration() {} -EffectBase::EffectBase() = default; +EffectBase::EffectBase(EffectType effect_type) : effect_type(effect_type) {} EffectBase::~EffectBase() = default; -UsageStatus EffectBase::GetUsage() const { +UsageState EffectBase::GetUsage() const { return usage; } +EffectType EffectBase::GetType() const { + return effect_type; +} + +bool EffectBase::IsEnabled() const { + return enabled; +} + +s32 EffectBase::GetMixID() const { + return mix_id; +} + +s32 EffectBase::GetProcessingOrder() const { + return processing_order; +} + +EffectI3dl2Reverb::EffectI3dl2Reverb() : EffectGeneric::EffectGeneric(EffectType::I3dl2Reverb) {} +EffectI3dl2Reverb::~EffectI3dl2Reverb() = default; + +void EffectI3dl2Reverb::Update(EffectInfo::InParams& in_params) { + auto& internal_params = GetParams(); + const auto* reverb_params = reinterpret_cast(in_params.raw.data()); + if (!ValidChannelCountForEffect(reverb_params->max_channels)) { + UNREACHABLE_MSG("Invalid reverb max channel count {}", reverb_params->max_channels); + return; + } + + const auto last_status = internal_params.status; + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + internal_params = *reverb_params; + if (!ValidChannelCountForEffect(reverb_params->channel_count)) { + internal_params.channel_count = internal_params.max_channels; + } + enabled = in_params.is_enabled; + if (last_status != ParameterStatus::Updated) { + internal_params.status = last_status; + } + + if (in_params.is_new || skipped) { + usage = UsageState::Initialized; + internal_params.status = ParameterStatus::Initialized; + skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; + } +} + +void EffectI3dl2Reverb::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } + GetParams().status = ParameterStatus::Updated; +} + +EffectBiquadFilter::EffectBiquadFilter() : EffectGeneric::EffectGeneric(EffectType::BiquadFilter) {} +EffectBiquadFilter::~EffectBiquadFilter() = default; + +void EffectBiquadFilter::Update(EffectInfo::InParams& in_params) { + auto& internal_params = GetParams(); + const auto* biquad_params = reinterpret_cast(in_params.raw.data()); + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + internal_params = *biquad_params; + enabled = in_params.is_enabled; +} + +void EffectBiquadFilter::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } + GetParams().status = ParameterStatus::Updated; +} + +EffectAuxInfo::EffectAuxInfo() : EffectGeneric::EffectGeneric(EffectType::Aux) {} +EffectAuxInfo::~EffectAuxInfo() = default; + +void EffectAuxInfo::Update(EffectInfo::InParams& in_params) { + const auto* aux_params = reinterpret_cast(in_params.raw.data()); + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + GetParams() = *aux_params; + enabled = in_params.is_enabled; + + if (in_params.is_new || skipped) { + skipped = aux_params->send_buffer_info == 0 || aux_params->return_buffer_info == 0; + if (skipped) { + return; + } + + // There's two AuxInfos which are an identical size, the first one is managed by the cpu, + // the second is managed by the dsp. All we care about is managing the DSP one + send_info = aux_params->send_buffer_info + sizeof(AuxInfoDSP); + send_buffer = aux_params->send_buffer_info + (sizeof(AuxInfoDSP) * 2); + + recv_info = aux_params->return_buffer_info + sizeof(AuxInfoDSP); + recv_buffer = aux_params->return_buffer_info + (sizeof(AuxInfoDSP) * 2); + } +} + +void EffectAuxInfo::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } +} + +const VAddr EffectAuxInfo::GetSendInfo() const { + return send_info; +} + +const VAddr EffectAuxInfo::GetSendBuffer() const { + return send_buffer; +} + +const VAddr EffectAuxInfo::GetRecvInfo() const { + return recv_info; +} + +const VAddr EffectAuxInfo::GetRecvBuffer() const { + return recv_buffer; +} + +EffectDelay::EffectDelay() : EffectGeneric::EffectGeneric(EffectType::Delay) {} +EffectDelay::~EffectDelay() = default; + +void EffectDelay::Update(EffectInfo::InParams& in_params) { + const auto* delay_params = reinterpret_cast(in_params.raw.data()); + auto& internal_params = GetParams(); + if (!ValidChannelCountForEffect(delay_params->max_channels)) { + return; + } + + const auto last_status = internal_params.status; + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + internal_params = *delay_params; + if (!ValidChannelCountForEffect(delay_params->channels)) { + internal_params.channels = internal_params.max_channels; + } + enabled = in_params.is_enabled; + + if (last_status != ParameterStatus::Updated) { + internal_params.status = last_status; + } + + if (in_params.is_new || skipped) { + usage = UsageState::Initialized; + internal_params.status = ParameterStatus::Initialized; + skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; + } +} + +void EffectDelay::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } + GetParams().status = ParameterStatus::Updated; +} + +EffectBufferMixer::EffectBufferMixer() : EffectGeneric::EffectGeneric(EffectType::BufferMixer) {} +EffectBufferMixer::~EffectBufferMixer() = default; + +void EffectBufferMixer::Update(EffectInfo::InParams& in_params) { + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + GetParams() = *reinterpret_cast(in_params.raw.data()); + enabled = in_params.is_enabled; +} + +void EffectBufferMixer::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } +} + +EffectReverb::EffectReverb() : EffectGeneric::EffectGeneric(EffectType::Reverb) {} +EffectReverb::~EffectReverb() = default; + +void EffectReverb::Update(EffectInfo::InParams& in_params) { + const auto* reverb_params = reinterpret_cast(in_params.raw.data()); + auto& internal_params = GetParams(); + if (!ValidChannelCountForEffect(reverb_params->max_channels)) { + return; + } + + const auto last_status = internal_params.status; + mix_id = in_params.mix_id; + processing_order = in_params.processing_order; + internal_params = *reverb_params; + if (!ValidChannelCountForEffect(reverb_params->channels)) { + internal_params.channels = internal_params.max_channels; + } + enabled = in_params.is_enabled; + + if (last_status != ParameterStatus::Updated) { + internal_params.status = last_status; + } + + if (in_params.is_new || skipped) { + usage = UsageState::Initialized; + internal_params.status = ParameterStatus::Initialized; + skipped = in_params.buffer_address == 0 || in_params.buffer_size == 0; + } +} + +void EffectReverb::UpdateForCommandGeneration() { + if (enabled) { + usage = UsageState::Running; + } else { + usage = UsageState::Stopped; + } + GetParams().status = ParameterStatus::Updated; +} + } // namespace AudioCore diff --git a/src/audio_core/effect_context.h b/src/audio_core/effect_context.h index e3c3672966..2f2da72dd2 100644 --- a/src/audio_core/effect_context.h +++ b/src/audio_core/effect_context.h @@ -31,6 +31,19 @@ enum class UsageStatus : u8 { Removed = 4, }; +enum class UsageState { + Invalid = 0, + Initialized = 1, + Running = 2, + Stopped = 3, +}; + +enum class ParameterStatus : u8 { + Initialized = 0, + Updating = 1, + Updated = 2, +}; + struct BufferMixerParams { std::array input{}; std::array output{}; @@ -39,6 +52,14 @@ struct BufferMixerParams { }; static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size"); +struct AuxInfoDSP { + u32_le read_offset{}; + u32_le write_offset{}; + u32_le remaining{}; + INSERT_PADDING_WORDS(13); +}; +static_assert(sizeof(AuxInfoDSP) == 0x40, "AuxInfoDSP is an invalid size"); + struct AuxInfo { std::array input_mix_buffers{}; std::array output_mix_buffers{}; @@ -54,6 +75,81 @@ struct AuxInfo { }; static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size"); +struct I3dl2ReverbParams { + std::array input{}; + std::array output{}; + u16_le max_channels{}; + u16_le channel_count{}; + INSERT_PADDING_BYTES(1); + u32_le sample_rate{}; + f32 room_hf{}; + f32 hf_reference{}; + f32 decay_time{}; + f32 hf_decay_ratio{}; + f32 room{}; + f32 reflection{}; + f32 reverb{}; + f32 diffusion{}; + f32 reflection_delay{}; + f32 reverb_delay{}; + f32 density{}; + f32 dry_gain{}; + ParameterStatus status{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(I3dl2ReverbParams) == 0x4c, "I3dl2ReverbParams is an invalid size"); + +struct BiquadFilterParams { + std::array input{}; + std::array output{}; + std::array numerator; + std::array denominator; + s8 channel_count{}; + ParameterStatus status{}; +}; +static_assert(sizeof(BiquadFilterParams) == 0x18, "BiquadFilterParams is an invalid size"); + +struct DelayParams { + std::array input{}; + std::array output{}; + u16_le max_channels{}; + u16_le channels{}; + s32_le max_delay{}; + s32_le delay{}; + s32_le sample_rate{}; + s32_le gain{}; + s32_le feedback_gain{}; + s32_le out_gain{}; + s32_le dry_gain{}; + s32_le channel_spread{}; + s32_le low_pass{}; + ParameterStatus status{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(DelayParams) == 0x38, "DelayParams is an invalid size"); + +struct ReverbParams { + std::array input{}; + std::array output{}; + u16_le max_channels{}; + u16_le channels{}; + s32_le sample_rate{}; + s32_le mode0{}; + s32_le mode0_gain{}; + s32_le pre_delay{}; + s32_le mode1{}; + s32_le mode1_gain{}; + s32_le decay{}; + s32_le hf_decay_ratio{}; + s32_le coloration{}; + s32_le reverb_gain{}; + s32_le out_gain{}; + s32_le dry_gain{}; + ParameterStatus status{}; + INSERT_PADDING_BYTES(3); +}; +static_assert(sizeof(ReverbParams) == 0x44, "ReverbParams is an invalid size"); + class EffectInfo { public: struct InParams { @@ -64,7 +160,7 @@ public: s32_le mix_id{}; u64_le buffer_address{}; u64_le buffer_size{}; - s32_le priority{}; + s32_le processing_order{}; INSERT_PADDING_BYTES(4); union { std::array raw; @@ -79,16 +175,50 @@ public: static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size"); }; +struct AuxAddress { + VAddr send_dsp_info{}; + VAddr send_buffer_base{}; + VAddr return_dsp_info{}; + VAddr return_buffer_base{}; +}; + class EffectBase { public: - EffectBase(); + EffectBase(EffectType effect_type); ~EffectBase(); virtual void Update(EffectInfo::InParams& in_params) = 0; - UsageStatus GetUsage() const; + virtual void UpdateForCommandGeneration() = 0; + UsageState GetUsage() const; + EffectType GetType() const; + bool IsEnabled() const; + s32 GetMixID() const; + s32 GetProcessingOrder() const; protected: - UsageStatus usage{UsageStatus::Invalid}; + UsageState usage{UsageState::Invalid}; + EffectType effect_type{}; + s32 mix_id{}; + s32 processing_order{}; + bool enabled = false; +}; + +template +class EffectGeneric : public EffectBase { +public: + EffectGeneric(EffectType effect_type) : EffectBase::EffectBase(effect_type) {} + ~EffectGeneric() = default; + + T& GetParams() { + return internal_params; + } + + const I3dl2ReverbParams& GetParams() const { + return internal_params; + } + +private: + T internal_params{}; }; class EffectStubbed : public EffectBase { @@ -97,6 +227,82 @@ public: ~EffectStubbed(); void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; +}; + +class EffectI3dl2Reverb : public EffectGeneric { +public: + explicit EffectI3dl2Reverb(); + ~EffectI3dl2Reverb(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + +private: + bool skipped = false; +}; + +class EffectBiquadFilter : public EffectGeneric { +public: + explicit EffectBiquadFilter(); + ~EffectBiquadFilter(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; +}; + +class EffectAuxInfo : public EffectGeneric { +public: + explicit EffectAuxInfo(); + ~EffectAuxInfo(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + const VAddr GetSendInfo() const; + const VAddr GetSendBuffer() const; + const VAddr GetRecvInfo() const; + const VAddr GetRecvBuffer() const; + +private: + VAddr send_info{}; + VAddr send_buffer{}; + VAddr recv_info{}; + VAddr recv_buffer{}; + bool skipped = false; + AuxAddress addresses{}; +}; + +class EffectDelay : public EffectGeneric { +public: + explicit EffectDelay(); + ~EffectDelay(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + +private: + bool skipped = false; +}; + +class EffectBufferMixer : public EffectGeneric { +public: + explicit EffectBufferMixer(); + ~EffectBufferMixer(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; +}; + +class EffectReverb : public EffectGeneric { +public: + explicit EffectReverb(); + ~EffectReverb(); + + void Update(EffectInfo::InParams& in_params) override; + void UpdateForCommandGeneration() override; + +private: + bool skipped = false; }; class EffectContext { @@ -106,6 +312,7 @@ public: std::size_t GetCount() const; EffectBase* GetInfo(std::size_t i); + EffectBase* RetargetEffect(std::size_t i, EffectType effect); const EffectBase* GetInfo(std::size_t i) const; private: diff --git a/src/audio_core/info_updater.cpp b/src/audio_core/info_updater.cpp index 286aa03211..f53ce21a5a 100644 --- a/src/audio_core/info_updater.cpp +++ b/src/audio_core/info_updater.cpp @@ -244,14 +244,15 @@ bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) { // Update effects for (std::size_t i = 0; i < effect_count; i++) { auto* info = effect_context.GetInfo(i); + if (effect_in[i].type != info->GetType()) { + info = effect_context.RetargetEffect(i, effect_in[i].type); + } + info->Update(effect_in[i]); - // TODO(ogniK): Update individual effects - if ((!is_active && info->GetUsage() != UsageStatus::New) || - info->GetUsage() == UsageStatus::Removed) { + if ((!is_active && info->GetUsage() != UsageState::Initialized) || + info->GetUsage() == UsageState::Stopped) { effect_out[i].status = UsageStatus::Removed; - } else if (info->GetUsage() == UsageStatus::New) { - effect_out[i].status = UsageStatus::New; } else { effect_out[i].status = UsageStatus::Used; } @@ -290,7 +291,8 @@ bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) { } ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, - SplitterContext& splitter_context) { + SplitterContext& splitter_context, + EffectContext& effect_context) { std::vector mix_in_params; if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) { @@ -387,13 +389,13 @@ ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buf auto& mix_info_params = mix_info.GetInParams(); if (mix_info_params.in_use != mix_in.in_use) { mix_info_params.in_use = mix_in.in_use; - // TODO(ogniK): Update effect processing order + mix_info.ResetEffectProcessingOrder(); should_sort = true; } if (mix_in.in_use) { should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info, - splitter_context); + splitter_context, effect_context); } } diff --git a/src/audio_core/info_updater.h b/src/audio_core/info_updater.h index 6969de67dd..06f9d770f0 100644 --- a/src/audio_core/info_updater.h +++ b/src/audio_core/info_updater.h @@ -34,7 +34,7 @@ public: bool UpdateEffects(EffectContext& effect_context, bool is_active); bool UpdateSplitterInfo(SplitterContext& splitter_context); ResultCode UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count, - SplitterContext& splitter_context); + SplitterContext& splitter_context, EffectContext& effect_context); bool UpdateSinks(SinkContext& sink_context); bool UpdatePerformanceBuffer(); bool UpdateErrorInfo(BehaviorInfo& in_behavior_info); diff --git a/src/audio_core/mix_context.cpp b/src/audio_core/mix_context.cpp index f6f119a113..0428914907 100644 --- a/src/audio_core/mix_context.cpp +++ b/src/audio_core/mix_context.cpp @@ -4,6 +4,7 @@ #include "audio_core/behavior_info.h" #include "audio_core/common.h" +#include "audio_core/effect_context.h" #include "audio_core/mix_context.h" #include "audio_core/splitter_context.h" @@ -11,7 +12,8 @@ namespace AudioCore { MixContext::MixContext() = default; MixContext::~MixContext() = default; -void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count) { +void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, + std::size_t effect_count) { info_count = mix_count; infos.resize(info_count); auto& final_mix = GetInfo(AudioCommon::FINAL_MIX); @@ -21,6 +23,10 @@ void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_c sorted_info.push_back(&info); } + for (auto& info : infos) { + info.SetEffectCount(effect_count); + } + // Only initialize our edge matrix and node states if splitters are supported if (behavior_info.IsSplitterSupported()) { node_states.Initialize(mix_count); @@ -185,7 +191,8 @@ ServerMixInfo::InParams& ServerMixInfo::GetInParams() { } bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, - BehaviorInfo& behavior_info, SplitterContext& splitter_context) { + BehaviorInfo& behavior_info, SplitterContext& splitter_context, + EffectContext& effect_context) { in_params.volume = mix_in.volume; in_params.sample_rate = mix_in.sample_rate; in_params.buffer_count = mix_in.buffer_count; @@ -206,6 +213,15 @@ bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix in_params.splitter_id = AudioCommon::NO_SPLITTER; } + ResetEffectProcessingOrder(); + const auto effect_count = effect_context.GetCount(); + for (std::size_t i = 0; i < effect_count; i++) { + auto* effect_info = effect_context.GetInfo(i); + if (effect_info->GetMixID() == in_params.mix_id) { + effect_processing_order[effect_info->GetProcessingOrder()] = static_cast(i); + } + } + // TODO(ogniK): Update effect processing order return require_sort; } @@ -228,6 +244,21 @@ void ServerMixInfo::Cleanup() { std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size()); } +void ServerMixInfo::SetEffectCount(std::size_t count) { + effect_processing_order.resize(count); + ResetEffectProcessingOrder(); +} + +void ServerMixInfo::ResetEffectProcessingOrder() { + for (auto& order : effect_processing_order) { + order = AudioCommon::NO_EFFECT_ORDER; + } +} + +s32 ServerMixInfo::GetEffectOrder(std::size_t i) const { + return effect_processing_order.at(i); +} + bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, SplitterContext& splitter_context) { // Mixes are identical diff --git a/src/audio_core/mix_context.h b/src/audio_core/mix_context.h index 3815666999..6a588eeb4e 100644 --- a/src/audio_core/mix_context.h +++ b/src/audio_core/mix_context.h @@ -13,6 +13,7 @@ namespace AudioCore { class BehaviorInfo; +class EffectContext; class MixInfo { public: @@ -65,11 +66,16 @@ public: ServerMixInfo::InParams& GetInParams(); bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, - BehaviorInfo& behavior_info, SplitterContext& splitter_context); + BehaviorInfo& behavior_info, SplitterContext& splitter_context, + EffectContext& effect_context); bool HasAnyConnection() const; void Cleanup(); + void SetEffectCount(std::size_t count); + void ResetEffectProcessingOrder(); + s32 GetEffectOrder(std::size_t i) const; private: + std::vector effect_processing_order; InParams in_params{}; bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in, SplitterContext& splitter_context); @@ -80,7 +86,8 @@ public: MixContext(); ~MixContext(); - void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count); + void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count, + std::size_t effect_count); void SortInfo(); bool TsortInfo(SplitterContext& splitter_context); -- cgit v1.2.3