From 380658c21d39cf05ac765a9284da246388cca2a4 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 000000000..722f9b6c5 --- /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 b924c71822225b6de396b687debb93c1af59e2d6 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/command_generator.cpp | 155 +++++++++++++++++------------------ 1 file changed, 77 insertions(+), 78 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 722f9b6c5..440bfc140 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 -- cgit v1.2.3 From 1b8fe7073b4877b697bd4101a48d77011100579e 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 +++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 22 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 440bfc140..0c3b278ea 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; -- cgit v1.2.3 From 0947f613b1f5480258a1bf197ccc7a85a54ab7e1 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 ++++++++++++++++++++++++++--------- 1 file changed, 94 insertions(+), 29 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 0c3b278ea..8f89292be 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); } -- cgit v1.2.3 From 1b3d86c02fbc82db4dfd7b0ce908d02e48b5a35d 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 +++++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 32 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 8f89292be..157140d68 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; } -- cgit v1.2.3 From 1f1c3bddc0d89d0c0b75a9611805e983a7c10bcb 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 157140d68..73608c9ed 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 80ac1331b545d993aa7c205dc24f8b20a4d6d44e 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/command_generator.cpp | 187 ++++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 4 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 73608c9ed..84782cde6 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); -- cgit v1.2.3