audio_core: Apollo Part 1, AudioRenderer refactor
This commit is contained in:
		@@ -12,16 +12,32 @@ add_library(audio_core STATIC
 | 
			
		||||
    buffer.h
 | 
			
		||||
    codec.cpp
 | 
			
		||||
    codec.h
 | 
			
		||||
    command_generator.cpp
 | 
			
		||||
    command_generator.h
 | 
			
		||||
    common.h
 | 
			
		||||
    effect_context.cpp
 | 
			
		||||
    effect_context.h
 | 
			
		||||
    info_updater.cpp
 | 
			
		||||
    info_updater.h
 | 
			
		||||
    memory_pool.cpp
 | 
			
		||||
    memory_pool.h
 | 
			
		||||
    mix_context.cpp
 | 
			
		||||
    mix_context.h
 | 
			
		||||
    null_sink.h
 | 
			
		||||
    sink.h
 | 
			
		||||
    sink_context.cpp
 | 
			
		||||
    sink_context.h
 | 
			
		||||
    sink_details.cpp
 | 
			
		||||
    sink_details.h
 | 
			
		||||
    sink_stream.h
 | 
			
		||||
    splitter_context.cpp
 | 
			
		||||
    splitter_context.h
 | 
			
		||||
    stream.cpp
 | 
			
		||||
    stream.h
 | 
			
		||||
    time_stretch.cpp
 | 
			
		||||
    time_stretch.h
 | 
			
		||||
    voice_context.cpp
 | 
			
		||||
    voice_context.h
 | 
			
		||||
 | 
			
		||||
    $<$<BOOL:${ENABLE_CUBEB}>:cubeb_sink.cpp cubeb_sink.h>
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -197,4 +197,36 @@ std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16> input,
 | 
			
		||||
    return output;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count) {
 | 
			
		||||
    const std::array<s16, 512>& lut = [pitch] {
 | 
			
		||||
        if (pitch > 0xaaaa) {
 | 
			
		||||
            return curve_lut0;
 | 
			
		||||
        }
 | 
			
		||||
        if (pitch <= 0x8000) {
 | 
			
		||||
            return curve_lut1;
 | 
			
		||||
        }
 | 
			
		||||
        return curve_lut2;
 | 
			
		||||
    }();
 | 
			
		||||
 | 
			
		||||
    std::size_t index{};
 | 
			
		||||
 | 
			
		||||
    for (std::size_t i = 0; i < sample_count; i++) {
 | 
			
		||||
        const std::size_t lut_index{(static_cast<std::size_t>(fraction) >> 8) * 4};
 | 
			
		||||
        const auto l0 = lut[lut_index + 0];
 | 
			
		||||
        const auto l1 = lut[lut_index + 1];
 | 
			
		||||
        const auto l2 = lut[lut_index + 2];
 | 
			
		||||
        const auto l3 = lut[lut_index + 3];
 | 
			
		||||
 | 
			
		||||
        const auto s0 = static_cast<s32>(input[index]);
 | 
			
		||||
        const auto s1 = static_cast<s32>(input[index + 1]);
 | 
			
		||||
        const auto s2 = static_cast<s32>(input[index + 2]);
 | 
			
		||||
        const auto s3 = static_cast<s32>(input[index + 3]);
 | 
			
		||||
 | 
			
		||||
        output[i] = (l0 * s0 + l1 * s1 + l2 * s2 + l3 * s3) >> 15;
 | 
			
		||||
        fraction += pitch;
 | 
			
		||||
        index += (fraction >> 15);
 | 
			
		||||
        fraction &= 0x7fff;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
 
 | 
			
		||||
@@ -38,4 +38,7 @@ inline std::vector<s16> Interpolate(InterpolationState& state, std::vector<s16>
 | 
			
		||||
    return Interpolate(state, std::move(input), ratio);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Nintendo Switchs DSP resampling algorithm. Based on a single channel
 | 
			
		||||
void Resample(s32* output, const s32* input, s32 pitch, s32& fraction, std::size_t sample_count);
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
 
 | 
			
		||||
@@ -2,90 +2,42 @@
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "audio_core/algorithm/interpolate.h"
 | 
			
		||||
#include "audio_core/audio_out.h"
 | 
			
		||||
#include "audio_core/audio_renderer.h"
 | 
			
		||||
#include "audio_core/codec.h"
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "audio_core/info_updater.h"
 | 
			
		||||
#include "audio_core/voice_context.h"
 | 
			
		||||
#include "common/assert.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
#include "core/core.h"
 | 
			
		||||
#include "core/hle/kernel/writable_event.h"
 | 
			
		||||
#include "core/memory.h"
 | 
			
		||||
#include "core/settings.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
constexpr u32 STREAM_SAMPLE_RATE{48000};
 | 
			
		||||
constexpr u32 STREAM_NUM_CHANNELS{2};
 | 
			
		||||
using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>;
 | 
			
		||||
class AudioRenderer::VoiceState {
 | 
			
		||||
public:
 | 
			
		||||
    bool IsPlaying() const {
 | 
			
		||||
        return is_in_use && info.play_state == PlayState::Started;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const VoiceOutStatus& GetOutStatus() const {
 | 
			
		||||
        return out_status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const VoiceInfo& GetInfo() const {
 | 
			
		||||
        return info;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    VoiceInfo& GetInfo() {
 | 
			
		||||
        return info;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void SetWaveIndex(std::size_t index);
 | 
			
		||||
    std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory,
 | 
			
		||||
                                    const VoiceChannelHolder& voice_resources);
 | 
			
		||||
    void UpdateState();
 | 
			
		||||
    void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool is_in_use{};
 | 
			
		||||
    bool is_refresh_pending{};
 | 
			
		||||
    std::size_t wave_index{};
 | 
			
		||||
    std::size_t offset{};
 | 
			
		||||
    Codec::ADPCMState adpcm_state{};
 | 
			
		||||
    InterpolationState interp_state{};
 | 
			
		||||
    std::vector<s16> samples;
 | 
			
		||||
    VoiceOutStatus out_status{};
 | 
			
		||||
    VoiceInfo info{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class AudioRenderer::EffectState {
 | 
			
		||||
public:
 | 
			
		||||
    const EffectOutStatus& GetOutStatus() const {
 | 
			
		||||
        return out_status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const EffectInStatus& GetInfo() const {
 | 
			
		||||
        return info;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    EffectInStatus& GetInfo() {
 | 
			
		||||
        return info;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void UpdateState(Core::Memory::Memory& memory);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    EffectOutStatus out_status{};
 | 
			
		||||
    EffectInStatus info{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
 | 
			
		||||
                             AudioRendererParameter params,
 | 
			
		||||
                             AudioCommon::AudioRendererParameter params,
 | 
			
		||||
                             std::shared_ptr<Kernel::WritableEvent> buffer_event,
 | 
			
		||||
                             std::size_t instance_number)
 | 
			
		||||
    : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
 | 
			
		||||
      voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} {
 | 
			
		||||
    : worker_params{params}, buffer_event{buffer_event},
 | 
			
		||||
      memory_pool_info(params.effect_count + params.voice_count * 4),
 | 
			
		||||
      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),
 | 
			
		||||
      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);
 | 
			
		||||
    audio_out = std::make_unique<AudioCore::AudioOut>();
 | 
			
		||||
    stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS,
 | 
			
		||||
                                   fmt::format("AudioRenderer-Instance{}", instance_number),
 | 
			
		||||
                                   [=]() { buffer_event->Signal(); });
 | 
			
		||||
    stream =
 | 
			
		||||
        audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
 | 
			
		||||
                              fmt::format("AudioRenderer-Instance{}", instance_number),
 | 
			
		||||
                              [=]() { buffer_event->Signal(); });
 | 
			
		||||
    audio_out->StartStream(stream);
 | 
			
		||||
 | 
			
		||||
    QueueMixedBuffer(0);
 | 
			
		||||
@@ -111,355 +63,200 @@ Stream::State AudioRenderer::GetStreamState() const {
 | 
			
		||||
    return stream->GetState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params) {
 | 
			
		||||
    // Copy UpdateDataHeader struct
 | 
			
		||||
    UpdateDataHeader config{};
 | 
			
		||||
    std::memcpy(&config, input_params.data(), sizeof(UpdateDataHeader));
 | 
			
		||||
    u32 memory_pool_count = worker_params.effect_count + (worker_params.voice_count * 4);
 | 
			
		||||
 | 
			
		||||
    if (!behavior_info.UpdateInput(input_params, sizeof(UpdateDataHeader))) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update behavior info input parameters");
 | 
			
		||||
        return Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Copy MemoryPoolInfo structs
 | 
			
		||||
    std::vector<MemoryPoolInfo> mem_pool_info(memory_pool_count);
 | 
			
		||||
    std::memcpy(mem_pool_info.data(),
 | 
			
		||||
                input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
 | 
			
		||||
                memory_pool_count * sizeof(MemoryPoolInfo));
 | 
			
		||||
 | 
			
		||||
    // Copy voice resources
 | 
			
		||||
    const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size +
 | 
			
		||||
                                            config.memory_pools_size};
 | 
			
		||||
    std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset,
 | 
			
		||||
                sizeof(VoiceResourceInformation) * voice_resources.size());
 | 
			
		||||
 | 
			
		||||
    // Copy VoiceInfo structs
 | 
			
		||||
    std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
 | 
			
		||||
                             config.memory_pools_size + config.voice_resource_size};
 | 
			
		||||
    for (auto& voice : voices) {
 | 
			
		||||
        std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
 | 
			
		||||
        voice_offset += sizeof(VoiceInfo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
 | 
			
		||||
                              config.memory_pools_size + config.voice_resource_size +
 | 
			
		||||
                              config.voices_size};
 | 
			
		||||
    for (auto& effect : effects) {
 | 
			
		||||
        std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
 | 
			
		||||
        effect_offset += sizeof(EffectInStatus);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update memory pool state
 | 
			
		||||
    std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
 | 
			
		||||
    for (std::size_t index = 0; index < memory_pool.size(); ++index) {
 | 
			
		||||
        if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
 | 
			
		||||
            memory_pool[index].state = MemoryPoolStates::Attached;
 | 
			
		||||
        } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
 | 
			
		||||
            memory_pool[index].state = MemoryPoolStates::Detached;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update voices
 | 
			
		||||
    for (auto& voice : voices) {
 | 
			
		||||
        voice.UpdateState();
 | 
			
		||||
        if (!voice.GetInfo().is_in_use) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        if (voice.GetInfo().is_new) {
 | 
			
		||||
            voice.SetWaveIndex(voice.GetInfo().wave_buffer_head);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto& effect : effects) {
 | 
			
		||||
        effect.UpdateState(memory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Release previous buffers and queue next ones for playback
 | 
			
		||||
    ReleaseAndQueueBuffers();
 | 
			
		||||
 | 
			
		||||
    // Copy output header
 | 
			
		||||
    UpdateDataHeader response_data{worker_params};
 | 
			
		||||
    if (behavior_info.IsElapsedFrameCountSupported()) {
 | 
			
		||||
        response_data.render_info = sizeof(RendererInfo);
 | 
			
		||||
        response_data.total_size += sizeof(RendererInfo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<u8> output_params(response_data.total_size);
 | 
			
		||||
    std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
 | 
			
		||||
 | 
			
		||||
    // Copy output memory pool entries
 | 
			
		||||
    std::memcpy(output_params.data() + sizeof(UpdateDataHeader), memory_pool.data(),
 | 
			
		||||
                response_data.memory_pools_size);
 | 
			
		||||
 | 
			
		||||
    // Copy output voice status
 | 
			
		||||
    std::size_t voice_out_status_offset{sizeof(UpdateDataHeader) + response_data.memory_pools_size};
 | 
			
		||||
    for (const auto& voice : voices) {
 | 
			
		||||
        std::memcpy(output_params.data() + voice_out_status_offset, &voice.GetOutStatus(),
 | 
			
		||||
                    sizeof(VoiceOutStatus));
 | 
			
		||||
        voice_out_status_offset += sizeof(VoiceOutStatus);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::size_t effect_out_status_offset{
 | 
			
		||||
        sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
 | 
			
		||||
        response_data.voice_resource_size};
 | 
			
		||||
    for (const auto& effect : effects) {
 | 
			
		||||
        std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
 | 
			
		||||
                    sizeof(EffectOutStatus));
 | 
			
		||||
        effect_out_status_offset += sizeof(EffectOutStatus);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update behavior info output
 | 
			
		||||
    const std::size_t behavior_out_status_offset{
 | 
			
		||||
        sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
 | 
			
		||||
        response_data.effects_size + response_data.sinks_size +
 | 
			
		||||
        response_data.performance_manager_size};
 | 
			
		||||
 | 
			
		||||
    if (!behavior_info.UpdateOutput(output_params, behavior_out_status_offset)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update behavior info output parameters");
 | 
			
		||||
        return Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (behavior_info.IsElapsedFrameCountSupported()) {
 | 
			
		||||
        const std::size_t renderer_info_offset{
 | 
			
		||||
            sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
 | 
			
		||||
            response_data.effects_size + response_data.sinks_size +
 | 
			
		||||
            response_data.performance_manager_size + response_data.behavior_size};
 | 
			
		||||
        RendererInfo renderer_info{};
 | 
			
		||||
        renderer_info.elasped_frame_count = elapsed_frame_count;
 | 
			
		||||
        std::memcpy(output_params.data() + renderer_info_offset, &renderer_info,
 | 
			
		||||
                    sizeof(RendererInfo));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return MakeResult(output_params);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
 | 
			
		||||
    wave_index = index & 3;
 | 
			
		||||
    is_refresh_pending = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(
 | 
			
		||||
    std::size_t sample_count, Core::Memory::Memory& memory,
 | 
			
		||||
    const VoiceChannelHolder& voice_resources) {
 | 
			
		||||
    if (!IsPlaying()) {
 | 
			
		||||
        return {};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (is_refresh_pending) {
 | 
			
		||||
        RefreshBuffer(memory, voice_resources);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const std::size_t max_size{samples.size() - offset};
 | 
			
		||||
    const std::size_t dequeue_offset{offset};
 | 
			
		||||
    std::size_t size{sample_count * STREAM_NUM_CHANNELS};
 | 
			
		||||
    if (size > max_size) {
 | 
			
		||||
        size = max_size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    out_status.played_sample_count += size / STREAM_NUM_CHANNELS;
 | 
			
		||||
    offset += size;
 | 
			
		||||
 | 
			
		||||
    const auto& wave_buffer{info.wave_buffer[wave_index]};
 | 
			
		||||
    if (offset == samples.size()) {
 | 
			
		||||
        offset = 0;
 | 
			
		||||
 | 
			
		||||
        if (!wave_buffer.is_looping && wave_buffer.buffer_sz) {
 | 
			
		||||
            SetWaveIndex(wave_index + 1);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (wave_buffer.buffer_sz) {
 | 
			
		||||
            out_status.wave_buffer_consumed++;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (wave_buffer.end_of_stream || wave_buffer.buffer_sz == 0) {
 | 
			
		||||
            info.play_state = PlayState::Paused;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return {samples.begin() + dequeue_offset, samples.begin() + dequeue_offset + size};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioRenderer::VoiceState::UpdateState() {
 | 
			
		||||
    if (is_in_use && !info.is_in_use) {
 | 
			
		||||
        // No longer in use, reset state
 | 
			
		||||
        is_refresh_pending = true;
 | 
			
		||||
        wave_index = 0;
 | 
			
		||||
        offset = 0;
 | 
			
		||||
        out_status = {};
 | 
			
		||||
    }
 | 
			
		||||
    is_in_use = info.is_in_use;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory,
 | 
			
		||||
                                              const VoiceChannelHolder& voice_resources) {
 | 
			
		||||
    const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr;
 | 
			
		||||
    const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz;
 | 
			
		||||
    std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
 | 
			
		||||
    memory.ReadBlock(wave_buffer_address, new_samples.data(), wave_buffer_size);
 | 
			
		||||
 | 
			
		||||
    switch (static_cast<Codec::PcmFormat>(info.sample_format)) {
 | 
			
		||||
    case Codec::PcmFormat::Int16: {
 | 
			
		||||
        // PCM16 is played as-is
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    case Codec::PcmFormat::Adpcm: {
 | 
			
		||||
        // Decode ADPCM to PCM16
 | 
			
		||||
        Codec::ADPCM_Coeff coeffs;
 | 
			
		||||
        memory.ReadBlock(info.additional_params_addr, coeffs.data(), sizeof(Codec::ADPCM_Coeff));
 | 
			
		||||
        new_samples = Codec::DecodeADPCM(reinterpret_cast<u8*>(new_samples.data()),
 | 
			
		||||
                                         new_samples.size() * sizeof(s16), coeffs, adpcm_state);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
        UNIMPLEMENTED_MSG("Unimplemented sample_format={}", info.sample_format);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (info.channel_count) {
 | 
			
		||||
    case 1: {
 | 
			
		||||
        // 1 channel is upsampled to 2 channel
 | 
			
		||||
        samples.resize(new_samples.size() * 2);
 | 
			
		||||
 | 
			
		||||
        for (std::size_t index = 0; index < new_samples.size(); ++index) {
 | 
			
		||||
            auto sample = static_cast<float>(new_samples[index]);
 | 
			
		||||
            if (voice_resources[0]->in_use) {
 | 
			
		||||
                sample *= voice_resources[0]->mix_volumes[0];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            samples[index * 2] = static_cast<s16>(sample * info.volume);
 | 
			
		||||
            samples[index * 2 + 1] = static_cast<s16>(sample * info.volume);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    case 2: {
 | 
			
		||||
        // 2 channel is played as is
 | 
			
		||||
        samples = std::move(new_samples);
 | 
			
		||||
        const std::size_t sample_count = (samples.size() / 2);
 | 
			
		||||
        for (std::size_t index = 0; index < sample_count; ++index) {
 | 
			
		||||
            const std::size_t index_l = index * 2;
 | 
			
		||||
            const std::size_t index_r = index * 2 + 1;
 | 
			
		||||
 | 
			
		||||
            auto sample_l = static_cast<float>(samples[index_l]);
 | 
			
		||||
            auto sample_r = static_cast<float>(samples[index_r]);
 | 
			
		||||
 | 
			
		||||
            if (voice_resources[0]->in_use) {
 | 
			
		||||
                sample_l *= voice_resources[0]->mix_volumes[0];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (voice_resources[1]->in_use) {
 | 
			
		||||
                sample_r *= voice_resources[1]->mix_volumes[1];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            samples[index_l] = static_cast<s16>(sample_l * info.volume);
 | 
			
		||||
            samples[index_r] = static_cast<s16>(sample_r * info.volume);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    case 6: {
 | 
			
		||||
        samples.resize((new_samples.size() / 6) * 2);
 | 
			
		||||
        const std::size_t sample_count = samples.size() / 2;
 | 
			
		||||
 | 
			
		||||
        for (std::size_t index = 0; index < sample_count; ++index) {
 | 
			
		||||
            auto FL = static_cast<float>(new_samples[index * 6]);
 | 
			
		||||
            auto FR = static_cast<float>(new_samples[index * 6 + 1]);
 | 
			
		||||
            auto FC = static_cast<float>(new_samples[index * 6 + 2]);
 | 
			
		||||
            auto BL = static_cast<float>(new_samples[index * 6 + 4]);
 | 
			
		||||
            auto BR = static_cast<float>(new_samples[index * 6 + 5]);
 | 
			
		||||
 | 
			
		||||
            if (voice_resources[0]->in_use) {
 | 
			
		||||
                FL *= voice_resources[0]->mix_volumes[0];
 | 
			
		||||
            }
 | 
			
		||||
            if (voice_resources[1]->in_use) {
 | 
			
		||||
                FR *= voice_resources[1]->mix_volumes[1];
 | 
			
		||||
            }
 | 
			
		||||
            if (voice_resources[2]->in_use) {
 | 
			
		||||
                FC *= voice_resources[2]->mix_volumes[2];
 | 
			
		||||
            }
 | 
			
		||||
            if (voice_resources[4]->in_use) {
 | 
			
		||||
                BL *= voice_resources[4]->mix_volumes[4];
 | 
			
		||||
            }
 | 
			
		||||
            if (voice_resources[5]->in_use) {
 | 
			
		||||
                BR *= voice_resources[5]->mix_volumes[5];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            samples[index * 2] =
 | 
			
		||||
                static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume);
 | 
			
		||||
            samples[index * 2 + 1] =
 | 
			
		||||
                static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume);
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
        UNIMPLEMENTED_MSG("Unimplemented channel_count={}", info.channel_count);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Only interpolate when necessary, expensive.
 | 
			
		||||
    if (GetInfo().sample_rate != STREAM_SAMPLE_RATE) {
 | 
			
		||||
        samples = Interpolate(interp_state, std::move(samples), GetInfo().sample_rate,
 | 
			
		||||
                              STREAM_SAMPLE_RATE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    is_refresh_pending = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioRenderer::EffectState::UpdateState(Core::Memory::Memory& memory) {
 | 
			
		||||
    if (info.is_new) {
 | 
			
		||||
        out_status.state = EffectStatus::New;
 | 
			
		||||
    } else {
 | 
			
		||||
        if (info.type == Effect::Aux) {
 | 
			
		||||
            ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_info) == 0,
 | 
			
		||||
                       "Aux buffers tried to update");
 | 
			
		||||
            ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_info) == 0,
 | 
			
		||||
                       "Aux buffers tried to update");
 | 
			
		||||
            ASSERT_MSG(memory.Read32(info.aux_info.return_buffer_base) == 0,
 | 
			
		||||
                       "Aux buffers tried to update");
 | 
			
		||||
            ASSERT_MSG(memory.Read32(info.aux_info.send_buffer_base) == 0,
 | 
			
		||||
                       "Aux buffers tried to update");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static constexpr s16 ClampToS16(s32 value) {
 | 
			
		||||
    return static_cast<s16>(std::clamp(value, -32768, 32767));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_params,
 | 
			
		||||
                                              std::vector<u8>& output_params) {
 | 
			
		||||
 | 
			
		||||
    InfoUpdater info_updater{input_params, output_params, behavior_info};
 | 
			
		||||
 | 
			
		||||
    if (!info_updater.UpdateBehaviorInfo(behavior_info)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update behavior info input parameters");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!info_updater.UpdateMemoryPools(memory_pool_info)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update memory pool parameters");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!info_updater.UpdateVoiceChannelResources(voice_context)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update voice channel resource parameters");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!info_updater.UpdateVoices(voice_context, memory_pool_info, 0)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update voice parameters");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(ogniK): Deal with stopped audio renderer but updates still taking place
 | 
			
		||||
    if (!info_updater.UpdateEffects(effect_context, true)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update effect parameters");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (behavior_info.IsSplitterSupported()) {
 | 
			
		||||
        if (!info_updater.UpdateSplitterInfo(splitter_context)) {
 | 
			
		||||
            LOG_ERROR(Audio, "Failed to update splitter parameters");
 | 
			
		||||
            return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto mix_result =
 | 
			
		||||
        info_updater.UpdateMixes(mix_context, worker_params.mix_buffer_count, splitter_context);
 | 
			
		||||
 | 
			
		||||
    if (mix_result.IsError()) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update mix parameters");
 | 
			
		||||
        return mix_result;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(ogniK): Sinks
 | 
			
		||||
    if (!info_updater.UpdateSinks(sink_context)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update sink parameters");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(ogniK): Performance buffer
 | 
			
		||||
    if (!info_updater.UpdatePerformanceBuffer()) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update performance buffer parameters");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!info_updater.UpdateErrorInfo(behavior_info)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update error info");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (behavior_info.IsElapsedFrameCountSupported()) {
 | 
			
		||||
        if (!info_updater.UpdateRendererInfo(elapsed_frame_count)) {
 | 
			
		||||
            LOG_ERROR(Audio, "Failed to update renderer info");
 | 
			
		||||
            return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    // TODO(ogniK): Statistics
 | 
			
		||||
 | 
			
		||||
    if (!info_updater.WriteOutputHeader()) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to write output header");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(ogniK): Check when all sections are implemented
 | 
			
		||||
 | 
			
		||||
    if (!info_updater.CheckConsumedSize()) {
 | 
			
		||||
        LOG_ERROR(Audio, "Audio buffers were not consumed!");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ReleaseAndQueueBuffers();
 | 
			
		||||
 | 
			
		||||
    return RESULT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
 | 
			
		||||
    constexpr std::size_t BUFFER_SIZE{512};
 | 
			
		||||
    command_generator.PreCommand();
 | 
			
		||||
    // Clear mix buffers before our next operation
 | 
			
		||||
    command_generator.ClearMixBuffers();
 | 
			
		||||
 | 
			
		||||
    // If the splitter is not in use, sort our mixes
 | 
			
		||||
    if (!splitter_context.UsingSplitter()) {
 | 
			
		||||
        mix_context.SortInfo();
 | 
			
		||||
    }
 | 
			
		||||
    // Sort our voices
 | 
			
		||||
    voice_context.SortInfo();
 | 
			
		||||
 | 
			
		||||
    // Handle samples
 | 
			
		||||
    command_generator.GenerateVoiceCommands();
 | 
			
		||||
    command_generator.GenerateSubMixCommands();
 | 
			
		||||
    command_generator.GenerateFinalMixCommands();
 | 
			
		||||
 | 
			
		||||
    command_generator.PostCommand();
 | 
			
		||||
    // Base sample size
 | 
			
		||||
    std::size_t BUFFER_SIZE{worker_params.sample_count};
 | 
			
		||||
    // Samples
 | 
			
		||||
    std::vector<s16> buffer(BUFFER_SIZE * stream->GetNumChannels());
 | 
			
		||||
    // Make sure to clear our samples
 | 
			
		||||
    std::memset(buffer.data(), 0, buffer.size() * sizeof(s16));
 | 
			
		||||
 | 
			
		||||
    for (auto& voice : voices) {
 | 
			
		||||
        if (!voice.IsPlaying()) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        VoiceChannelHolder resources{};
 | 
			
		||||
        for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) {
 | 
			
		||||
            const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel];
 | 
			
		||||
            resources[channel] = &voice_resources[channel_resource_id];
 | 
			
		||||
    if (sink_context.InUse()) {
 | 
			
		||||
        const auto stream_channel_count = stream->GetNumChannels();
 | 
			
		||||
        const auto buffer_offsets = sink_context.OutputBuffers();
 | 
			
		||||
        const auto channel_count = buffer_offsets.size();
 | 
			
		||||
        const auto& final_mix = mix_context.GetFinalMixInfo();
 | 
			
		||||
        const auto& in_params = final_mix.GetInParams();
 | 
			
		||||
        std::vector<s32*> mix_buffers(channel_count);
 | 
			
		||||
        for (std::size_t i = 0; i < channel_count; i++) {
 | 
			
		||||
            mix_buffers[i] =
 | 
			
		||||
                command_generator.GetMixBuffer(in_params.buffer_offset + buffer_offsets[i]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::size_t offset{};
 | 
			
		||||
        s64 samples_remaining{BUFFER_SIZE};
 | 
			
		||||
        while (samples_remaining > 0) {
 | 
			
		||||
            const std::vector<s16> samples{
 | 
			
		||||
                voice.DequeueSamples(samples_remaining, memory, resources)};
 | 
			
		||||
        for (std::size_t i = 0; i < BUFFER_SIZE; i++) {
 | 
			
		||||
            if (channel_count == 1) {
 | 
			
		||||
                const auto sample = ClampToS16(mix_buffers[0][i]);
 | 
			
		||||
                buffer[i * stream_channel_count + 0] = sample;
 | 
			
		||||
                if (stream_channel_count > 1) {
 | 
			
		||||
                    buffer[i * stream_channel_count + 1] = sample;
 | 
			
		||||
                }
 | 
			
		||||
                if (stream_channel_count == 6) {
 | 
			
		||||
                    buffer[i * stream_channel_count + 2] = sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 4] = sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 5] = sample;
 | 
			
		||||
                }
 | 
			
		||||
            } else if (channel_count == 2) {
 | 
			
		||||
                const auto l_sample = ClampToS16(mix_buffers[0][i]);
 | 
			
		||||
                const auto r_sample = ClampToS16(mix_buffers[1][i]);
 | 
			
		||||
                if (stream_channel_count == 0) {
 | 
			
		||||
                    buffer[i * stream_channel_count + 0] = l_sample;
 | 
			
		||||
                } else if (stream_channel_count == 2) {
 | 
			
		||||
                    buffer[i * stream_channel_count + 0] = l_sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 1] = r_sample;
 | 
			
		||||
                } else if (stream_channel_count == 6) {
 | 
			
		||||
                    buffer[i * stream_channel_count + 0] = l_sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 1] = r_sample;
 | 
			
		||||
 | 
			
		||||
            if (samples.empty()) {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
                    buffer[i * stream_channel_count + 2] =
 | 
			
		||||
                        ClampToS16((static_cast<s32>(l_sample) + static_cast<s32>(r_sample)) / 2);
 | 
			
		||||
 | 
			
		||||
            samples_remaining -= samples.size() / stream->GetNumChannels();
 | 
			
		||||
                    buffer[i * stream_channel_count + 4] = l_sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 5] = r_sample;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            for (const auto& sample : samples) {
 | 
			
		||||
                const s32 buffer_sample{buffer[offset]};
 | 
			
		||||
                buffer[offset++] =
 | 
			
		||||
                    ClampToS16(buffer_sample + static_cast<s32>(sample * voice.GetInfo().volume));
 | 
			
		||||
            } else if (channel_count == 6) {
 | 
			
		||||
                const auto fl_sample = ClampToS16(mix_buffers[0][i]);
 | 
			
		||||
                const auto fr_sample = ClampToS16(mix_buffers[1][i]);
 | 
			
		||||
                const auto fc_sample = ClampToS16(mix_buffers[2][i]);
 | 
			
		||||
                const auto lf_sample = ClampToS16(mix_buffers[3][i]);
 | 
			
		||||
                const auto bl_sample = ClampToS16(mix_buffers[4][i]);
 | 
			
		||||
                const auto br_sample = ClampToS16(mix_buffers[5][i]);
 | 
			
		||||
 | 
			
		||||
                if (stream_channel_count == 1) {
 | 
			
		||||
                    buffer[i * stream_channel_count + 0] = fc_sample;
 | 
			
		||||
                } else if (stream_channel_count == 2) {
 | 
			
		||||
                    buffer[i * stream_channel_count + 0] =
 | 
			
		||||
                        static_cast<s16>(0.3694f * static_cast<float>(fl_sample) +
 | 
			
		||||
                                         0.2612f * static_cast<float>(fc_sample) +
 | 
			
		||||
                                         0.3694f * static_cast<float>(bl_sample));
 | 
			
		||||
                    buffer[i * stream_channel_count + 1] =
 | 
			
		||||
                        static_cast<s16>(0.3694f * static_cast<float>(fr_sample) +
 | 
			
		||||
                                         0.2612f * static_cast<float>(fc_sample) +
 | 
			
		||||
                                         0.3694f * static_cast<float>(br_sample));
 | 
			
		||||
                } else if (stream_channel_count == 6) {
 | 
			
		||||
                    buffer[i * stream_channel_count + 0] = fl_sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 1] = fr_sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 2] = fc_sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 3] = lf_sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 4] = bl_sample;
 | 
			
		||||
                    buffer[i * stream_channel_count + 5] = br_sample;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    audio_out->QueueBuffer(stream, tag, std::move(buffer));
 | 
			
		||||
    elapsed_frame_count++;
 | 
			
		||||
    voice_context.UpdateStateByDspShared();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AudioRenderer::ReleaseAndQueueBuffers() {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,15 @@
 | 
			
		||||
#include <vector>
 | 
			
		||||
 | 
			
		||||
#include "audio_core/behavior_info.h"
 | 
			
		||||
#include "audio_core/command_generator.h"
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "audio_core/effect_context.h"
 | 
			
		||||
#include "audio_core/memory_pool.h"
 | 
			
		||||
#include "audio_core/mix_context.h"
 | 
			
		||||
#include "audio_core/sink_context.h"
 | 
			
		||||
#include "audio_core/splitter_context.h"
 | 
			
		||||
#include "audio_core/stream.h"
 | 
			
		||||
#include "audio_core/voice_context.h"
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
@@ -30,220 +37,25 @@ class Memory;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
using DSPStateHolder = std::array<VoiceState*, 6>;
 | 
			
		||||
 | 
			
		||||
class AudioOut;
 | 
			
		||||
 | 
			
		||||
enum class PlayState : u8 {
 | 
			
		||||
    Started = 0,
 | 
			
		||||
    Stopped = 1,
 | 
			
		||||
    Paused = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class Effect : u8 {
 | 
			
		||||
    None = 0,
 | 
			
		||||
    Aux = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class EffectStatus : u8 {
 | 
			
		||||
    None = 0,
 | 
			
		||||
    New = 1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct AudioRendererParameter {
 | 
			
		||||
    u32_le sample_rate;
 | 
			
		||||
    u32_le sample_count;
 | 
			
		||||
    u32_le mix_buffer_count;
 | 
			
		||||
    u32_le submix_count;
 | 
			
		||||
    u32_le voice_count;
 | 
			
		||||
    u32_le sink_count;
 | 
			
		||||
    u32_le effect_count;
 | 
			
		||||
    u32_le performance_frame_count;
 | 
			
		||||
    u8 is_voice_drop_enabled;
 | 
			
		||||
    u8 unknown_21;
 | 
			
		||||
    u8 unknown_22;
 | 
			
		||||
    u8 execution_mode;
 | 
			
		||||
    u32_le splitter_count;
 | 
			
		||||
    u32_le num_splitter_send_channels;
 | 
			
		||||
    u32_le unknown_30;
 | 
			
		||||
    u32_le revision;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
 | 
			
		||||
 | 
			
		||||
enum class MemoryPoolStates : u32 { // Should be LE
 | 
			
		||||
    Invalid = 0x0,
 | 
			
		||||
    Unknown = 0x1,
 | 
			
		||||
    RequestDetach = 0x2,
 | 
			
		||||
    Detached = 0x3,
 | 
			
		||||
    RequestAttach = 0x4,
 | 
			
		||||
    Attached = 0x5,
 | 
			
		||||
    Released = 0x6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct MemoryPoolEntry {
 | 
			
		||||
    MemoryPoolStates state;
 | 
			
		||||
    u32_le unknown_4;
 | 
			
		||||
    u32_le unknown_8;
 | 
			
		||||
    u32_le unknown_c;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(MemoryPoolEntry) == 0x10, "MemoryPoolEntry has wrong size");
 | 
			
		||||
 | 
			
		||||
struct MemoryPoolInfo {
 | 
			
		||||
    u64_le pool_address;
 | 
			
		||||
    u64_le pool_size;
 | 
			
		||||
    MemoryPoolStates pool_state;
 | 
			
		||||
    INSERT_PADDING_WORDS(3); // Unknown
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(MemoryPoolInfo) == 0x20, "MemoryPoolInfo has wrong size");
 | 
			
		||||
struct BiquadFilter {
 | 
			
		||||
    u8 enable;
 | 
			
		||||
    INSERT_PADDING_BYTES(1);
 | 
			
		||||
    std::array<s16_le, 3> numerator;
 | 
			
		||||
    std::array<s16_le, 2> denominator;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(BiquadFilter) == 0xc, "BiquadFilter has wrong size");
 | 
			
		||||
 | 
			
		||||
struct WaveBuffer {
 | 
			
		||||
    u64_le buffer_addr;
 | 
			
		||||
    u64_le buffer_sz;
 | 
			
		||||
    s32_le start_sample_offset;
 | 
			
		||||
    s32_le end_sample_offset;
 | 
			
		||||
    u8 is_looping;
 | 
			
		||||
    u8 end_of_stream;
 | 
			
		||||
    u8 sent_to_server;
 | 
			
		||||
    INSERT_PADDING_BYTES(5);
 | 
			
		||||
    u64 context_addr;
 | 
			
		||||
    u64 context_sz;
 | 
			
		||||
    INSERT_PADDING_BYTES(8);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
 | 
			
		||||
 | 
			
		||||
struct VoiceResourceInformation {
 | 
			
		||||
    s32_le id{};
 | 
			
		||||
    std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{};
 | 
			
		||||
    bool in_use{};
 | 
			
		||||
    INSERT_PADDING_BYTES(11);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size");
 | 
			
		||||
 | 
			
		||||
struct VoiceInfo {
 | 
			
		||||
    u32_le id;
 | 
			
		||||
    u32_le node_id;
 | 
			
		||||
    u8 is_new;
 | 
			
		||||
    u8 is_in_use;
 | 
			
		||||
    PlayState play_state;
 | 
			
		||||
    u8 sample_format;
 | 
			
		||||
    u32_le sample_rate;
 | 
			
		||||
    u32_le priority;
 | 
			
		||||
    u32_le sorting_order;
 | 
			
		||||
    u32_le channel_count;
 | 
			
		||||
    float_le pitch;
 | 
			
		||||
    float_le volume;
 | 
			
		||||
    std::array<BiquadFilter, 2> biquad_filter;
 | 
			
		||||
    u32_le wave_buffer_count;
 | 
			
		||||
    u32_le wave_buffer_head;
 | 
			
		||||
    INSERT_PADDING_WORDS(1);
 | 
			
		||||
    u64_le additional_params_addr;
 | 
			
		||||
    u64_le additional_params_sz;
 | 
			
		||||
    u32_le mix_id;
 | 
			
		||||
    u32_le splitter_info_id;
 | 
			
		||||
    std::array<WaveBuffer, 4> wave_buffer;
 | 
			
		||||
    std::array<u32_le, 6> voice_channel_resource_ids;
 | 
			
		||||
    INSERT_PADDING_BYTES(24);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(VoiceInfo) == 0x170, "VoiceInfo is wrong size");
 | 
			
		||||
 | 
			
		||||
struct VoiceOutStatus {
 | 
			
		||||
    u64_le played_sample_count;
 | 
			
		||||
    u32_le wave_buffer_consumed;
 | 
			
		||||
    u32_le voice_drops_count;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
 | 
			
		||||
 | 
			
		||||
struct AuxInfo {
 | 
			
		||||
    std::array<u8, 24> input_mix_buffers;
 | 
			
		||||
    std::array<u8, 24> output_mix_buffers;
 | 
			
		||||
    u32_le mix_buffer_count;
 | 
			
		||||
    u32_le sample_rate; // Stored in the aux buffer currently
 | 
			
		||||
    u32_le sample_count;
 | 
			
		||||
    u64_le send_buffer_info;
 | 
			
		||||
    u64_le send_buffer_base;
 | 
			
		||||
 | 
			
		||||
    u64_le return_buffer_info;
 | 
			
		||||
    u64_le return_buffer_base;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct EffectInStatus {
 | 
			
		||||
    Effect type;
 | 
			
		||||
    u8 is_new;
 | 
			
		||||
    u8 is_enabled;
 | 
			
		||||
    INSERT_PADDING_BYTES(1);
 | 
			
		||||
    u32_le mix_id;
 | 
			
		||||
    u64_le buffer_base;
 | 
			
		||||
    u64_le buffer_sz;
 | 
			
		||||
    s32_le priority;
 | 
			
		||||
    INSERT_PADDING_BYTES(4);
 | 
			
		||||
    union {
 | 
			
		||||
        std::array<u8, 0xa0> raw;
 | 
			
		||||
        AuxInfo aux_info;
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct EffectOutStatus {
 | 
			
		||||
    EffectStatus state;
 | 
			
		||||
    INSERT_PADDING_BYTES(0xf);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct RendererInfo {
 | 
			
		||||
    u64_le elasped_frame_count{};
 | 
			
		||||
    INSERT_PADDING_WORDS(2);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct UpdateDataHeader {
 | 
			
		||||
    UpdateDataHeader() {}
 | 
			
		||||
 | 
			
		||||
    explicit UpdateDataHeader(const AudioRendererParameter& config) {
 | 
			
		||||
        revision = Common::MakeMagic('R', 'E', 'V', '8'); // 9.2.0 Revision
 | 
			
		||||
        behavior_size = 0xb0;
 | 
			
		||||
        memory_pools_size = (config.effect_count + (config.voice_count * 4)) * 0x10;
 | 
			
		||||
        voices_size = config.voice_count * 0x10;
 | 
			
		||||
        voice_resource_size = 0x0;
 | 
			
		||||
        effects_size = config.effect_count * 0x10;
 | 
			
		||||
        mixes_size = 0x0;
 | 
			
		||||
        sinks_size = config.sink_count * 0x20;
 | 
			
		||||
        performance_manager_size = 0x10;
 | 
			
		||||
        render_info = 0;
 | 
			
		||||
        total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
 | 
			
		||||
                     effects_size + sinks_size + performance_manager_size;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    u32_le revision{};
 | 
			
		||||
    u32_le behavior_size{};
 | 
			
		||||
    u32_le memory_pools_size{};
 | 
			
		||||
    u32_le voices_size{};
 | 
			
		||||
    u32_le voice_resource_size{};
 | 
			
		||||
    u32_le effects_size{};
 | 
			
		||||
    u32_le mixes_size{};
 | 
			
		||||
    u32_le sinks_size{};
 | 
			
		||||
    u32_le performance_manager_size{};
 | 
			
		||||
    u32_le splitter_size{};
 | 
			
		||||
    u32_le render_info{};
 | 
			
		||||
    INSERT_PADDING_WORDS(4);
 | 
			
		||||
    u32_le total_size{};
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size");
 | 
			
		||||
 | 
			
		||||
class AudioRenderer {
 | 
			
		||||
public:
 | 
			
		||||
    AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
 | 
			
		||||
                  AudioRendererParameter params,
 | 
			
		||||
                  AudioCommon::AudioRendererParameter params,
 | 
			
		||||
                  std::shared_ptr<Kernel::WritableEvent> buffer_event, std::size_t instance_number);
 | 
			
		||||
    ~AudioRenderer();
 | 
			
		||||
 | 
			
		||||
    ResultVal<std::vector<u8>> UpdateAudioRenderer(const std::vector<u8>& input_params);
 | 
			
		||||
    ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
 | 
			
		||||
                                   std::vector<u8>& output_params);
 | 
			
		||||
    void QueueMixedBuffer(Buffer::Tag tag);
 | 
			
		||||
    void ReleaseAndQueueBuffers();
 | 
			
		||||
    u32 GetSampleRate() const;
 | 
			
		||||
@@ -252,19 +64,23 @@ public:
 | 
			
		||||
    Stream::State GetStreamState() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    class EffectState;
 | 
			
		||||
    class VoiceState;
 | 
			
		||||
    BehaviorInfo behavior_info{};
 | 
			
		||||
 | 
			
		||||
    AudioRendererParameter worker_params;
 | 
			
		||||
    AudioCommon::AudioRendererParameter worker_params;
 | 
			
		||||
    std::shared_ptr<Kernel::WritableEvent> buffer_event;
 | 
			
		||||
    std::vector<ServerMemoryPoolInfo> memory_pool_info;
 | 
			
		||||
    VoiceContext voice_context;
 | 
			
		||||
    EffectContext effect_context;
 | 
			
		||||
    MixContext mix_context;
 | 
			
		||||
    SinkContext sink_context;
 | 
			
		||||
    SplitterContext splitter_context;
 | 
			
		||||
    std::vector<VoiceState> voices;
 | 
			
		||||
    std::vector<VoiceResourceInformation> voice_resources;
 | 
			
		||||
    std::vector<EffectState> effects;
 | 
			
		||||
    std::unique_ptr<AudioOut> audio_out;
 | 
			
		||||
    StreamPtr stream;
 | 
			
		||||
    Core::Memory::Memory& memory;
 | 
			
		||||
    CommandGenerator command_generator;
 | 
			
		||||
    std::size_t elapsed_frame_count{};
 | 
			
		||||
    std::vector<s32> temp_mix_buffer{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
 
 | 
			
		||||
@@ -9,39 +9,11 @@
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
BehaviorInfo::BehaviorInfo() : process_revision(CURRENT_PROCESS_REVISION) {}
 | 
			
		||||
BehaviorInfo::BehaviorInfo() : process_revision(AudioCommon::CURRENT_PROCESS_REVISION) {}
 | 
			
		||||
BehaviorInfo::~BehaviorInfo() = default;
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::UpdateInput(const std::vector<u8>& buffer, std::size_t offset) {
 | 
			
		||||
    if (!CanConsumeBuffer(buffer.size(), offset, sizeof(InParams))) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    InParams params{};
 | 
			
		||||
    std::memcpy(¶ms, buffer.data() + offset, sizeof(InParams));
 | 
			
		||||
 | 
			
		||||
    if (!IsValidRevision(params.revision)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", params.revision);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (user_revision != params.revision) {
 | 
			
		||||
        LOG_ERROR(Audio,
 | 
			
		||||
                  "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
 | 
			
		||||
                  user_revision, params.revision);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ClearError();
 | 
			
		||||
    UpdateFlags(params.flags);
 | 
			
		||||
 | 
			
		||||
    // TODO(ogniK): Check input params size when InfoUpdater is used
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::UpdateOutput(std::vector<u8>& buffer, std::size_t offset) {
 | 
			
		||||
    if (!CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(buffer.size(), offset, sizeof(OutParams))) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
@@ -65,36 +37,69 @@ void BehaviorInfo::SetUserRevision(u32_le revision) {
 | 
			
		||||
    user_revision = revision;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32_le BehaviorInfo::GetUserRevision() const {
 | 
			
		||||
    return user_revision;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
u32_le BehaviorInfo::GetProcessRevision() const {
 | 
			
		||||
    return process_revision;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsAdpcmLoopContextBugFixed() const {
 | 
			
		||||
    return IsRevisionSupported(2, user_revision);
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(2, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsSplitterSupported() const {
 | 
			
		||||
    return IsRevisionSupported(2, user_revision);
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(2, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsLongSizePreDelaySupported() const {
 | 
			
		||||
    return IsRevisionSupported(3, user_revision);
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(3, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit80PercentSupported() const {
 | 
			
		||||
    return IsRevisionSupported(5, user_revision);
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(5, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit75PercentSupported() const {
 | 
			
		||||
    return IsRevisionSupported(4, user_revision);
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(4, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsAudioRenererProcessingTimeLimit70PercentSupported() const {
 | 
			
		||||
    return IsRevisionSupported(1, user_revision);
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(1, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsElapsedFrameCountSupported() const {
 | 
			
		||||
    return IsRevisionSupported(5, user_revision);
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(5, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsMemoryPoolForceMappingEnabled() const {
 | 
			
		||||
    return (flags & 1) != 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsFlushVoiceWaveBuffersSupported() const {
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(5, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsVoicePlayedSampleCountResetAtLoopPointSupported() const {
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(5, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsVoicePitchAndSrcSkippedSupported() const {
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(5, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsMixInParameterDirtyOnlyUpdateSupported() const {
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(7, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool BehaviorInfo::IsSplitterBugFixed() const {
 | 
			
		||||
    return AudioCommon::IsRevisionSupported(5, user_revision);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void BehaviorInfo::CopyErrorInfo(BehaviorInfo::OutParams& dst) {
 | 
			
		||||
    dst.error_count = static_cast<u32>(error_count);
 | 
			
		||||
    std::memcpy(dst.errors.data(), errors.data(), sizeof(ErrorInfo) * dst.errors.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
 
 | 
			
		||||
@@ -14,30 +14,6 @@
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
class BehaviorInfo {
 | 
			
		||||
public:
 | 
			
		||||
    explicit BehaviorInfo();
 | 
			
		||||
    ~BehaviorInfo();
 | 
			
		||||
 | 
			
		||||
    bool UpdateInput(const std::vector<u8>& buffer, std::size_t offset);
 | 
			
		||||
    bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
 | 
			
		||||
 | 
			
		||||
    void ClearError();
 | 
			
		||||
    void UpdateFlags(u64_le dest_flags);
 | 
			
		||||
    void SetUserRevision(u32_le revision);
 | 
			
		||||
 | 
			
		||||
    bool IsAdpcmLoopContextBugFixed() const;
 | 
			
		||||
    bool IsSplitterSupported() const;
 | 
			
		||||
    bool IsLongSizePreDelaySupported() const;
 | 
			
		||||
    bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
 | 
			
		||||
    bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
 | 
			
		||||
    bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
 | 
			
		||||
    bool IsElapsedFrameCountSupported() const;
 | 
			
		||||
    bool IsMemoryPoolForceMappingEnabled() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    u32_le process_revision{};
 | 
			
		||||
    u32_le user_revision{};
 | 
			
		||||
    u64_le flags{};
 | 
			
		||||
 | 
			
		||||
    struct ErrorInfo {
 | 
			
		||||
        u32_le result{};
 | 
			
		||||
        INSERT_PADDING_WORDS(1);
 | 
			
		||||
@@ -45,9 +21,6 @@ private:
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(ErrorInfo) == 0x10, "ErrorInfo is an invalid size");
 | 
			
		||||
 | 
			
		||||
    std::array<ErrorInfo, 10> errors{};
 | 
			
		||||
    std::size_t error_count{};
 | 
			
		||||
 | 
			
		||||
    struct InParams {
 | 
			
		||||
        u32_le revision{};
 | 
			
		||||
        u32_le padding{};
 | 
			
		||||
@@ -61,6 +34,39 @@ private:
 | 
			
		||||
        INSERT_PADDING_BYTES(12);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(OutParams) == 0xb0, "OutParams is an invalid size");
 | 
			
		||||
 | 
			
		||||
    explicit BehaviorInfo();
 | 
			
		||||
    ~BehaviorInfo();
 | 
			
		||||
 | 
			
		||||
    bool UpdateOutput(std::vector<u8>& buffer, std::size_t offset);
 | 
			
		||||
 | 
			
		||||
    void ClearError();
 | 
			
		||||
    void UpdateFlags(u64_le dest_flags);
 | 
			
		||||
    void SetUserRevision(u32_le revision);
 | 
			
		||||
    u32_le GetUserRevision() const;
 | 
			
		||||
    u32_le GetProcessRevision() const;
 | 
			
		||||
 | 
			
		||||
    bool IsAdpcmLoopContextBugFixed() const;
 | 
			
		||||
    bool IsSplitterSupported() const;
 | 
			
		||||
    bool IsLongSizePreDelaySupported() const;
 | 
			
		||||
    bool IsAudioRenererProcessingTimeLimit80PercentSupported() const;
 | 
			
		||||
    bool IsAudioRenererProcessingTimeLimit75PercentSupported() const;
 | 
			
		||||
    bool IsAudioRenererProcessingTimeLimit70PercentSupported() const;
 | 
			
		||||
    bool IsElapsedFrameCountSupported() const;
 | 
			
		||||
    bool IsMemoryPoolForceMappingEnabled() const;
 | 
			
		||||
    bool IsFlushVoiceWaveBuffersSupported() const;
 | 
			
		||||
    bool IsVoicePlayedSampleCountResetAtLoopPointSupported() const;
 | 
			
		||||
    bool IsVoicePitchAndSrcSkippedSupported() const;
 | 
			
		||||
    bool IsMixInParameterDirtyOnlyUpdateSupported() const;
 | 
			
		||||
    bool IsSplitterBugFixed() const;
 | 
			
		||||
    void CopyErrorInfo(OutParams& dst);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    u32_le process_revision{};
 | 
			
		||||
    u32_le user_revision{};
 | 
			
		||||
    u64_le flags{};
 | 
			
		||||
    std::array<ErrorInfo, 10> errors{};
 | 
			
		||||
    std::size_t error_count{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										668
									
								
								src/audio_core/command_generator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										668
									
								
								src/audio_core/command_generator.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -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 <std::size_t N>
 | 
			
		||||
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<s32>((static_cast<s64>(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<s32>(static_cast<float>(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<s32>((static_cast<s64>(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<s32>((static_cast<s64>(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<s64, 2>& 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<int32_t>::min();
 | 
			
		||||
    constexpr s64 MAX = std::numeric_limits<int32_t>::max();
 | 
			
		||||
 | 
			
		||||
    for (int i = 0; i < sample_count; ++i) {
 | 
			
		||||
        const auto sample = static_cast<int64_t>(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<s32>(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<s32>(last_volume * 32768.0f);
 | 
			
		||||
    const auto current = static_cast<s32>(current_volume * 32768.0f);
 | 
			
		||||
    const auto delta = static_cast<s32>((static_cast<float>(current) - static_cast<float>(last)) /
 | 
			
		||||
                                        static_cast<float>(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<float>((mix_volumes[i] - last_mix_volumes[i])) /
 | 
			
		||||
                               static_cast<float>(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<s32>(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<s32>(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<s16> 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<s16> 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<u8> 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<s32>(
 | 
			
		||||
        static_cast<float>(in_params.sample_rate) / static_cast<float>(target_sample_rate) *
 | 
			
		||||
        static_cast<float>(static_cast<s32>(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<s32>(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
 | 
			
		||||
							
								
								
									
										83
									
								
								src/audio_core/command_generator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								src/audio_core/command_generator.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "audio_core/voice_context.h"
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
 | 
			
		||||
namespace Core::Memory {
 | 
			
		||||
class Memory;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
class MixContext;
 | 
			
		||||
class SplitterContext;
 | 
			
		||||
class ServerSplitterDestinationData;
 | 
			
		||||
class ServerMixInfo;
 | 
			
		||||
using MixVolumeBuffer = std::array<float, AudioCommon::MAX_MIX_BUFFERS>;
 | 
			
		||||
class CommandGenerator {
 | 
			
		||||
public:
 | 
			
		||||
    explicit CommandGenerator(AudioCommon::AudioRendererParameter& worker_params,
 | 
			
		||||
                              VoiceContext& voice_context, MixContext& mix_context,
 | 
			
		||||
                              SplitterContext& splitter_context, Core::Memory::Memory& memory);
 | 
			
		||||
    ~CommandGenerator();
 | 
			
		||||
 | 
			
		||||
    void ClearMixBuffers();
 | 
			
		||||
    void GenerateVoiceCommands();
 | 
			
		||||
    void GenerateVoiceCommand(ServerVoiceInfo& voice_info);
 | 
			
		||||
    void GenerateSubMixCommands();
 | 
			
		||||
    void GenerateFinalMixCommands();
 | 
			
		||||
    void PreCommand();
 | 
			
		||||
    void PostCommand();
 | 
			
		||||
 | 
			
		||||
    s32* GetChannelMixBuffer(s32 channel);
 | 
			
		||||
    const s32* GetChannelMixBuffer(s32 channel) const;
 | 
			
		||||
    s32* GetMixBuffer(std::size_t index);
 | 
			
		||||
    const s32* GetMixBuffer(std::size_t index) const;
 | 
			
		||||
    std::size_t GetMixChannelBufferOffset(s32 channel) const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void GenerateDataSourceCommand(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 channel);
 | 
			
		||||
    void GenerateBiquadFilterCommandForVoice(ServerVoiceInfo& voice_info, VoiceState& dsp_state,
 | 
			
		||||
                                             s32 mix_buffer_count, s32 channel);
 | 
			
		||||
    void GenerateVolumeRampCommand(float last_volume, float current_volume, s32 channel,
 | 
			
		||||
                                   s32 node_id);
 | 
			
		||||
    void 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);
 | 
			
		||||
    void GenerateSubMixCommand(ServerMixInfo& mix_info);
 | 
			
		||||
    void GenerateMixCommands(ServerMixInfo& mix_info);
 | 
			
		||||
    void GenerateMixCommand(std::size_t output_offset, std::size_t input_offset, float volume,
 | 
			
		||||
                            s32 node_id);
 | 
			
		||||
    void GenerateFinalMixCommand();
 | 
			
		||||
    void GenerateBiquadFilterCommand(s32 mix_buffer, const BiquadFilterParameter& params,
 | 
			
		||||
                                     std::array<s64, 2>& state, std::size_t input_offset,
 | 
			
		||||
                                     std::size_t output_offset, s32 sample_count, s32 node_id);
 | 
			
		||||
    void GenerateDepopPrepareCommand(VoiceState& dsp_state);
 | 
			
		||||
    ServerSplitterDestinationData* GetDestinationData(s32 splitter_id, s32 index);
 | 
			
		||||
 | 
			
		||||
    // DSP Code
 | 
			
		||||
    s32 DecodePcm16(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
 | 
			
		||||
                    s32 channel, std::size_t mix_offset);
 | 
			
		||||
    s32 DecodeAdpcm(ServerVoiceInfo& voice_info, VoiceState& dsp_state, s32 sample_count,
 | 
			
		||||
                    s32 channel, std::size_t mix_offset);
 | 
			
		||||
    void DecodeFromWaveBuffers(ServerVoiceInfo& voice_info, s32* output, VoiceState& dsp_state,
 | 
			
		||||
                               s32 channel, s32 target_sample_rate, s32 sample_count, s32 node_id);
 | 
			
		||||
    void Resample(s32* output, s32* input, s32 pitch, s32& fraction, s32 sample_count);
 | 
			
		||||
 | 
			
		||||
    AudioCommon::AudioRendererParameter& worker_params;
 | 
			
		||||
    VoiceContext& voice_context;
 | 
			
		||||
    MixContext& mix_context;
 | 
			
		||||
    SplitterContext& splitter_context;
 | 
			
		||||
    Core::Memory::Memory& memory;
 | 
			
		||||
    std::vector<s32> mix_buffer{};
 | 
			
		||||
    std::vector<s32> sample_buffer{};
 | 
			
		||||
    bool dumping_frame{false};
 | 
			
		||||
};
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
@@ -8,13 +8,29 @@
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
#include "core/hle/result.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
namespace AudioCommon {
 | 
			
		||||
namespace Audren {
 | 
			
		||||
constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
 | 
			
		||||
}
 | 
			
		||||
constexpr ResultCode ERR_SPLITTER_SORT_FAILED{ErrorModule::Audio, 43};
 | 
			
		||||
} // namespace Audren
 | 
			
		||||
 | 
			
		||||
constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
 | 
			
		||||
constexpr std::size_t MAX_MIX_BUFFERS = 24;
 | 
			
		||||
constexpr std::size_t MAX_BIQUAD_FILTERS = 2;
 | 
			
		||||
constexpr std::size_t MAX_CHANNEL_COUNT = 6;
 | 
			
		||||
constexpr std::size_t MAX_WAVE_BUFFERS = 4;
 | 
			
		||||
constexpr std::size_t MAX_SAMPLE_HISTORY = 4;
 | 
			
		||||
constexpr u32 STREAM_SAMPLE_RATE = 48000;
 | 
			
		||||
constexpr u32 STREAM_NUM_CHANNELS = 6;
 | 
			
		||||
constexpr s32 NO_SPLITTER = -1;
 | 
			
		||||
constexpr s32 NO_MIX = 0x7fffffff;
 | 
			
		||||
constexpr s32 NO_FINAL_MIX = std::numeric_limits<s32>::min();
 | 
			
		||||
constexpr s32 FINAL_MIX = 0;
 | 
			
		||||
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
 | 
			
		||||
// likely the sample history
 | 
			
		||||
constexpr std::size_t TOTAL_TEMP_MIX_SIZE = TEMP_MIX_BASE_SIZE + AudioCommon::MAX_SAMPLE_HISTORY;
 | 
			
		||||
 | 
			
		||||
static constexpr u32 VersionFromRevision(u32_le rev) {
 | 
			
		||||
    // "REV7" -> 7
 | 
			
		||||
@@ -45,4 +61,46 @@ static constexpr bool CanConsumeBuffer(std::size_t size, std::size_t offset, std
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
struct UpdateDataSizes {
 | 
			
		||||
    u32_le behavior{};
 | 
			
		||||
    u32_le memory_pool{};
 | 
			
		||||
    u32_le voice{};
 | 
			
		||||
    u32_le voice_channel_resource{};
 | 
			
		||||
    u32_le effect{};
 | 
			
		||||
    u32_le mixer{};
 | 
			
		||||
    u32_le sink{};
 | 
			
		||||
    u32_le performance{};
 | 
			
		||||
    u32_le splitter{};
 | 
			
		||||
    u32_le render_info{};
 | 
			
		||||
    INSERT_PADDING_WORDS(4);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(UpdateDataSizes) == 0x38, "UpdateDataSizes is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct UpdateDataHeader {
 | 
			
		||||
    u32_le revision{};
 | 
			
		||||
    UpdateDataSizes size{};
 | 
			
		||||
    u32_le total_size{};
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct AudioRendererParameter {
 | 
			
		||||
    u32_le sample_rate;
 | 
			
		||||
    u32_le sample_count;
 | 
			
		||||
    u32_le mix_buffer_count;
 | 
			
		||||
    u32_le submix_count;
 | 
			
		||||
    u32_le voice_count;
 | 
			
		||||
    u32_le sink_count;
 | 
			
		||||
    u32_le effect_count;
 | 
			
		||||
    u32_le performance_frame_count;
 | 
			
		||||
    u8 is_voice_drop_enabled;
 | 
			
		||||
    u8 unknown_21;
 | 
			
		||||
    u8 unknown_22;
 | 
			
		||||
    u8 execution_mode;
 | 
			
		||||
    u32_le splitter_count;
 | 
			
		||||
    u32_le num_splitter_send_channels;
 | 
			
		||||
    u32_le unknown_30;
 | 
			
		||||
    u32_le revision;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCommon
 | 
			
		||||
 
 | 
			
		||||
@@ -23,14 +23,24 @@ class CubebSinkStream final : public SinkStream {
 | 
			
		||||
public:
 | 
			
		||||
    CubebSinkStream(cubeb* ctx, u32 sample_rate, u32 num_channels_, cubeb_devid output_device,
 | 
			
		||||
                    const std::string& name)
 | 
			
		||||
        : ctx{ctx}, num_channels{std::min(num_channels_, 2u)}, time_stretch{sample_rate,
 | 
			
		||||
        : ctx{ctx}, num_channels{std::min(num_channels_, 6u)}, time_stretch{sample_rate,
 | 
			
		||||
                                                                            num_channels} {
 | 
			
		||||
 | 
			
		||||
        cubeb_stream_params params{};
 | 
			
		||||
        params.rate = sample_rate;
 | 
			
		||||
        params.channels = num_channels;
 | 
			
		||||
        params.format = CUBEB_SAMPLE_S16NE;
 | 
			
		||||
        params.layout = num_channels == 1 ? CUBEB_LAYOUT_MONO : CUBEB_LAYOUT_STEREO;
 | 
			
		||||
        switch (num_channels) {
 | 
			
		||||
        case 1:
 | 
			
		||||
            params.layout = CUBEB_LAYOUT_MONO;
 | 
			
		||||
            break;
 | 
			
		||||
        case 2:
 | 
			
		||||
            params.layout = CUBEB_LAYOUT_STEREO;
 | 
			
		||||
            break;
 | 
			
		||||
        case 6:
 | 
			
		||||
            params.layout = CUBEB_LAYOUT_3F2_LFE;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        u32 minimum_latency{};
 | 
			
		||||
        if (cubeb_get_min_latency(ctx, ¶ms, &minimum_latency) != CUBEB_OK) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								src/audio_core/effect_context.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/audio_core/effect_context.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#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<EffectStubbed>());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
EffectContext::~EffectContext() = default;
 | 
			
		||||
 | 
			
		||||
std::size_t EffectContext::GetCount() const {
 | 
			
		||||
    return effect_count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EffectBase* EffectContext::GetInfo(std::size_t i) {
 | 
			
		||||
    return effects.at(i).get();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EffectStubbed::EffectStubbed() : EffectBase::EffectBase() {}
 | 
			
		||||
EffectStubbed::~EffectStubbed() = default;
 | 
			
		||||
 | 
			
		||||
void EffectStubbed::Update(EffectInfo::InParams& in_params) {
 | 
			
		||||
    if (in_params.is_new) {
 | 
			
		||||
        usage = UsageStatus::New;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EffectBase::EffectBase() = default;
 | 
			
		||||
EffectBase::~EffectBase() = default;
 | 
			
		||||
 | 
			
		||||
UsageStatus EffectBase::GetUsage() const {
 | 
			
		||||
    return usage;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										114
									
								
								src/audio_core/effect_context.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/audio_core/effect_context.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
enum class EffectType : u8 {
 | 
			
		||||
    Invalid = 0,
 | 
			
		||||
    BufferMixer = 1,
 | 
			
		||||
    Aux = 2,
 | 
			
		||||
    Delay = 3,
 | 
			
		||||
    Reverb = 4,
 | 
			
		||||
    I3dl2Reverb = 5,
 | 
			
		||||
    BiquadFilter = 6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class UsageStatus : u8 {
 | 
			
		||||
    Invalid = 0,
 | 
			
		||||
    New = 1,
 | 
			
		||||
    Initialized = 2,
 | 
			
		||||
    Used = 3,
 | 
			
		||||
    Removed = 4,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct BufferMixerParams {
 | 
			
		||||
    std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input{};
 | 
			
		||||
    std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output{};
 | 
			
		||||
    std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> volume{};
 | 
			
		||||
    s32_le count{};
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(BufferMixerParams) == 0x94, "BufferMixerParams is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct AuxInfo {
 | 
			
		||||
    std::array<s8, AudioCommon::MAX_MIX_BUFFERS> input_mix_buffers{};
 | 
			
		||||
    std::array<s8, AudioCommon::MAX_MIX_BUFFERS> output_mix_buffers{};
 | 
			
		||||
    u32_le count{};
 | 
			
		||||
    s32_le sample_rate{};
 | 
			
		||||
    s32_le sample_count{};
 | 
			
		||||
    s32_le mix_buffer_count{};
 | 
			
		||||
    u64_le send_buffer_info{};
 | 
			
		||||
    u64_le send_buffer_base{};
 | 
			
		||||
 | 
			
		||||
    u64_le return_buffer_info{};
 | 
			
		||||
    u64_le return_buffer_base{};
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
 | 
			
		||||
 | 
			
		||||
class EffectInfo {
 | 
			
		||||
public:
 | 
			
		||||
    struct InParams {
 | 
			
		||||
        EffectType type{};
 | 
			
		||||
        u8 is_new{};
 | 
			
		||||
        u8 is_enabled{};
 | 
			
		||||
        INSERT_PADDING_BYTES(1);
 | 
			
		||||
        s32_le mix_id{};
 | 
			
		||||
        u64_le buffer_address{};
 | 
			
		||||
        u64_le buffer_size{};
 | 
			
		||||
        s32_le priority{};
 | 
			
		||||
        INSERT_PADDING_BYTES(4);
 | 
			
		||||
        union {
 | 
			
		||||
            std::array<u8, 0xa0> raw;
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(EffectInfo::InParams) == 0xc0, "InParams is an invalid size");
 | 
			
		||||
 | 
			
		||||
    struct OutParams {
 | 
			
		||||
        UsageStatus status{};
 | 
			
		||||
        INSERT_PADDING_BYTES(15);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(EffectInfo::OutParams) == 0x10, "OutParams is an invalid size");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class EffectBase {
 | 
			
		||||
public:
 | 
			
		||||
    EffectBase();
 | 
			
		||||
    ~EffectBase();
 | 
			
		||||
 | 
			
		||||
    virtual void Update(EffectInfo::InParams& in_params) = 0;
 | 
			
		||||
    UsageStatus GetUsage() const;
 | 
			
		||||
 | 
			
		||||
protected:
 | 
			
		||||
    UsageStatus usage{UsageStatus::Invalid};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class EffectStubbed : public EffectBase {
 | 
			
		||||
public:
 | 
			
		||||
    explicit EffectStubbed();
 | 
			
		||||
    ~EffectStubbed();
 | 
			
		||||
 | 
			
		||||
    void Update(EffectInfo::InParams& in_params) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class EffectContext {
 | 
			
		||||
public:
 | 
			
		||||
    explicit EffectContext(std::size_t effect_count);
 | 
			
		||||
    ~EffectContext();
 | 
			
		||||
 | 
			
		||||
    std::size_t GetCount() const;
 | 
			
		||||
    EffectBase* GetInfo(std::size_t i);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::size_t effect_count{};
 | 
			
		||||
    std::vector<std::unique_ptr<EffectBase>> effects;
 | 
			
		||||
};
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										515
									
								
								src/audio_core/info_updater.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										515
									
								
								src/audio_core/info_updater.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,515 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "audio_core/behavior_info.h"
 | 
			
		||||
#include "audio_core/effect_context.h"
 | 
			
		||||
#include "audio_core/info_updater.h"
 | 
			
		||||
#include "audio_core/memory_pool.h"
 | 
			
		||||
#include "audio_core/mix_context.h"
 | 
			
		||||
#include "audio_core/sink_context.h"
 | 
			
		||||
#include "audio_core/splitter_context.h"
 | 
			
		||||
#include "audio_core/voice_context.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
InfoUpdater::InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
 | 
			
		||||
                         BehaviorInfo& behavior_info)
 | 
			
		||||
    : in_params(in_params), out_params(out_params), behavior_info(behavior_info) {
 | 
			
		||||
    ASSERT(
 | 
			
		||||
        AudioCommon::CanConsumeBuffer(in_params.size(), 0, sizeof(AudioCommon::UpdateDataHeader)));
 | 
			
		||||
    std::memcpy(&input_header, in_params.data(), sizeof(AudioCommon::UpdateDataHeader));
 | 
			
		||||
    output_header.total_size = sizeof(AudioCommon::UpdateDataHeader);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
InfoUpdater::~InfoUpdater() = default;
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& in_behavior_info) {
 | 
			
		||||
    if (input_header.size.behavior != sizeof(BehaviorInfo::InParams)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Behavior info is an invalid size, expecting 0x{:X} but got 0x{:X}",
 | 
			
		||||
                  sizeof(BehaviorInfo::InParams), input_header.size.behavior);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
 | 
			
		||||
                                       sizeof(BehaviorInfo::InParams))) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BehaviorInfo::InParams behavior_in{};
 | 
			
		||||
    std::memcpy(&behavior_in, in_params.data() + input_offset, sizeof(BehaviorInfo::InParams));
 | 
			
		||||
    input_offset += sizeof(BehaviorInfo::InParams);
 | 
			
		||||
 | 
			
		||||
    // Make sure it's an audio revision we can actually support
 | 
			
		||||
    if (!AudioCommon::IsValidRevision(behavior_in.revision)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Invalid input revision, revision=0x{:08X}", behavior_in.revision);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Make sure that our behavior info revision matches the input
 | 
			
		||||
    if (in_behavior_info.GetUserRevision() != behavior_in.revision) {
 | 
			
		||||
        LOG_ERROR(Audio,
 | 
			
		||||
                  "User revision differs from input revision, expecting 0x{:08X} but got 0x{:08X}",
 | 
			
		||||
                  in_behavior_info.GetUserRevision(), behavior_in.revision);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update behavior info flags
 | 
			
		||||
    in_behavior_info.ClearError();
 | 
			
		||||
    in_behavior_info.UpdateFlags(behavior_in.flags);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info) {
 | 
			
		||||
    const auto force_mapping = behavior_info.IsMemoryPoolForceMappingEnabled();
 | 
			
		||||
    const auto memory_pool_count = memory_pool_info.size();
 | 
			
		||||
    const auto total_memory_pool_in = sizeof(ServerMemoryPoolInfo::InParams) * memory_pool_count;
 | 
			
		||||
    const auto total_memory_pool_out = sizeof(ServerMemoryPoolInfo::OutParams) * memory_pool_count;
 | 
			
		||||
 | 
			
		||||
    if (input_header.size.memory_pool != total_memory_pool_in) {
 | 
			
		||||
        LOG_ERROR(Audio, "Memory pools are an invalid size, expecting 0x{:X} but got 0x{:X}",
 | 
			
		||||
                  total_memory_pool_in, input_header.size.memory_pool);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_memory_pool_in)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::vector<ServerMemoryPoolInfo::InParams> mempool_in(memory_pool_count);
 | 
			
		||||
    std::vector<ServerMemoryPoolInfo::OutParams> mempool_out(memory_pool_count);
 | 
			
		||||
 | 
			
		||||
    std::memcpy(mempool_in.data(), in_params.data() + input_offset, total_memory_pool_in);
 | 
			
		||||
    input_offset += total_memory_pool_in;
 | 
			
		||||
 | 
			
		||||
    // Update our memory pools
 | 
			
		||||
    for (std::size_t i = 0; i < memory_pool_count; i++) {
 | 
			
		||||
        if (!memory_pool_info[i].Update(mempool_in[i], mempool_out[i])) {
 | 
			
		||||
            LOG_ERROR(Audio, "Failed to update memory pool {}!", i);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset,
 | 
			
		||||
                                       sizeof(BehaviorInfo::InParams))) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::memcpy(out_params.data() + output_offset, mempool_out.data(), total_memory_pool_out);
 | 
			
		||||
    output_offset += total_memory_pool_out;
 | 
			
		||||
    output_header.size.memory_pool = static_cast<u32>(total_memory_pool_out);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdateVoiceChannelResources(VoiceContext& voice_context) {
 | 
			
		||||
    const auto voice_count = voice_context.GetVoiceCount();
 | 
			
		||||
    const auto voice_size = voice_count * sizeof(VoiceChannelResource::InParams);
 | 
			
		||||
    std::vector<VoiceChannelResource::InParams> resources_in(voice_count);
 | 
			
		||||
 | 
			
		||||
    if (input_header.size.voice_channel_resource != voice_size) {
 | 
			
		||||
        LOG_ERROR(Audio, "VoiceChannelResource is an invalid size, expecting 0x{:X} but got 0x{:X}",
 | 
			
		||||
                  voice_size, input_header.size.voice_channel_resource);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_size)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::memcpy(resources_in.data(), in_params.data() + input_offset, voice_size);
 | 
			
		||||
    input_offset += voice_size;
 | 
			
		||||
 | 
			
		||||
    // Update our channel resources
 | 
			
		||||
    for (std::size_t i = 0; i < voice_count; i++) {
 | 
			
		||||
        // Grab our channel resource
 | 
			
		||||
        auto& resource = voice_context.GetChannelResource(i);
 | 
			
		||||
        resource.Update(resources_in[i]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdateVoices(VoiceContext& voice_context,
 | 
			
		||||
                               std::vector<ServerMemoryPoolInfo>& memory_pool_info,
 | 
			
		||||
                               VAddr audio_codec_dsp_addr) {
 | 
			
		||||
    const auto voice_count = voice_context.GetVoiceCount();
 | 
			
		||||
    std::vector<VoiceInfo::InParams> voice_in(voice_count);
 | 
			
		||||
    std::vector<VoiceInfo::OutParams> voice_out(voice_count);
 | 
			
		||||
 | 
			
		||||
    const auto voice_in_size = voice_count * sizeof(VoiceInfo::InParams);
 | 
			
		||||
    const auto voice_out_size = voice_count * sizeof(VoiceInfo::OutParams);
 | 
			
		||||
 | 
			
		||||
    if (input_header.size.voice != voice_in_size) {
 | 
			
		||||
        LOG_ERROR(Audio, "Voices are an invalid size, expecting 0x{:X} but got 0x{:X}",
 | 
			
		||||
                  voice_in_size, input_header.size.voice);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, voice_in_size)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::memcpy(voice_in.data(), in_params.data() + input_offset, voice_in_size);
 | 
			
		||||
    input_offset += voice_in_size;
 | 
			
		||||
 | 
			
		||||
    // Set all voices to not be in use
 | 
			
		||||
    for (std::size_t i = 0; i < voice_count; i++) {
 | 
			
		||||
        voice_context.GetInfo(i).GetInParams().in_use = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update our voices
 | 
			
		||||
    for (std::size_t i = 0; i < voice_count; i++) {
 | 
			
		||||
        auto& in_params = voice_in[i];
 | 
			
		||||
        const auto channel_count = static_cast<std::size_t>(in_params.channel_count);
 | 
			
		||||
        // Skip if it's not currently in use
 | 
			
		||||
        if (!in_params.is_in_use) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
        // Voice states for each channel
 | 
			
		||||
        std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> voice_states{};
 | 
			
		||||
        ASSERT(in_params.id < voice_count);
 | 
			
		||||
 | 
			
		||||
        // Grab our current voice info
 | 
			
		||||
        auto& voice_info = voice_context.GetInfo(static_cast<std::size_t>(in_params.id));
 | 
			
		||||
 | 
			
		||||
        ASSERT(channel_count <= AudioCommon::MAX_CHANNEL_COUNT);
 | 
			
		||||
 | 
			
		||||
        // Get all our channel voice states
 | 
			
		||||
        for (std::size_t channel = 0; channel < channel_count; channel++) {
 | 
			
		||||
            voice_states[channel] =
 | 
			
		||||
                &voice_context.GetState(in_params.voice_channel_resource_ids[channel]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_params.is_new) {
 | 
			
		||||
            // Default our values for our voice
 | 
			
		||||
            voice_info.Initialize();
 | 
			
		||||
            if (channel_count == 0 || channel_count > AudioCommon::MAX_CHANNEL_COUNT) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Zero out our voice states
 | 
			
		||||
            for (std::size_t channel = 0; channel < channel_count; channel++) {
 | 
			
		||||
                std::memset(voice_states[channel], 0, sizeof(VoiceState));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Update our voice
 | 
			
		||||
        voice_info.UpdateParameters(in_params, behavior_info);
 | 
			
		||||
        // TODO(ogniK): Handle mapping errors with behavior info based on in params response
 | 
			
		||||
 | 
			
		||||
        // Update our wave buffers
 | 
			
		||||
        voice_info.UpdateWaveBuffers(in_params, voice_states, behavior_info);
 | 
			
		||||
        voice_info.WriteOutStatus(voice_out[i], in_params, voice_states);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, voice_out_size)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    std::memcpy(out_params.data() + output_offset, voice_out.data(), voice_out_size);
 | 
			
		||||
    output_offset += voice_out_size;
 | 
			
		||||
    output_header.size.voice = static_cast<u32>(voice_out_size);
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdateEffects(EffectContext& effect_context, bool is_active) {
 | 
			
		||||
    const auto effect_count = effect_context.GetCount();
 | 
			
		||||
    std::vector<EffectInfo::InParams> effect_in(effect_count);
 | 
			
		||||
    std::vector<EffectInfo::OutParams> effect_out(effect_count);
 | 
			
		||||
 | 
			
		||||
    const auto total_effect_in = effect_count * sizeof(EffectInfo::InParams);
 | 
			
		||||
    const auto total_effect_out = effect_count * sizeof(EffectInfo::OutParams);
 | 
			
		||||
 | 
			
		||||
    if (input_header.size.effect != total_effect_in) {
 | 
			
		||||
        LOG_ERROR(Audio, "Effects are an invalid size, expecting 0x{:X} but got 0x{:X}",
 | 
			
		||||
                  total_effect_in, input_header.size.effect);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_effect_in)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::memcpy(effect_in.data(), in_params.data() + input_offset, total_effect_in);
 | 
			
		||||
    input_offset += total_effect_in;
 | 
			
		||||
 | 
			
		||||
    // Update effects
 | 
			
		||||
    for (std::size_t i = 0; i < effect_count; i++) {
 | 
			
		||||
        auto* info = effect_context.GetInfo(i);
 | 
			
		||||
        info->Update(effect_in[i]);
 | 
			
		||||
 | 
			
		||||
        // TODO(ogniK): Update individual effects
 | 
			
		||||
        if ((!is_active && info->GetUsage() != UsageStatus::New) ||
 | 
			
		||||
            info->GetUsage() == UsageStatus::Removed) {
 | 
			
		||||
            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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_effect_out)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::memcpy(out_params.data() + output_offset, effect_out.data(), total_effect_out);
 | 
			
		||||
    output_offset += total_effect_out;
 | 
			
		||||
    output_header.size.effect = static_cast<u32>(total_effect_out);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdateSplitterInfo(SplitterContext& splitter_context) {
 | 
			
		||||
    std::size_t start_offset = input_offset;
 | 
			
		||||
    std::size_t bytes_read{};
 | 
			
		||||
    // Update splitter context
 | 
			
		||||
    if (!splitter_context.Update(in_params, input_offset, bytes_read)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Failed to update splitter context!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const auto consumed = input_offset - start_offset;
 | 
			
		||||
 | 
			
		||||
    if (input_header.size.splitter != consumed) {
 | 
			
		||||
        LOG_ERROR(Audio, "Splitters is an invalid size, expecting 0x{:X} but got 0x{:X}",
 | 
			
		||||
                  bytes_read, input_header.size.splitter);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ResultCode InfoUpdater::UpdateMixes(MixContext& mix_context, std::size_t mix_buffer_count,
 | 
			
		||||
                                    SplitterContext& splitter_context) {
 | 
			
		||||
    std::vector<MixInfo::InParams> mix_in_params;
 | 
			
		||||
 | 
			
		||||
    if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
 | 
			
		||||
        // If we're not dirty, get ALL mix in parameters
 | 
			
		||||
        const auto context_mix_count = mix_context.GetCount();
 | 
			
		||||
        const auto total_mix_in = context_mix_count * sizeof(MixInfo::InParams);
 | 
			
		||||
        if (input_header.size.mixer != total_mix_in) {
 | 
			
		||||
            LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
 | 
			
		||||
                      total_mix_in, input_header.size.mixer);
 | 
			
		||||
            return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_mix_in)) {
 | 
			
		||||
            LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
            return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mix_in_params.resize(context_mix_count);
 | 
			
		||||
        std::memcpy(mix_in_params.data(), in_params.data() + input_offset, total_mix_in);
 | 
			
		||||
 | 
			
		||||
        input_offset += total_mix_in;
 | 
			
		||||
    } else {
 | 
			
		||||
        // Only update the "dirty" mixes
 | 
			
		||||
        MixInfo::DirtyHeader dirty_header{};
 | 
			
		||||
        if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset,
 | 
			
		||||
                                           sizeof(MixInfo::DirtyHeader))) {
 | 
			
		||||
            LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
            return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::memcpy(&dirty_header, in_params.data() + input_offset, sizeof(MixInfo::DirtyHeader));
 | 
			
		||||
        input_offset += sizeof(MixInfo::DirtyHeader);
 | 
			
		||||
 | 
			
		||||
        const auto total_mix_in =
 | 
			
		||||
            dirty_header.mixer_count * sizeof(MixInfo::InParams) + sizeof(MixInfo::DirtyHeader);
 | 
			
		||||
 | 
			
		||||
        if (input_header.size.mixer != total_mix_in) {
 | 
			
		||||
            LOG_ERROR(Audio, "Mixer is an invalid size, expecting 0x{:X} but got 0x{:X}",
 | 
			
		||||
                      total_mix_in, input_header.size.mixer);
 | 
			
		||||
            return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (dirty_header.mixer_count != 0) {
 | 
			
		||||
            mix_in_params.resize(dirty_header.mixer_count);
 | 
			
		||||
            std::memcpy(mix_in_params.data(), in_params.data() + input_offset,
 | 
			
		||||
                        mix_in_params.size() * sizeof(MixInfo::InParams));
 | 
			
		||||
            input_offset += mix_in_params.size() * sizeof(MixInfo::InParams);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get our total input count
 | 
			
		||||
    const auto mix_count = mix_in_params.size();
 | 
			
		||||
 | 
			
		||||
    if (!behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
 | 
			
		||||
        // Only verify our buffer count if we're not dirty
 | 
			
		||||
        std::size_t total_buffer_count{};
 | 
			
		||||
        for (std::size_t i = 0; i < mix_count; i++) {
 | 
			
		||||
            const auto& in = mix_in_params[i];
 | 
			
		||||
            total_buffer_count += in.buffer_count;
 | 
			
		||||
            if (in.dest_mix_id > mix_count && in.dest_mix_id != AudioCommon::NO_MIX &&
 | 
			
		||||
                in.mix_id != AudioCommon::FINAL_MIX) {
 | 
			
		||||
                LOG_ERROR(
 | 
			
		||||
                    Audio,
 | 
			
		||||
                    "Invalid mix destination, mix_id={:X}, dest_mix_id={:X}, mix_buffer_count={:X}",
 | 
			
		||||
                    in.mix_id, in.dest_mix_id, mix_buffer_count);
 | 
			
		||||
                return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (total_buffer_count > mix_buffer_count) {
 | 
			
		||||
            LOG_ERROR(Audio,
 | 
			
		||||
                      "Too many mix buffers used! mix_buffer_count={:X}, requesting_buffers={:X}",
 | 
			
		||||
                      mix_buffer_count, total_buffer_count);
 | 
			
		||||
            return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (mix_buffer_count == 0) {
 | 
			
		||||
        LOG_ERROR(Audio, "No mix buffers!");
 | 
			
		||||
        return AudioCommon::Audren::ERR_INVALID_PARAMETERS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool should_sort = false;
 | 
			
		||||
    for (std::size_t i = 0; i < mix_count; i++) {
 | 
			
		||||
        const auto& mix_in = mix_in_params[i];
 | 
			
		||||
        std::size_t target_mix{};
 | 
			
		||||
        if (behavior_info.IsMixInParameterDirtyOnlyUpdateSupported()) {
 | 
			
		||||
            target_mix = mix_in.mix_id;
 | 
			
		||||
        } else {
 | 
			
		||||
            // Non dirty supported games just use i instead of the actual mix_id
 | 
			
		||||
            target_mix = i;
 | 
			
		||||
        }
 | 
			
		||||
        auto& mix_info = mix_context.GetInfo(target_mix);
 | 
			
		||||
        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
 | 
			
		||||
            should_sort = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (mix_in.in_use) {
 | 
			
		||||
            should_sort |= mix_info.Update(mix_context.GetEdgeMatrix(), mix_in, behavior_info,
 | 
			
		||||
                                           splitter_context);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (should_sort && behavior_info.IsSplitterSupported()) {
 | 
			
		||||
        // Sort our splitter data
 | 
			
		||||
        if (!mix_context.TsortInfo(splitter_context)) {
 | 
			
		||||
            return AudioCommon::Audren::ERR_SPLITTER_SORT_FAILED;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(ogniK): Sort when splitter is suppoorted
 | 
			
		||||
 | 
			
		||||
    return RESULT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdateSinks(SinkContext& sink_context) {
 | 
			
		||||
    const auto sink_count = sink_context.GetCount();
 | 
			
		||||
    std::vector<SinkInfo::InParams> sink_in_params(sink_count);
 | 
			
		||||
    const auto total_sink_in = sink_count * sizeof(SinkInfo::InParams);
 | 
			
		||||
 | 
			
		||||
    if (input_header.size.sink != total_sink_in) {
 | 
			
		||||
        LOG_ERROR(Audio, "Sinks are an invalid size, expecting 0x{:X} but got 0x{:X}",
 | 
			
		||||
                  total_sink_in, input_header.size.effect);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(in_params.size(), input_offset, total_sink_in)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::memcpy(sink_in_params.data(), in_params.data() + input_offset, total_sink_in);
 | 
			
		||||
    input_offset += total_sink_in;
 | 
			
		||||
 | 
			
		||||
    // TODO(ogniK): Properly update sinks
 | 
			
		||||
    if (!sink_in_params.empty()) {
 | 
			
		||||
        sink_context.UpdateMainSink(sink_in_params[0]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    output_header.size.sink = static_cast<u32>(0x20 * sink_count);
 | 
			
		||||
    output_offset += 0x20 * sink_count;
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdatePerformanceBuffer() {
 | 
			
		||||
    output_header.size.performance = 0x10;
 | 
			
		||||
    output_offset += 0x10;
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdateErrorInfo(BehaviorInfo& in_behavior_info) {
 | 
			
		||||
    const auto total_beahvior_info_out = sizeof(BehaviorInfo::OutParams);
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_beahvior_info_out)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    BehaviorInfo::OutParams behavior_info_out{};
 | 
			
		||||
    behavior_info.CopyErrorInfo(behavior_info_out);
 | 
			
		||||
 | 
			
		||||
    std::memcpy(out_params.data() + output_offset, &behavior_info_out, total_beahvior_info_out);
 | 
			
		||||
    output_offset += total_beahvior_info_out;
 | 
			
		||||
    output_header.size.behavior = total_beahvior_info_out;
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct RendererInfo {
 | 
			
		||||
    u64_le elasped_frame_count{};
 | 
			
		||||
    INSERT_PADDING_WORDS(2);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::UpdateRendererInfo(std::size_t elapsed_frame_count) {
 | 
			
		||||
    const auto total_renderer_info_out = sizeof(RendererInfo);
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(out_params.size(), output_offset, total_renderer_info_out)) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    RendererInfo out{};
 | 
			
		||||
    out.elasped_frame_count = elapsed_frame_count;
 | 
			
		||||
    std::memcpy(out_params.data() + output_offset, &out, total_renderer_info_out);
 | 
			
		||||
    output_offset += total_renderer_info_out;
 | 
			
		||||
    output_header.size.render_info = total_renderer_info_out;
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::CheckConsumedSize() const {
 | 
			
		||||
    if (output_offset != out_params.size()) {
 | 
			
		||||
        LOG_ERROR(Audio, "Output is not consumed! Consumed {}, but requires {}. {} bytes remaining",
 | 
			
		||||
                  output_offset, out_params.size(), out_params.size() - output_offset);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    /*if (input_offset != in_params.size()) {
 | 
			
		||||
        LOG_ERROR(Audio, "Input is not consumed!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }*/
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool InfoUpdater::WriteOutputHeader() {
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(out_params.size(), 0,
 | 
			
		||||
                                       sizeof(AudioCommon::UpdateDataHeader))) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    output_header.revision = AudioCommon::CURRENT_PROCESS_REVISION;
 | 
			
		||||
    const auto& sz = output_header.size;
 | 
			
		||||
    output_header.total_size += sz.behavior + sz.memory_pool + sz.voice +
 | 
			
		||||
                                sz.voice_channel_resource + sz.effect + sz.mixer + sz.sink +
 | 
			
		||||
                                sz.performance + sz.splitter + sz.render_info;
 | 
			
		||||
 | 
			
		||||
    std::memcpy(out_params.data(), &output_header, sizeof(AudioCommon::UpdateDataHeader));
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										58
									
								
								src/audio_core/info_updater.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/audio_core/info_updater.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
class BehaviorInfo;
 | 
			
		||||
class ServerMemoryPoolInfo;
 | 
			
		||||
class VoiceContext;
 | 
			
		||||
class EffectContext;
 | 
			
		||||
class MixContext;
 | 
			
		||||
class SinkContext;
 | 
			
		||||
class SplitterContext;
 | 
			
		||||
 | 
			
		||||
class InfoUpdater {
 | 
			
		||||
public:
 | 
			
		||||
    // TODO(ogniK): Pass process handle when we support it
 | 
			
		||||
    InfoUpdater(const std::vector<u8>& in_params, std::vector<u8>& out_params,
 | 
			
		||||
                BehaviorInfo& behavior_info);
 | 
			
		||||
    ~InfoUpdater();
 | 
			
		||||
 | 
			
		||||
    bool UpdateBehaviorInfo(BehaviorInfo& in_behavior_info);
 | 
			
		||||
    bool UpdateMemoryPools(std::vector<ServerMemoryPoolInfo>& memory_pool_info);
 | 
			
		||||
    bool UpdateVoiceChannelResources(VoiceContext& voice_context);
 | 
			
		||||
    bool UpdateVoices(VoiceContext& voice_context,
 | 
			
		||||
                      std::vector<ServerMemoryPoolInfo>& memory_pool_info,
 | 
			
		||||
                      VAddr audio_codec_dsp_addr);
 | 
			
		||||
    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);
 | 
			
		||||
    bool UpdateSinks(SinkContext& sink_context);
 | 
			
		||||
    bool UpdatePerformanceBuffer();
 | 
			
		||||
    bool UpdateErrorInfo(BehaviorInfo& in_behavior_info);
 | 
			
		||||
    bool UpdateRendererInfo(std::size_t elapsed_frame_count);
 | 
			
		||||
    bool CheckConsumedSize() const;
 | 
			
		||||
 | 
			
		||||
    bool WriteOutputHeader();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    const std::vector<u8>& in_params;
 | 
			
		||||
    std::vector<u8>& out_params;
 | 
			
		||||
    BehaviorInfo& behavior_info;
 | 
			
		||||
 | 
			
		||||
    AudioCommon::UpdateDataHeader input_header{};
 | 
			
		||||
    AudioCommon::UpdateDataHeader output_header{};
 | 
			
		||||
 | 
			
		||||
    std::size_t input_offset{sizeof(AudioCommon::UpdateDataHeader)};
 | 
			
		||||
    std::size_t output_offset{sizeof(AudioCommon::UpdateDataHeader)};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										62
									
								
								src/audio_core/memory_pool.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/audio_core/memory_pool.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "audio_core/memory_pool.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
ServerMemoryPoolInfo::ServerMemoryPoolInfo() = default;
 | 
			
		||||
ServerMemoryPoolInfo::~ServerMemoryPoolInfo() = default;
 | 
			
		||||
bool ServerMemoryPoolInfo::Update(const ServerMemoryPoolInfo::InParams& in_params,
 | 
			
		||||
                                  ServerMemoryPoolInfo::OutParams& out_params) {
 | 
			
		||||
    // Our state does not need to be changed
 | 
			
		||||
    if (in_params.state != ServerMemoryPoolInfo::State::RequestAttach &&
 | 
			
		||||
        in_params.state != ServerMemoryPoolInfo::State::RequestDetach) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Address or size is null
 | 
			
		||||
    if (in_params.address == 0 || in_params.size == 0) {
 | 
			
		||||
        LOG_ERROR(Audio, "Memory pool address or size is zero! address={:X}, size={:X}",
 | 
			
		||||
                  in_params.address, in_params.size);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Address or size is not aligned
 | 
			
		||||
    if ((in_params.address % 0x1000) != 0 || (in_params.size % 0x1000) != 0) {
 | 
			
		||||
        LOG_ERROR(Audio, "Memory pool address or size is not aligned! address={:X}, size={:X}",
 | 
			
		||||
                  in_params.address, in_params.size);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (in_params.state == ServerMemoryPoolInfo::State::RequestAttach) {
 | 
			
		||||
        cpu_address = in_params.address;
 | 
			
		||||
        size = in_params.size;
 | 
			
		||||
        used = true;
 | 
			
		||||
        out_params.state = ServerMemoryPoolInfo::State::Attached;
 | 
			
		||||
    } else {
 | 
			
		||||
        // Unexpected address
 | 
			
		||||
        if (cpu_address != in_params.address) {
 | 
			
		||||
            LOG_ERROR(Audio, "Memory pool address differs! Expecting {:X} but address is {:X}",
 | 
			
		||||
                      cpu_address, in_params.address);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (size != in_params.size) {
 | 
			
		||||
            LOG_ERROR(Audio, "Memory pool size differs! Expecting {:X} but size is {:X}", size,
 | 
			
		||||
                      in_params.size);
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cpu_address = 0;
 | 
			
		||||
        size = 0;
 | 
			
		||||
        used = false;
 | 
			
		||||
        out_params.state = ServerMemoryPoolInfo::State::Detached;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										53
									
								
								src/audio_core/memory_pool.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/audio_core/memory_pool.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
class ServerMemoryPoolInfo {
 | 
			
		||||
public:
 | 
			
		||||
    ServerMemoryPoolInfo();
 | 
			
		||||
    ~ServerMemoryPoolInfo();
 | 
			
		||||
 | 
			
		||||
    enum class State : u32_le {
 | 
			
		||||
        Invalid = 0x0,
 | 
			
		||||
        Aquired = 0x1,
 | 
			
		||||
        RequestDetach = 0x2,
 | 
			
		||||
        Detached = 0x3,
 | 
			
		||||
        RequestAttach = 0x4,
 | 
			
		||||
        Attached = 0x5,
 | 
			
		||||
        Released = 0x6,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct InParams {
 | 
			
		||||
        u64_le address{};
 | 
			
		||||
        u64_le size{};
 | 
			
		||||
        ServerMemoryPoolInfo::State state{};
 | 
			
		||||
        INSERT_PADDING_WORDS(3);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(ServerMemoryPoolInfo::InParams) == 0x20, "InParams are an invalid size");
 | 
			
		||||
 | 
			
		||||
    struct OutParams {
 | 
			
		||||
        ServerMemoryPoolInfo::State state{};
 | 
			
		||||
        INSERT_PADDING_WORDS(3);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(ServerMemoryPoolInfo::OutParams) == 0x10, "OutParams are an invalid size");
 | 
			
		||||
 | 
			
		||||
    bool Update(const ServerMemoryPoolInfo::InParams& in_params,
 | 
			
		||||
                ServerMemoryPoolInfo::OutParams& out_params);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    // There's another entry here which is the DSP address, however since we're not talking to the
 | 
			
		||||
    // DSP we can just use the same address provided by the guest without needing to remap
 | 
			
		||||
    u64_le cpu_address{};
 | 
			
		||||
    u64_le size{};
 | 
			
		||||
    bool used{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										264
									
								
								src/audio_core/mix_context.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								src/audio_core/mix_context.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,264 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "audio_core/behavior_info.h"
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "audio_core/mix_context.h"
 | 
			
		||||
#include "audio_core/splitter_context.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
MixContext::MixContext() = default;
 | 
			
		||||
MixContext::~MixContext() = default;
 | 
			
		||||
 | 
			
		||||
void MixContext::Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count) {
 | 
			
		||||
    info_count = mix_count;
 | 
			
		||||
    infos.resize(info_count);
 | 
			
		||||
    auto& final_mix = GetInfo(AudioCommon::FINAL_MIX);
 | 
			
		||||
    final_mix.GetInParams().mix_id = AudioCommon::FINAL_MIX;
 | 
			
		||||
    for (auto& info : infos) {
 | 
			
		||||
        sorted_info.push_back(&info);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Only initialize our edge matrix and node states if splitters are supported
 | 
			
		||||
    if (behavior_info.IsSplitterSupported()) {
 | 
			
		||||
        node_states.Initialize(mix_count);
 | 
			
		||||
        edge_matrix.Initialize(mix_count);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MixContext::UpdateDistancesFromFinalMix() {
 | 
			
		||||
    // Set all distances to be invalid
 | 
			
		||||
    for (std::size_t i = 0; i < info_count; i++) {
 | 
			
		||||
        GetInfo(i).GetInParams().final_mix_distance = AudioCommon::NO_FINAL_MIX;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (std::size_t i = 0; i < info_count; i++) {
 | 
			
		||||
        auto& info = GetInfo(i);
 | 
			
		||||
        auto& in_params = info.GetInParams();
 | 
			
		||||
        // Populate our sorted info
 | 
			
		||||
        sorted_info[i] = &info;
 | 
			
		||||
 | 
			
		||||
        if (!in_params.in_use) {
 | 
			
		||||
            continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auto mix_id = in_params.mix_id;
 | 
			
		||||
        // Needs to be referenced out of scope
 | 
			
		||||
        s32 distance_to_final_mix{AudioCommon::FINAL_MIX};
 | 
			
		||||
        for (; distance_to_final_mix < info_count; distance_to_final_mix++) {
 | 
			
		||||
            if (mix_id == AudioCommon::FINAL_MIX) {
 | 
			
		||||
                // If we're at the final mix, we're done
 | 
			
		||||
                break;
 | 
			
		||||
            } else if (mix_id == AudioCommon::NO_MIX) {
 | 
			
		||||
                // If we have no more mix ids, we're done
 | 
			
		||||
                distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
 | 
			
		||||
                break;
 | 
			
		||||
            } else {
 | 
			
		||||
                const auto& dest_mix = GetInfo(mix_id);
 | 
			
		||||
                const auto dest_mix_distance = dest_mix.GetInParams().final_mix_distance;
 | 
			
		||||
 | 
			
		||||
                if (dest_mix_distance == AudioCommon::NO_FINAL_MIX) {
 | 
			
		||||
                    // If our current mix isn't pointing to a final mix, follow through
 | 
			
		||||
                    mix_id = dest_mix.GetInParams().dest_mix_id;
 | 
			
		||||
                } else {
 | 
			
		||||
                    // Our current mix + 1 = final distance
 | 
			
		||||
                    distance_to_final_mix = dest_mix_distance + 1;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // If we're out of range for our distance, mark it as no final mix
 | 
			
		||||
        if (distance_to_final_mix >= info_count) {
 | 
			
		||||
            distance_to_final_mix = AudioCommon::NO_FINAL_MIX;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        in_params.final_mix_distance = distance_to_final_mix;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MixContext::CalcMixBufferOffset() {
 | 
			
		||||
    s32 offset{};
 | 
			
		||||
    for (std::size_t i = 0; i < info_count; i++) {
 | 
			
		||||
        auto& info = GetSortedInfo(i);
 | 
			
		||||
        auto& in_params = info.GetInParams();
 | 
			
		||||
        if (in_params.in_use) {
 | 
			
		||||
            // Only update if in use
 | 
			
		||||
            in_params.buffer_offset = offset;
 | 
			
		||||
            offset += in_params.buffer_count;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MixContext::SortInfo() {
 | 
			
		||||
    // Get the distance to the final mix
 | 
			
		||||
    UpdateDistancesFromFinalMix();
 | 
			
		||||
 | 
			
		||||
    // Sort based on the distance to the final mix
 | 
			
		||||
    std::sort(sorted_info.begin(), sorted_info.end(),
 | 
			
		||||
              [](const ServerMixInfo* lhs, const ServerMixInfo* rhs) {
 | 
			
		||||
                  return lhs->GetInParams().final_mix_distance >
 | 
			
		||||
                         rhs->GetInParams().final_mix_distance;
 | 
			
		||||
              });
 | 
			
		||||
 | 
			
		||||
    // Calculate the mix buffer offset
 | 
			
		||||
    CalcMixBufferOffset();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool MixContext::TsortInfo(SplitterContext& splitter_context) {
 | 
			
		||||
    // If we're not using mixes, just calculate the mix buffer offset
 | 
			
		||||
    if (!splitter_context.UsingSplitter()) {
 | 
			
		||||
        CalcMixBufferOffset();
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
    // Sort our node states
 | 
			
		||||
    if (!node_states.Tsort(edge_matrix)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Get our sorted list
 | 
			
		||||
    const auto sorted_list = node_states.GetIndexList();
 | 
			
		||||
    std::size_t info_id{};
 | 
			
		||||
    for (auto itr = sorted_list.rbegin(); itr != sorted_list.rend(); ++itr) {
 | 
			
		||||
        // Set our sorted info
 | 
			
		||||
        sorted_info[info_id++] = &GetInfo(*itr);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Calculate the mix buffer offset
 | 
			
		||||
    CalcMixBufferOffset();
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t MixContext::GetCount() const {
 | 
			
		||||
    return info_count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerMixInfo& MixContext::GetInfo(std::size_t i) {
 | 
			
		||||
    ASSERT(i < info_count);
 | 
			
		||||
    return infos.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerMixInfo& MixContext::GetInfo(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < info_count);
 | 
			
		||||
    return infos.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) {
 | 
			
		||||
    ASSERT(i < info_count);
 | 
			
		||||
    return *sorted_info.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerMixInfo& MixContext::GetSortedInfo(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < info_count);
 | 
			
		||||
    return *sorted_info.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerMixInfo& MixContext::GetFinalMixInfo() {
 | 
			
		||||
    return infos.at(AudioCommon::FINAL_MIX);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerMixInfo& MixContext::GetFinalMixInfo() const {
 | 
			
		||||
    return infos.at(AudioCommon::FINAL_MIX);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EdgeMatrix& MixContext::GetEdgeMatrix() {
 | 
			
		||||
    return edge_matrix;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const EdgeMatrix& MixContext::GetEdgeMatrix() const {
 | 
			
		||||
    return edge_matrix;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerMixInfo::ServerMixInfo() {
 | 
			
		||||
    Cleanup();
 | 
			
		||||
}
 | 
			
		||||
ServerMixInfo::~ServerMixInfo() = default;
 | 
			
		||||
 | 
			
		||||
const ServerMixInfo::InParams& ServerMixInfo::GetInParams() const {
 | 
			
		||||
    return in_params;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerMixInfo::InParams& ServerMixInfo::GetInParams() {
 | 
			
		||||
    return in_params;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerMixInfo::Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
 | 
			
		||||
                           BehaviorInfo& behavior_info, SplitterContext& splitter_context) {
 | 
			
		||||
    in_params.volume = mix_in.volume;
 | 
			
		||||
    in_params.sample_rate = mix_in.sample_rate;
 | 
			
		||||
    in_params.buffer_count = mix_in.buffer_count;
 | 
			
		||||
    in_params.in_use = mix_in.in_use;
 | 
			
		||||
    in_params.mix_id = mix_in.mix_id;
 | 
			
		||||
    in_params.node_id = mix_in.node_id;
 | 
			
		||||
    for (std::size_t i = 0; i < mix_in.mix_volume.size(); i++) {
 | 
			
		||||
        std::copy(mix_in.mix_volume[i].begin(), mix_in.mix_volume[i].end(),
 | 
			
		||||
                  in_params.mix_volume[i].begin());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool require_sort = false;
 | 
			
		||||
 | 
			
		||||
    if (behavior_info.IsSplitterSupported()) {
 | 
			
		||||
        require_sort = UpdateConnection(edge_matrix, mix_in, splitter_context);
 | 
			
		||||
    } else {
 | 
			
		||||
        in_params.dest_mix_id = mix_in.dest_mix_id;
 | 
			
		||||
        in_params.splitter_id = AudioCommon::NO_SPLITTER;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(ogniK): Update effect processing order
 | 
			
		||||
    return require_sort;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerMixInfo::HasAnyConnection() const {
 | 
			
		||||
    return in_params.splitter_id != AudioCommon::NO_SPLITTER ||
 | 
			
		||||
           in_params.mix_id != AudioCommon::NO_MIX;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerMixInfo::Cleanup() {
 | 
			
		||||
    in_params.volume = 0.0f;
 | 
			
		||||
    in_params.sample_rate = 0;
 | 
			
		||||
    in_params.buffer_count = 0;
 | 
			
		||||
    in_params.in_use = false;
 | 
			
		||||
    in_params.mix_id = AudioCommon::NO_MIX;
 | 
			
		||||
    in_params.node_id = 0;
 | 
			
		||||
    in_params.buffer_offset = 0;
 | 
			
		||||
    in_params.dest_mix_id = AudioCommon::NO_MIX;
 | 
			
		||||
    in_params.splitter_id = AudioCommon::NO_SPLITTER;
 | 
			
		||||
    std::memset(in_params.mix_volume.data(), 0, sizeof(float) * in_params.mix_volume.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerMixInfo::UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
 | 
			
		||||
                                     SplitterContext& splitter_context) {
 | 
			
		||||
    // Mixes are identical
 | 
			
		||||
    if (in_params.dest_mix_id == mix_in.dest_mix_id &&
 | 
			
		||||
        in_params.splitter_id == mix_in.splitter_id &&
 | 
			
		||||
        ((in_params.splitter_id == AudioCommon::NO_SPLITTER) ||
 | 
			
		||||
         !splitter_context.GetInfo(in_params.splitter_id).HasNewConnection())) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    // Remove current edges for mix id
 | 
			
		||||
    edge_matrix.RemoveEdges(in_params.mix_id);
 | 
			
		||||
    if (mix_in.dest_mix_id != AudioCommon::NO_MIX) {
 | 
			
		||||
        // If we have a valid destination mix id, set our edge matrix
 | 
			
		||||
        edge_matrix.Connect(in_params.mix_id, mix_in.dest_mix_id);
 | 
			
		||||
    } else if (mix_in.splitter_id != AudioCommon::NO_SPLITTER) {
 | 
			
		||||
        // Recurse our splitter linked and set our edges
 | 
			
		||||
        auto& splitter_info = splitter_context.GetInfo(mix_in.splitter_id);
 | 
			
		||||
        const auto length = splitter_info.GetLength();
 | 
			
		||||
        for (s32 i = 0; i < length; i++) {
 | 
			
		||||
            const auto* splitter_destination =
 | 
			
		||||
                splitter_context.GetDestinationData(mix_in.splitter_id, i);
 | 
			
		||||
            if (splitter_destination == nullptr) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            if (splitter_destination->ValidMixId()) {
 | 
			
		||||
                edge_matrix.Connect(in_params.mix_id, splitter_destination->GetMixId());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    in_params.dest_mix_id = mix_in.dest_mix_id;
 | 
			
		||||
    in_params.splitter_id = mix_in.splitter_id;
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										107
									
								
								src/audio_core/mix_context.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/audio_core/mix_context.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "audio_core/splitter_context.h"
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
class BehaviorInfo;
 | 
			
		||||
 | 
			
		||||
class MixInfo {
 | 
			
		||||
public:
 | 
			
		||||
    struct DirtyHeader {
 | 
			
		||||
        u32_le magic{};
 | 
			
		||||
        u32_le mixer_count{};
 | 
			
		||||
        INSERT_PADDING_BYTES(0x18);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(DirtyHeader) == 0x20, "MixInfo::DirtyHeader is an invalid size");
 | 
			
		||||
 | 
			
		||||
    struct InParams {
 | 
			
		||||
        float_le volume{};
 | 
			
		||||
        s32_le sample_rate{};
 | 
			
		||||
        s32_le buffer_count{};
 | 
			
		||||
        bool in_use{};
 | 
			
		||||
        INSERT_PADDING_BYTES(3);
 | 
			
		||||
        s32_le mix_id{};
 | 
			
		||||
        s32_le effect_count{};
 | 
			
		||||
        u32_le node_id{};
 | 
			
		||||
        INSERT_PADDING_WORDS(2);
 | 
			
		||||
        std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
 | 
			
		||||
            mix_volume{};
 | 
			
		||||
        s32_le dest_mix_id{};
 | 
			
		||||
        s32_le splitter_id{};
 | 
			
		||||
        INSERT_PADDING_WORDS(1);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(MixInfo::InParams) == 0x930, "MixInfo::InParams is an invalid size");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ServerMixInfo {
 | 
			
		||||
public:
 | 
			
		||||
    struct InParams {
 | 
			
		||||
        float volume{};
 | 
			
		||||
        s32 sample_rate{};
 | 
			
		||||
        s32 buffer_count{};
 | 
			
		||||
        bool in_use{};
 | 
			
		||||
        s32 mix_id{};
 | 
			
		||||
        u32 node_id{};
 | 
			
		||||
        std::array<std::array<float_le, AudioCommon::MAX_MIX_BUFFERS>, AudioCommon::MAX_MIX_BUFFERS>
 | 
			
		||||
            mix_volume{};
 | 
			
		||||
        s32 dest_mix_id{};
 | 
			
		||||
        s32 splitter_id{};
 | 
			
		||||
        s32 buffer_offset{};
 | 
			
		||||
        s32 final_mix_distance{};
 | 
			
		||||
    };
 | 
			
		||||
    ServerMixInfo();
 | 
			
		||||
    ~ServerMixInfo();
 | 
			
		||||
 | 
			
		||||
    const ServerMixInfo::InParams& GetInParams() const;
 | 
			
		||||
    ServerMixInfo::InParams& GetInParams();
 | 
			
		||||
 | 
			
		||||
    bool Update(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
 | 
			
		||||
                BehaviorInfo& behavior_info, SplitterContext& splitter_context);
 | 
			
		||||
    bool HasAnyConnection() const;
 | 
			
		||||
    void Cleanup();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    InParams in_params{};
 | 
			
		||||
    bool UpdateConnection(EdgeMatrix& edge_matrix, const MixInfo::InParams& mix_in,
 | 
			
		||||
                          SplitterContext& splitter_context);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class MixContext {
 | 
			
		||||
public:
 | 
			
		||||
    MixContext();
 | 
			
		||||
    ~MixContext();
 | 
			
		||||
 | 
			
		||||
    void Initialize(const BehaviorInfo& behavior_info, std::size_t mix_count);
 | 
			
		||||
    void SortInfo();
 | 
			
		||||
    bool TsortInfo(SplitterContext& splitter_context);
 | 
			
		||||
 | 
			
		||||
    std::size_t GetCount() const;
 | 
			
		||||
    ServerMixInfo& GetInfo(std::size_t i);
 | 
			
		||||
    const ServerMixInfo& GetInfo(std::size_t i) const;
 | 
			
		||||
    ServerMixInfo& GetSortedInfo(std::size_t i);
 | 
			
		||||
    const ServerMixInfo& GetSortedInfo(std::size_t i) const;
 | 
			
		||||
    ServerMixInfo& GetFinalMixInfo();
 | 
			
		||||
    const ServerMixInfo& GetFinalMixInfo() const;
 | 
			
		||||
    EdgeMatrix& GetEdgeMatrix();
 | 
			
		||||
    const EdgeMatrix& GetEdgeMatrix() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void CalcMixBufferOffset();
 | 
			
		||||
    void UpdateDistancesFromFinalMix();
 | 
			
		||||
 | 
			
		||||
    NodeStates node_states{};
 | 
			
		||||
    EdgeMatrix edge_matrix{};
 | 
			
		||||
    std::size_t info_count{};
 | 
			
		||||
    std::vector<ServerMixInfo> infos{};
 | 
			
		||||
    std::vector<ServerMixInfo*> sorted_info{};
 | 
			
		||||
};
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										31
									
								
								src/audio_core/sink_context.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/audio_core/sink_context.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "audio_core/sink_context.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
SinkContext::SinkContext(std::size_t sink_count) : sink_count(sink_count) {}
 | 
			
		||||
SinkContext::~SinkContext() = default;
 | 
			
		||||
 | 
			
		||||
std::size_t SinkContext::GetCount() const {
 | 
			
		||||
    return sink_count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SinkContext::UpdateMainSink(SinkInfo::InParams& in) {
 | 
			
		||||
    in_use = in.in_use;
 | 
			
		||||
    use_count = in.device.input_count;
 | 
			
		||||
    std::memcpy(buffers.data(), in.device.input.data(), AudioCommon::MAX_CHANNEL_COUNT);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SinkContext::InUse() const {
 | 
			
		||||
    return in_use;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<u8> SinkContext::OutputBuffers() const {
 | 
			
		||||
    std::vector<u8> buffer_ret(use_count);
 | 
			
		||||
    std::memcpy(buffer_ret.data(), buffers.data(), use_count);
 | 
			
		||||
    return buffer_ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										89
									
								
								src/audio_core/sink_context.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/audio_core/sink_context.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,89 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
enum class SinkTypes : u8 {
 | 
			
		||||
    Invalid = 0,
 | 
			
		||||
    Device = 1,
 | 
			
		||||
    Circular = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class SinkSampleFormat : u32_le {
 | 
			
		||||
    None = 0,
 | 
			
		||||
    Pcm8 = 1,
 | 
			
		||||
    Pcm16 = 2,
 | 
			
		||||
    Pcm24 = 3,
 | 
			
		||||
    Pcm32 = 4,
 | 
			
		||||
    PcmFloat = 5,
 | 
			
		||||
    Adpcm = 6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SinkInfo {
 | 
			
		||||
public:
 | 
			
		||||
    struct CircularBufferIn {
 | 
			
		||||
        u64_le address;
 | 
			
		||||
        u32_le size;
 | 
			
		||||
        u32_le input_count;
 | 
			
		||||
        u32_le sample_count;
 | 
			
		||||
        u32_le previous_position;
 | 
			
		||||
        SinkSampleFormat sample_format;
 | 
			
		||||
        std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
 | 
			
		||||
        bool in_use;
 | 
			
		||||
        INSERT_UNION_PADDING_BYTES(5);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(SinkInfo::CircularBufferIn) == 0x28,
 | 
			
		||||
                  "SinkInfo::CircularBufferIn is in invalid size");
 | 
			
		||||
 | 
			
		||||
    struct DeviceIn {
 | 
			
		||||
        std::array<u8, 255> device_name;
 | 
			
		||||
        INSERT_UNION_PADDING_BYTES(1);
 | 
			
		||||
        s32_le input_count;
 | 
			
		||||
        std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> input;
 | 
			
		||||
        INSERT_UNION_PADDING_BYTES(1);
 | 
			
		||||
        bool down_matrix_enabled;
 | 
			
		||||
        std::array<float_le, 4> down_matrix_coef;
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(SinkInfo::DeviceIn) == 0x11c, "SinkInfo::DeviceIn is an invalid size");
 | 
			
		||||
 | 
			
		||||
    struct InParams {
 | 
			
		||||
        SinkTypes type{};
 | 
			
		||||
        bool in_use{};
 | 
			
		||||
        INSERT_PADDING_BYTES(2);
 | 
			
		||||
        u32_le node_id{};
 | 
			
		||||
        INSERT_PADDING_WORDS(6);
 | 
			
		||||
        union {
 | 
			
		||||
            // std::array<u8, 0x120> raw{};
 | 
			
		||||
            SinkInfo::DeviceIn device;
 | 
			
		||||
            SinkInfo::CircularBufferIn circular_buffer;
 | 
			
		||||
        };
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(SinkInfo::InParams) == 0x140, "SinkInfo::InParams are an invalid size!");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SinkContext {
 | 
			
		||||
public:
 | 
			
		||||
    explicit SinkContext(std::size_t sink_count);
 | 
			
		||||
    ~SinkContext();
 | 
			
		||||
 | 
			
		||||
    std::size_t GetCount() const;
 | 
			
		||||
 | 
			
		||||
    void UpdateMainSink(SinkInfo::InParams& in);
 | 
			
		||||
    bool InUse() const;
 | 
			
		||||
    std::vector<u8> OutputBuffers() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool in_use{false};
 | 
			
		||||
    s32 use_count{};
 | 
			
		||||
    std::array<u8, AudioCommon::MAX_CHANNEL_COUNT> buffers{};
 | 
			
		||||
    std::size_t sink_count{};
 | 
			
		||||
};
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										617
									
								
								src/audio_core/splitter_context.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										617
									
								
								src/audio_core/splitter_context.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,617 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "audio_core/behavior_info.h"
 | 
			
		||||
#include "audio_core/splitter_context.h"
 | 
			
		||||
#include "common/alignment.h"
 | 
			
		||||
#include "common/assert.h"
 | 
			
		||||
#include "common/logging/log.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
ServerSplitterDestinationData::ServerSplitterDestinationData(s32 id) : id(id) {}
 | 
			
		||||
ServerSplitterDestinationData::~ServerSplitterDestinationData() = default;
 | 
			
		||||
 | 
			
		||||
void ServerSplitterDestinationData::Update(SplitterInfo::InDestinationParams& header) {
 | 
			
		||||
    // Log error as these are not actually failure states
 | 
			
		||||
    if (header.magic != SplitterMagic::DataHeader) {
 | 
			
		||||
        LOG_ERROR(Audio, "Splitter destination header is invalid!");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Incorrect splitter id
 | 
			
		||||
    if (header.splitter_id != id) {
 | 
			
		||||
        LOG_ERROR(Audio, "Splitter destination ids do not match!");
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    mix_id = header.mix_id;
 | 
			
		||||
    // Copy our mix volumes
 | 
			
		||||
    std::copy(header.mix_volumes.begin(), header.mix_volumes.end(), current_mix_volumes.begin());
 | 
			
		||||
    if (!in_use && header.in_use) {
 | 
			
		||||
        // Update mix volumes
 | 
			
		||||
        std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
 | 
			
		||||
        needs_update = false;
 | 
			
		||||
    }
 | 
			
		||||
    in_use = header.in_use;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() {
 | 
			
		||||
    return next;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerSplitterDestinationData* ServerSplitterDestinationData::GetNextDestination() const {
 | 
			
		||||
    return next;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerSplitterDestinationData::SetNextDestination(ServerSplitterDestinationData* dest) {
 | 
			
		||||
    next = dest;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerSplitterDestinationData::ValidMixId() const {
 | 
			
		||||
    return GetMixId() != AudioCommon::NO_MIX;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
s32 ServerSplitterDestinationData::GetMixId() const {
 | 
			
		||||
    return mix_id;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerSplitterDestinationData::IsConfigured() const {
 | 
			
		||||
    return in_use && ValidMixId();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ServerSplitterDestinationData::GetMixVolume(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
 | 
			
		||||
    return current_mix_volumes.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
 | 
			
		||||
ServerSplitterDestinationData::CurrentMixVolumes() const {
 | 
			
		||||
    return current_mix_volumes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
 | 
			
		||||
ServerSplitterDestinationData::LastMixVolumes() const {
 | 
			
		||||
    return last_mix_volumes;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerSplitterDestinationData::MarkDirty() {
 | 
			
		||||
    needs_update = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerSplitterDestinationData::UpdateInternalState() {
 | 
			
		||||
    if (in_use && needs_update) {
 | 
			
		||||
        std::copy(current_mix_volumes.begin(), current_mix_volumes.end(), last_mix_volumes.begin());
 | 
			
		||||
    }
 | 
			
		||||
    needs_update = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerSplitterInfo::ServerSplitterInfo(s32 id) : id(id) {}
 | 
			
		||||
ServerSplitterInfo::~ServerSplitterInfo() = default;
 | 
			
		||||
 | 
			
		||||
void ServerSplitterInfo::InitializeInfos() {
 | 
			
		||||
    send_length = 0;
 | 
			
		||||
    head = nullptr;
 | 
			
		||||
    new_connection = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerSplitterInfo::ClearNewConnectionFlag() {
 | 
			
		||||
    new_connection = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t ServerSplitterInfo::Update(SplitterInfo::InInfoPrams& header) {
 | 
			
		||||
    if (header.send_id != id) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sample_rate = header.sample_rate;
 | 
			
		||||
    new_connection = true;
 | 
			
		||||
    // We need to update the size here due to the splitter bug being present and providing an
 | 
			
		||||
    // incorrect size. We're suppose to also update the header here but we just ignore and continue
 | 
			
		||||
    return (sizeof(s32_le) * (header.length - 1)) + (sizeof(s32_le) * 3);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerSplitterDestinationData* ServerSplitterInfo::GetHead() {
 | 
			
		||||
    return head;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerSplitterDestinationData* ServerSplitterInfo::GetHead() const {
 | 
			
		||||
    return head;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) {
 | 
			
		||||
    auto current_head = head;
 | 
			
		||||
    for (std::size_t i = 0; i < depth; i++) {
 | 
			
		||||
        if (current_head == nullptr) {
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        }
 | 
			
		||||
        current_head = current_head->GetNextDestination();
 | 
			
		||||
    }
 | 
			
		||||
    return current_head;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerSplitterDestinationData* ServerSplitterInfo::GetData(std::size_t depth) const {
 | 
			
		||||
    auto current_head = head;
 | 
			
		||||
    for (std::size_t i = 0; i < depth; i++) {
 | 
			
		||||
        if (current_head == nullptr) {
 | 
			
		||||
            return nullptr;
 | 
			
		||||
        }
 | 
			
		||||
        current_head = current_head->GetNextDestination();
 | 
			
		||||
    }
 | 
			
		||||
    return current_head;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerSplitterInfo::HasNewConnection() const {
 | 
			
		||||
    return new_connection;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
s32 ServerSplitterInfo::GetLength() const {
 | 
			
		||||
    return send_length;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerSplitterInfo::SetHead(ServerSplitterDestinationData* new_head) {
 | 
			
		||||
    head = new_head;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerSplitterInfo::SetHeadDepth(s32 length) {
 | 
			
		||||
    send_length = length;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SplitterContext::SplitterContext() = default;
 | 
			
		||||
SplitterContext::~SplitterContext() = default;
 | 
			
		||||
 | 
			
		||||
void SplitterContext::Initialize(BehaviorInfo& behavior_info, std::size_t _info_count,
 | 
			
		||||
                                 std::size_t _data_count) {
 | 
			
		||||
    if (!behavior_info.IsSplitterSupported() || _data_count == 0 || _info_count == 0) {
 | 
			
		||||
        Setup(0, 0, false);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    // Only initialize if we're using splitters
 | 
			
		||||
    Setup(_info_count, _data_count, behavior_info.IsSplitterBugFixed());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SplitterContext::Update(const std::vector<u8>& input, std::size_t& input_offset,
 | 
			
		||||
                             std::size_t& bytes_read) {
 | 
			
		||||
    auto UpdateOffsets = [&](std::size_t read) {
 | 
			
		||||
        input_offset += read;
 | 
			
		||||
        bytes_read += read;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (info_count == 0 || data_count == 0) {
 | 
			
		||||
        bytes_read = 0;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
 | 
			
		||||
                                       sizeof(SplitterInfo::InHeader))) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    SplitterInfo::InHeader header{};
 | 
			
		||||
    std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InHeader));
 | 
			
		||||
    UpdateOffsets(sizeof(SplitterInfo::InHeader));
 | 
			
		||||
 | 
			
		||||
    if (header.magic != SplitterMagic::SplitterHeader) {
 | 
			
		||||
        LOG_ERROR(Audio, "Invalid header magic! Expecting {:X} but got {:X}",
 | 
			
		||||
                  SplitterMagic::SplitterHeader, header.magic);
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Clear all connections
 | 
			
		||||
    for (auto& info : infos) {
 | 
			
		||||
        info.ClearNewConnectionFlag();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    UpdateInfo(input, input_offset, bytes_read, header.info_count);
 | 
			
		||||
    UpdateData(input, input_offset, bytes_read, header.data_count);
 | 
			
		||||
    const auto aligned_bytes_read = Common::AlignUp(bytes_read, 16);
 | 
			
		||||
    input_offset += aligned_bytes_read - bytes_read;
 | 
			
		||||
    bytes_read = aligned_bytes_read;
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SplitterContext::UsingSplitter() const {
 | 
			
		||||
    return info_count > 0 && data_count > 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) {
 | 
			
		||||
    ASSERT(i < info_count);
 | 
			
		||||
    return infos.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerSplitterInfo& SplitterContext::GetInfo(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < info_count);
 | 
			
		||||
    return infos.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) {
 | 
			
		||||
    ASSERT(i < data_count);
 | 
			
		||||
    return datas.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerSplitterDestinationData& SplitterContext::GetData(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < data_count);
 | 
			
		||||
    return datas.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
 | 
			
		||||
                                                                   std::size_t data) {
 | 
			
		||||
    ASSERT(info < info_count);
 | 
			
		||||
    auto& cur_info = GetInfo(info);
 | 
			
		||||
    return cur_info.GetData(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerSplitterDestinationData* SplitterContext::GetDestinationData(std::size_t info,
 | 
			
		||||
                                                                         std::size_t data) const {
 | 
			
		||||
    ASSERT(info < info_count);
 | 
			
		||||
    auto& cur_info = GetInfo(info);
 | 
			
		||||
    return cur_info.GetData(data);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SplitterContext::UpdateInternalState() {
 | 
			
		||||
    if (data_count == 0) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (auto& data : datas) {
 | 
			
		||||
        data.UpdateInternalState();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t SplitterContext::GetInfoCount() const {
 | 
			
		||||
    return info_count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t SplitterContext::GetDataCount() const {
 | 
			
		||||
    return data_count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SplitterContext::Setup(std::size_t _info_count, std::size_t _data_count,
 | 
			
		||||
                            bool is_splitter_bug_fixed) {
 | 
			
		||||
 | 
			
		||||
    info_count = _info_count;
 | 
			
		||||
    data_count = _data_count;
 | 
			
		||||
 | 
			
		||||
    for (std::size_t i = 0; i < info_count; i++) {
 | 
			
		||||
        auto& splitter = infos.emplace_back(static_cast<s32>(i));
 | 
			
		||||
        splitter.InitializeInfos();
 | 
			
		||||
    }
 | 
			
		||||
    for (std::size_t i = 0; i < data_count; i++) {
 | 
			
		||||
        datas.emplace_back(static_cast<s32>(i));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bug_fixed = is_splitter_bug_fixed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SplitterContext::UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
 | 
			
		||||
                                 std::size_t& bytes_read, s32 in_splitter_count) {
 | 
			
		||||
    auto UpdateOffsets = [&](std::size_t read) {
 | 
			
		||||
        input_offset += read;
 | 
			
		||||
        bytes_read += read;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    for (s32 i = 0; i < in_splitter_count; i++) {
 | 
			
		||||
        if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
 | 
			
		||||
                                           sizeof(SplitterInfo::InInfoPrams))) {
 | 
			
		||||
            LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        SplitterInfo::InInfoPrams header{};
 | 
			
		||||
        std::memcpy(&header, input.data() + input_offset, sizeof(SplitterInfo::InInfoPrams));
 | 
			
		||||
 | 
			
		||||
        // Logged as warning as these don't actually cause a bailout for some reason
 | 
			
		||||
        if (header.magic != SplitterMagic::InfoHeader) {
 | 
			
		||||
            LOG_ERROR(Audio, "Bad splitter data header");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (header.send_id < 0 || header.send_id > info_count) {
 | 
			
		||||
            LOG_ERROR(Audio, "Bad splitter data id");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        UpdateOffsets(sizeof(SplitterInfo::InInfoPrams));
 | 
			
		||||
        auto& info = GetInfo(header.send_id);
 | 
			
		||||
        if (!RecomposeDestination(info, header, input, input_offset)) {
 | 
			
		||||
            LOG_ERROR(Audio, "Failed to recompose destination for splitter!");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        const std::size_t read = info.Update(header);
 | 
			
		||||
        bytes_read += read;
 | 
			
		||||
        input_offset += read;
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SplitterContext::UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
 | 
			
		||||
                                 std::size_t& bytes_read, s32 in_data_count) {
 | 
			
		||||
    auto UpdateOffsets = [&](std::size_t read) {
 | 
			
		||||
        input_offset += read;
 | 
			
		||||
        bytes_read += read;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    for (s32 i = 0; i < in_data_count; i++) {
 | 
			
		||||
        if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
 | 
			
		||||
                                           sizeof(SplitterInfo::InDestinationParams))) {
 | 
			
		||||
            LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        SplitterInfo::InDestinationParams header{};
 | 
			
		||||
        std::memcpy(&header, input.data() + input_offset,
 | 
			
		||||
                    sizeof(SplitterInfo::InDestinationParams));
 | 
			
		||||
        UpdateOffsets(sizeof(SplitterInfo::InDestinationParams));
 | 
			
		||||
 | 
			
		||||
        // Logged as warning as these don't actually cause a bailout for some reason
 | 
			
		||||
        if (header.magic != SplitterMagic::DataHeader) {
 | 
			
		||||
            LOG_ERROR(Audio, "Bad splitter data header");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (header.splitter_id < 0 || header.splitter_id > data_count) {
 | 
			
		||||
            LOG_ERROR(Audio, "Bad splitter data id");
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        GetData(header.splitter_id).Update(header);
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SplitterContext::RecomposeDestination(ServerSplitterInfo& info,
 | 
			
		||||
                                           SplitterInfo::InInfoPrams& header,
 | 
			
		||||
                                           const std::vector<u8>& input,
 | 
			
		||||
                                           const std::size_t& input_offset) {
 | 
			
		||||
    // Clear our current destinations
 | 
			
		||||
    auto* current_head = info.GetHead();
 | 
			
		||||
    while (current_head != nullptr) {
 | 
			
		||||
        auto next_head = current_head->GetNextDestination();
 | 
			
		||||
        current_head->SetNextDestination(nullptr);
 | 
			
		||||
        current_head = next_head;
 | 
			
		||||
    }
 | 
			
		||||
    info.SetHead(nullptr);
 | 
			
		||||
 | 
			
		||||
    s32 size = header.length;
 | 
			
		||||
    // If the splitter bug is present, calculate fixed size
 | 
			
		||||
    if (!bug_fixed) {
 | 
			
		||||
        if (info_count > 0) {
 | 
			
		||||
            const auto factor = data_count / info_count;
 | 
			
		||||
            size = std::min(header.length, static_cast<s32>(factor));
 | 
			
		||||
        } else {
 | 
			
		||||
            size = 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (size < 1) {
 | 
			
		||||
        LOG_ERROR(Audio, "Invalid splitter info size! size={:X}", size);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto* start_head = &GetData(header.resource_id_base);
 | 
			
		||||
    current_head = start_head;
 | 
			
		||||
    std::vector<s32_le> resource_ids(size - 1);
 | 
			
		||||
    if (!AudioCommon::CanConsumeBuffer(input.size(), input_offset,
 | 
			
		||||
                                       resource_ids.size() * sizeof(s32_le))) {
 | 
			
		||||
        LOG_ERROR(Audio, "Buffer is an invalid size!");
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
    std::memcpy(resource_ids.data(), input.data() + input_offset,
 | 
			
		||||
                resource_ids.size() * sizeof(s32_le));
 | 
			
		||||
 | 
			
		||||
    for (auto resource_id : resource_ids) {
 | 
			
		||||
        auto* head = &GetData(resource_id);
 | 
			
		||||
        current_head->SetNextDestination(head);
 | 
			
		||||
        current_head = head;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    info.SetHead(start_head);
 | 
			
		||||
    info.SetHeadDepth(size);
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NodeStates::NodeStates() = default;
 | 
			
		||||
NodeStates::~NodeStates() = default;
 | 
			
		||||
 | 
			
		||||
void NodeStates::Initialize(std::size_t _node_count) {
 | 
			
		||||
    // Setup our work parameters
 | 
			
		||||
    node_count = _node_count;
 | 
			
		||||
    was_node_found.resize(node_count);
 | 
			
		||||
    was_node_completed.resize(node_count);
 | 
			
		||||
    index_list.resize(node_count);
 | 
			
		||||
    index_stack.Reset(node_count * node_count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NodeStates::Tsort(EdgeMatrix& edge_matrix) {
 | 
			
		||||
    return DepthFirstSearch(edge_matrix);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t NodeStates::GetIndexPos() const {
 | 
			
		||||
    return index_pos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::vector<s32>& NodeStates::GetIndexList() const {
 | 
			
		||||
    return index_list;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NodeStates::PushTsortResult(s32 index) {
 | 
			
		||||
    ASSERT(index < node_count);
 | 
			
		||||
    index_list[index_pos++] = index;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool NodeStates::DepthFirstSearch(EdgeMatrix& edge_matrix) {
 | 
			
		||||
    ResetState();
 | 
			
		||||
    for (std::size_t i = 0; i < node_count; i++) {
 | 
			
		||||
        const auto node_id = static_cast<s32>(i);
 | 
			
		||||
 | 
			
		||||
        // If we don't have a state, send to our index stack for work
 | 
			
		||||
        if (GetState(i) == NodeStates::State::NoState) {
 | 
			
		||||
            index_stack.push(node_id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // While we have work to do in our stack
 | 
			
		||||
        while (index_stack.Count() > 0) {
 | 
			
		||||
            // Get the current node
 | 
			
		||||
            const auto current_stack_index = index_stack.top();
 | 
			
		||||
            // Check if we've seen the node yet
 | 
			
		||||
            const auto index_state = GetState(current_stack_index);
 | 
			
		||||
            if (index_state == NodeStates::State::NoState) {
 | 
			
		||||
                // Mark the node as seen
 | 
			
		||||
                UpdateState(NodeStates::State::InFound, current_stack_index);
 | 
			
		||||
            } else if (index_state == NodeStates::State::InFound) {
 | 
			
		||||
                // We've seen this node before, mark it as completed
 | 
			
		||||
                UpdateState(NodeStates::State::InCompleted, current_stack_index);
 | 
			
		||||
                // Update our index list
 | 
			
		||||
                PushTsortResult(current_stack_index);
 | 
			
		||||
                // Pop the stack
 | 
			
		||||
                index_stack.pop();
 | 
			
		||||
                continue;
 | 
			
		||||
            } else if (index_state == NodeStates::State::InCompleted) {
 | 
			
		||||
                // If our node is already sorted, clear it
 | 
			
		||||
                index_stack.pop();
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const auto node_count = edge_matrix.GetNodeCount();
 | 
			
		||||
            for (s32 j = 0; j < static_cast<s32>(node_count); j++) {
 | 
			
		||||
                // Check if our node is connected to our edge matrix
 | 
			
		||||
                if (!edge_matrix.Connected(current_stack_index, j)) {
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Check if our node exists
 | 
			
		||||
                const auto node_state = GetState(j);
 | 
			
		||||
                if (node_state == NodeStates::State::NoState) {
 | 
			
		||||
                    // Add more work
 | 
			
		||||
                    index_stack.push(j);
 | 
			
		||||
                } else if (node_state == NodeStates::State::InFound) {
 | 
			
		||||
                    UNREACHABLE_MSG("Node start marked as found");
 | 
			
		||||
                    ResetState();
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NodeStates::ResetState() {
 | 
			
		||||
    // Reset to the start of our index stack
 | 
			
		||||
    index_pos = 0;
 | 
			
		||||
    for (std::size_t i = 0; i < node_count; i++) {
 | 
			
		||||
        // Mark all nodes as not found
 | 
			
		||||
        was_node_found[i] = false;
 | 
			
		||||
        // Mark all nodes as uncompleted
 | 
			
		||||
        was_node_completed[i] = false;
 | 
			
		||||
        // Mark all indexes as invalid
 | 
			
		||||
        index_list[i] = -1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NodeStates::UpdateState(NodeStates::State state, std::size_t i) {
 | 
			
		||||
    switch (state) {
 | 
			
		||||
    case NodeStates::State::NoState:
 | 
			
		||||
        was_node_found[i] = false;
 | 
			
		||||
        was_node_completed[i] = false;
 | 
			
		||||
        break;
 | 
			
		||||
    case NodeStates::State::InFound:
 | 
			
		||||
        was_node_found[i] = true;
 | 
			
		||||
        was_node_completed[i] = false;
 | 
			
		||||
        break;
 | 
			
		||||
    case NodeStates::State::InCompleted:
 | 
			
		||||
        was_node_found[i] = false;
 | 
			
		||||
        was_node_completed[i] = true;
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NodeStates::State NodeStates::GetState(std::size_t i) {
 | 
			
		||||
    ASSERT(i < node_count);
 | 
			
		||||
    if (was_node_found[i]) {
 | 
			
		||||
        // If our node exists in our found list
 | 
			
		||||
        return NodeStates::State::InFound;
 | 
			
		||||
    } else if (was_node_completed[i]) {
 | 
			
		||||
        // If node is in the completed list
 | 
			
		||||
        return NodeStates::State::InCompleted;
 | 
			
		||||
    } else {
 | 
			
		||||
        // If in neither
 | 
			
		||||
        return NodeStates::State::NoState;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NodeStates::Stack::Stack() = default;
 | 
			
		||||
NodeStates::Stack::~Stack() = default;
 | 
			
		||||
 | 
			
		||||
void NodeStates::Stack::Reset(std::size_t size) {
 | 
			
		||||
    // Mark our stack as empty
 | 
			
		||||
    stack.resize(size);
 | 
			
		||||
    stack_size = size;
 | 
			
		||||
    stack_pos = 0;
 | 
			
		||||
    std::fill(stack.begin(), stack.end(), 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NodeStates::Stack::push(s32 val) {
 | 
			
		||||
    ASSERT(stack_pos < stack_size);
 | 
			
		||||
    stack[stack_pos++] = val;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t NodeStates::Stack::Count() const {
 | 
			
		||||
    return stack_pos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
s32 NodeStates::Stack::top() const {
 | 
			
		||||
    ASSERT(stack_pos > 0);
 | 
			
		||||
    return stack[stack_pos - 1];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
s32 NodeStates::Stack::pop() {
 | 
			
		||||
    ASSERT(stack_pos > 0);
 | 
			
		||||
    stack_pos--;
 | 
			
		||||
    return stack[stack_pos];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EdgeMatrix::EdgeMatrix() = default;
 | 
			
		||||
EdgeMatrix::~EdgeMatrix() = default;
 | 
			
		||||
 | 
			
		||||
void EdgeMatrix::Initialize(std::size_t _node_count) {
 | 
			
		||||
    node_count = _node_count;
 | 
			
		||||
    edge_matrix.resize(node_count * node_count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool EdgeMatrix::Connected(s32 a, s32 b) {
 | 
			
		||||
    return GetState(a, b);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EdgeMatrix::Connect(s32 a, s32 b) {
 | 
			
		||||
    SetState(a, b, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EdgeMatrix::Disconnect(s32 a, s32 b) {
 | 
			
		||||
    SetState(a, b, false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EdgeMatrix::RemoveEdges(s32 edge) {
 | 
			
		||||
    for (std::size_t i = 0; i < node_count; i++) {
 | 
			
		||||
        SetState(edge, static_cast<s32>(i), false);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t EdgeMatrix::GetNodeCount() const {
 | 
			
		||||
    return node_count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EdgeMatrix::SetState(s32 a, s32 b, bool state) {
 | 
			
		||||
    ASSERT(InRange(a, b));
 | 
			
		||||
    edge_matrix.at(a * node_count + b) = state;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool EdgeMatrix::GetState(s32 a, s32 b) {
 | 
			
		||||
    ASSERT(InRange(a, b));
 | 
			
		||||
    return edge_matrix.at(a * node_count + b);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool EdgeMatrix::InRange(s32 a, s32 b) const {
 | 
			
		||||
    const std::size_t pos = a * node_count + b;
 | 
			
		||||
    return pos < (node_count * node_count);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										221
									
								
								src/audio_core/splitter_context.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								src/audio_core/splitter_context.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,221 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stack>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
#include "common/swap.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
class BehaviorInfo;
 | 
			
		||||
 | 
			
		||||
class EdgeMatrix {
 | 
			
		||||
public:
 | 
			
		||||
    EdgeMatrix();
 | 
			
		||||
    ~EdgeMatrix();
 | 
			
		||||
 | 
			
		||||
    void Initialize(std::size_t _node_count);
 | 
			
		||||
    bool Connected(s32 a, s32 b);
 | 
			
		||||
    void Connect(s32 a, s32 b);
 | 
			
		||||
    void Disconnect(s32 a, s32 b);
 | 
			
		||||
    void RemoveEdges(s32 edge);
 | 
			
		||||
    std::size_t GetNodeCount() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void SetState(s32 a, s32 b, bool state);
 | 
			
		||||
    bool GetState(s32 a, s32 b);
 | 
			
		||||
 | 
			
		||||
    bool InRange(s32 a, s32 b) const;
 | 
			
		||||
    std::vector<bool> edge_matrix{};
 | 
			
		||||
    std::size_t node_count{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class NodeStates {
 | 
			
		||||
public:
 | 
			
		||||
    enum class State {
 | 
			
		||||
        NoState = 0,
 | 
			
		||||
        InFound = 1,
 | 
			
		||||
        InCompleted = 2,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Looks to be a fixed size stack. Placed within the NodeStates class based on symbols
 | 
			
		||||
    class Stack {
 | 
			
		||||
    public:
 | 
			
		||||
        Stack();
 | 
			
		||||
        ~Stack();
 | 
			
		||||
 | 
			
		||||
        void Reset(std::size_t size);
 | 
			
		||||
        void push(s32 val);
 | 
			
		||||
        std::size_t Count() const;
 | 
			
		||||
        s32 top() const;
 | 
			
		||||
        s32 pop();
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        std::vector<s32> stack{};
 | 
			
		||||
        std::size_t stack_size{};
 | 
			
		||||
        std::size_t stack_pos{};
 | 
			
		||||
    };
 | 
			
		||||
    NodeStates();
 | 
			
		||||
    ~NodeStates();
 | 
			
		||||
 | 
			
		||||
    void Initialize(std::size_t _node_count);
 | 
			
		||||
    bool Tsort(EdgeMatrix& edge_matrix);
 | 
			
		||||
    std::size_t GetIndexPos() const;
 | 
			
		||||
    const std::vector<s32>& GetIndexList() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void PushTsortResult(s32 index);
 | 
			
		||||
    bool DepthFirstSearch(EdgeMatrix& edge_matrix);
 | 
			
		||||
    void ResetState();
 | 
			
		||||
    void UpdateState(NodeStates::State state, std::size_t i);
 | 
			
		||||
    NodeStates::State GetState(std::size_t i);
 | 
			
		||||
 | 
			
		||||
    std::size_t node_count{};
 | 
			
		||||
    std::vector<bool> was_node_found{};
 | 
			
		||||
    std::vector<bool> was_node_completed{};
 | 
			
		||||
    std::size_t index_pos{};
 | 
			
		||||
    std::vector<s32> index_list{};
 | 
			
		||||
    NodeStates::Stack index_stack{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class SplitterMagic : u32_le {
 | 
			
		||||
    SplitterHeader = Common::MakeMagic('S', 'N', 'D', 'H'),
 | 
			
		||||
    DataHeader = Common::MakeMagic('S', 'N', 'D', 'D'),
 | 
			
		||||
    InfoHeader = Common::MakeMagic('S', 'N', 'D', 'I'),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SplitterInfo {
 | 
			
		||||
public:
 | 
			
		||||
    struct InHeader {
 | 
			
		||||
        SplitterMagic magic{};
 | 
			
		||||
        s32_le info_count{};
 | 
			
		||||
        s32_le data_count{};
 | 
			
		||||
        INSERT_PADDING_WORDS(5);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(SplitterInfo::InHeader) == 0x20,
 | 
			
		||||
                  "SplitterInfo::InHeader is an invalid size");
 | 
			
		||||
 | 
			
		||||
    struct InInfoPrams {
 | 
			
		||||
        SplitterMagic magic{};
 | 
			
		||||
        s32_le send_id{};
 | 
			
		||||
        s32_le sample_rate{};
 | 
			
		||||
        s32_le length{};
 | 
			
		||||
        s32_le resource_id_base{};
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(SplitterInfo::InInfoPrams) == 0x14,
 | 
			
		||||
                  "SplitterInfo::InInfoPrams is an invalid size");
 | 
			
		||||
 | 
			
		||||
    struct InDestinationParams {
 | 
			
		||||
        SplitterMagic magic{};
 | 
			
		||||
        s32_le splitter_id{};
 | 
			
		||||
        std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volumes{};
 | 
			
		||||
        s32_le mix_id{};
 | 
			
		||||
        bool in_use{};
 | 
			
		||||
        INSERT_PADDING_BYTES(3);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(SplitterInfo::InDestinationParams) == 0x70,
 | 
			
		||||
                  "SplitterInfo::InDestinationParams is an invalid size");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ServerSplitterDestinationData {
 | 
			
		||||
public:
 | 
			
		||||
    explicit ServerSplitterDestinationData(s32 id);
 | 
			
		||||
    ~ServerSplitterDestinationData();
 | 
			
		||||
 | 
			
		||||
    void Update(SplitterInfo::InDestinationParams& header);
 | 
			
		||||
 | 
			
		||||
    ServerSplitterDestinationData* GetNextDestination();
 | 
			
		||||
    const ServerSplitterDestinationData* GetNextDestination() const;
 | 
			
		||||
    void SetNextDestination(ServerSplitterDestinationData* dest);
 | 
			
		||||
    bool ValidMixId() const;
 | 
			
		||||
    s32 GetMixId() const;
 | 
			
		||||
    bool IsConfigured() const;
 | 
			
		||||
    float GetMixVolume(std::size_t i) const;
 | 
			
		||||
    const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& CurrentMixVolumes() const;
 | 
			
		||||
    const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& LastMixVolumes() const;
 | 
			
		||||
    void MarkDirty();
 | 
			
		||||
    void UpdateInternalState();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    bool needs_update{};
 | 
			
		||||
    bool in_use{};
 | 
			
		||||
    s32 id{};
 | 
			
		||||
    s32 mix_id{};
 | 
			
		||||
    std::array<float, AudioCommon::MAX_MIX_BUFFERS> current_mix_volumes{};
 | 
			
		||||
    std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volumes{};
 | 
			
		||||
    ServerSplitterDestinationData* next = nullptr;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ServerSplitterInfo {
 | 
			
		||||
public:
 | 
			
		||||
    explicit ServerSplitterInfo(s32 id);
 | 
			
		||||
    ~ServerSplitterInfo();
 | 
			
		||||
 | 
			
		||||
    void InitializeInfos();
 | 
			
		||||
    void ClearNewConnectionFlag();
 | 
			
		||||
    std::size_t Update(SplitterInfo::InInfoPrams& header);
 | 
			
		||||
 | 
			
		||||
    ServerSplitterDestinationData* GetHead();
 | 
			
		||||
    const ServerSplitterDestinationData* GetHead() const;
 | 
			
		||||
    ServerSplitterDestinationData* GetData(std::size_t depth);
 | 
			
		||||
    const ServerSplitterDestinationData* GetData(std::size_t depth) const;
 | 
			
		||||
 | 
			
		||||
    bool HasNewConnection() const;
 | 
			
		||||
    s32 GetLength() const;
 | 
			
		||||
 | 
			
		||||
    void SetHead(ServerSplitterDestinationData* new_head);
 | 
			
		||||
    void SetHeadDepth(s32 length);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    s32 sample_rate{};
 | 
			
		||||
    s32 id{};
 | 
			
		||||
    s32 send_length{};
 | 
			
		||||
    ServerSplitterDestinationData* head = nullptr;
 | 
			
		||||
    bool new_connection{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class SplitterContext {
 | 
			
		||||
public:
 | 
			
		||||
    SplitterContext();
 | 
			
		||||
    ~SplitterContext();
 | 
			
		||||
 | 
			
		||||
    void Initialize(BehaviorInfo& behavior_info, std::size_t splitter_count,
 | 
			
		||||
                    std::size_t data_count);
 | 
			
		||||
 | 
			
		||||
    bool Update(const std::vector<u8>& input, std::size_t& input_offset, std::size_t& bytes_read);
 | 
			
		||||
    bool UsingSplitter() const;
 | 
			
		||||
 | 
			
		||||
    ServerSplitterInfo& GetInfo(std::size_t i);
 | 
			
		||||
    const ServerSplitterInfo& GetInfo(std::size_t i) const;
 | 
			
		||||
    ServerSplitterDestinationData& GetData(std::size_t i);
 | 
			
		||||
    const ServerSplitterDestinationData& GetData(std::size_t i) const;
 | 
			
		||||
    ServerSplitterDestinationData* GetDestinationData(std::size_t info, std::size_t data);
 | 
			
		||||
    const ServerSplitterDestinationData* GetDestinationData(std::size_t info,
 | 
			
		||||
                                                            std::size_t data) const;
 | 
			
		||||
    void UpdateInternalState();
 | 
			
		||||
 | 
			
		||||
    std::size_t GetInfoCount() const;
 | 
			
		||||
    std::size_t GetDataCount() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    void Setup(std::size_t info_count, std::size_t data_count, bool is_splitter_bug_fixed);
 | 
			
		||||
    bool UpdateInfo(const std::vector<u8>& input, std::size_t& input_offset,
 | 
			
		||||
                    std::size_t& bytes_read, s32 in_splitter_count);
 | 
			
		||||
    bool UpdateData(const std::vector<u8>& input, std::size_t& input_offset,
 | 
			
		||||
                    std::size_t& bytes_read, s32 in_data_count);
 | 
			
		||||
    bool RecomposeDestination(ServerSplitterInfo& info, SplitterInfo::InInfoPrams& header,
 | 
			
		||||
                              const std::vector<u8>& input, const std::size_t& input_offset);
 | 
			
		||||
 | 
			
		||||
    std::vector<ServerSplitterInfo> infos{};
 | 
			
		||||
    std::vector<ServerSplitterDestinationData> datas{};
 | 
			
		||||
 | 
			
		||||
    std::size_t info_count{};
 | 
			
		||||
    std::size_t data_count{};
 | 
			
		||||
    bool bug_fixed{};
 | 
			
		||||
};
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										531
									
								
								src/audio_core/voice_context.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										531
									
								
								src/audio_core/voice_context.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,531 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#include "audio_core/behavior_info.h"
 | 
			
		||||
#include "audio_core/voice_context.h"
 | 
			
		||||
#include "core/memory.h"
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
ServerVoiceChannelResource::ServerVoiceChannelResource(s32 id) : id(id) {}
 | 
			
		||||
ServerVoiceChannelResource::~ServerVoiceChannelResource() = default;
 | 
			
		||||
 | 
			
		||||
bool ServerVoiceChannelResource::InUse() const {
 | 
			
		||||
    return in_use;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ServerVoiceChannelResource::GetCurrentMixVolumeAt(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
 | 
			
		||||
    return mix_volume.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
float ServerVoiceChannelResource::GetLastMixVolumeAt(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < AudioCommon::MAX_MIX_BUFFERS);
 | 
			
		||||
    return last_mix_volume.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerVoiceChannelResource::Update(VoiceChannelResource::InParams& in_params) {
 | 
			
		||||
    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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerVoiceChannelResource::UpdateLastMixVolumes() {
 | 
			
		||||
    std::copy(mix_volume.begin(), mix_volume.end(), last_mix_volume.begin());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
 | 
			
		||||
ServerVoiceChannelResource::GetCurrentMixVolume() const {
 | 
			
		||||
    return mix_volume;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::array<float, AudioCommon::MAX_MIX_BUFFERS>&
 | 
			
		||||
ServerVoiceChannelResource::GetLastMixVolume() const {
 | 
			
		||||
    return last_mix_volume;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerVoiceInfo::ServerVoiceInfo() {
 | 
			
		||||
    Initialize();
 | 
			
		||||
}
 | 
			
		||||
ServerVoiceInfo::~ServerVoiceInfo() = default;
 | 
			
		||||
 | 
			
		||||
void ServerVoiceInfo::Initialize() {
 | 
			
		||||
    in_params.in_use = false;
 | 
			
		||||
    in_params.node_id = 0;
 | 
			
		||||
    in_params.id = 0;
 | 
			
		||||
    in_params.current_playstate = ServerPlayState::Stop;
 | 
			
		||||
    in_params.priority = 255;
 | 
			
		||||
    in_params.sample_rate = 0;
 | 
			
		||||
    in_params.sample_format = SampleFormat::Invalid;
 | 
			
		||||
    in_params.channel_count = 0;
 | 
			
		||||
    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.wave_buffer_count = 0;
 | 
			
		||||
    in_params.wave_bufffer_head = 0;
 | 
			
		||||
    in_params.mix_id = AudioCommon::NO_MIX;
 | 
			
		||||
    in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
 | 
			
		||||
    in_params.additional_params_address = 0;
 | 
			
		||||
    in_params.additional_params_size = 0;
 | 
			
		||||
    in_params.is_new = false;
 | 
			
		||||
    out_params.played_sample_count = 0;
 | 
			
		||||
    out_params.wave_buffer_consumed = 0;
 | 
			
		||||
    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);
 | 
			
		||||
 | 
			
		||||
    for (auto& wave_buffer : in_params.wave_buffer) {
 | 
			
		||||
        wave_buffer.start_sample_offset = 0;
 | 
			
		||||
        wave_buffer.end_sample_offset = 0;
 | 
			
		||||
        wave_buffer.is_looping = false;
 | 
			
		||||
        wave_buffer.end_of_stream = false;
 | 
			
		||||
        wave_buffer.buffer_address = 0;
 | 
			
		||||
        wave_buffer.buffer_size = 0;
 | 
			
		||||
        wave_buffer.context_address = 0;
 | 
			
		||||
        wave_buffer.context_size = 0;
 | 
			
		||||
        wave_buffer.sent_to_dsp = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    stored_samples.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerVoiceInfo::UpdateParameters(const VoiceInfo::InParams& voice_in,
 | 
			
		||||
                                       BehaviorInfo& behavior_info) {
 | 
			
		||||
    in_params.in_use = voice_in.is_in_use;
 | 
			
		||||
    in_params.id = voice_in.id;
 | 
			
		||||
    in_params.node_id = voice_in.node_id;
 | 
			
		||||
    in_params.last_playstate = in_params.current_playstate;
 | 
			
		||||
    switch (voice_in.play_state) {
 | 
			
		||||
    case PlayState::Paused:
 | 
			
		||||
        in_params.current_playstate = ServerPlayState::Paused;
 | 
			
		||||
        break;
 | 
			
		||||
    case PlayState::Stopped:
 | 
			
		||||
        if (in_params.current_playstate != ServerPlayState::Stop) {
 | 
			
		||||
            in_params.current_playstate = ServerPlayState::RequestStop;
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    case PlayState::Started:
 | 
			
		||||
        in_params.current_playstate = ServerPlayState::Play;
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        UNREACHABLE_MSG("Unknown playstate {}", voice_in.play_state);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    in_params.priority = voice_in.priority;
 | 
			
		||||
    in_params.sorting_order = voice_in.sorting_order;
 | 
			
		||||
    in_params.sample_rate = voice_in.sample_rate;
 | 
			
		||||
    in_params.sample_format = voice_in.sample_format;
 | 
			
		||||
    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.wave_buffer_count = voice_in.wave_buffer_count;
 | 
			
		||||
    in_params.wave_bufffer_head = voice_in.wave_buffer_head;
 | 
			
		||||
    if (behavior_info.IsFlushVoiceWaveBuffersSupported()) {
 | 
			
		||||
        in_params.wave_buffer_flush_request_count += voice_in.wave_buffer_flush_request_count;
 | 
			
		||||
    }
 | 
			
		||||
    in_params.mix_id = voice_in.mix_id;
 | 
			
		||||
    if (behavior_info.IsSplitterSupported()) {
 | 
			
		||||
        in_params.splitter_info_id = voice_in.splitter_info_id;
 | 
			
		||||
    } else {
 | 
			
		||||
        in_params.splitter_info_id = AudioCommon::NO_SPLITTER;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::memcpy(in_params.voice_channel_resource_id.data(),
 | 
			
		||||
                voice_in.voice_channel_resource_ids.data(),
 | 
			
		||||
                sizeof(s32) * in_params.voice_channel_resource_id.size());
 | 
			
		||||
 | 
			
		||||
    if (behavior_info.IsVoicePlayedSampleCountResetAtLoopPointSupported()) {
 | 
			
		||||
        in_params.behavior_flags.is_played_samples_reset_at_loop_point =
 | 
			
		||||
            voice_in.behavior_flags.is_played_samples_reset_at_loop_point;
 | 
			
		||||
    } else {
 | 
			
		||||
        in_params.behavior_flags.is_played_samples_reset_at_loop_point.Assign(0);
 | 
			
		||||
    }
 | 
			
		||||
    if (behavior_info.IsVoicePitchAndSrcSkippedSupported()) {
 | 
			
		||||
        in_params.behavior_flags.is_pitch_and_src_skipped =
 | 
			
		||||
            voice_in.behavior_flags.is_pitch_and_src_skipped;
 | 
			
		||||
    } else {
 | 
			
		||||
        in_params.behavior_flags.is_pitch_and_src_skipped.Assign(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (voice_in.is_voice_drop_flag_clear_requested) {
 | 
			
		||||
        in_params.voice_drop_flag = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (in_params.additional_params_address != voice_in.additional_params_address ||
 | 
			
		||||
        in_params.additional_params_size != voice_in.additional_params_size) {
 | 
			
		||||
        in_params.additional_params_address = voice_in.additional_params_address;
 | 
			
		||||
        in_params.additional_params_size = voice_in.additional_params_size;
 | 
			
		||||
        // TODO(ogniK): Reattach buffer, do we actually need to? Maybe just signal to the DSP that
 | 
			
		||||
        // our context is new
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerVoiceInfo::UpdateWaveBuffers(
 | 
			
		||||
    const VoiceInfo::InParams& voice_in,
 | 
			
		||||
    std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
 | 
			
		||||
    BehaviorInfo& behavior_info) {
 | 
			
		||||
    if (voice_in.is_new) {
 | 
			
		||||
        // Initialize our wave buffers
 | 
			
		||||
        for (auto& wave_buffer : in_params.wave_buffer) {
 | 
			
		||||
            wave_buffer.start_sample_offset = 0;
 | 
			
		||||
            wave_buffer.end_sample_offset = 0;
 | 
			
		||||
            wave_buffer.is_looping = false;
 | 
			
		||||
            wave_buffer.end_of_stream = false;
 | 
			
		||||
            wave_buffer.buffer_address = 0;
 | 
			
		||||
            wave_buffer.buffer_size = 0;
 | 
			
		||||
            wave_buffer.context_address = 0;
 | 
			
		||||
            wave_buffer.context_size = 0;
 | 
			
		||||
            wave_buffer.sent_to_dsp = true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Mark all our wave buffers as invalid
 | 
			
		||||
        for (std::size_t channel = 0; channel < static_cast<std::size_t>(in_params.channel_count);
 | 
			
		||||
             channel++) {
 | 
			
		||||
            for (auto& is_valid : voice_states[channel]->is_wave_buffer_valid) {
 | 
			
		||||
                is_valid = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Update our wave buffers
 | 
			
		||||
    for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
 | 
			
		||||
        // Assume that we have at least 1 channel voice state
 | 
			
		||||
        const auto have_valid_wave_buffer = voice_states[0]->is_wave_buffer_valid[i];
 | 
			
		||||
 | 
			
		||||
        UpdateWaveBuffer(in_params.wave_buffer[i], voice_in.wave_buffer[i], in_params.sample_format,
 | 
			
		||||
                         have_valid_wave_buffer, behavior_info);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerVoiceInfo::UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer,
 | 
			
		||||
                                       const WaveBuffer& in_wave_buffer, SampleFormat sample_format,
 | 
			
		||||
                                       bool is_buffer_valid, BehaviorInfo& behavior_info) {
 | 
			
		||||
    if (!is_buffer_valid && out_wavebuffer.sent_to_dsp) {
 | 
			
		||||
        out_wavebuffer.buffer_address = 0;
 | 
			
		||||
        out_wavebuffer.buffer_size = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!in_wave_buffer.sent_to_server || !in_params.buffer_mapped) {
 | 
			
		||||
        // Validate sample offset sizings
 | 
			
		||||
        if (sample_format == SampleFormat::Pcm16) {
 | 
			
		||||
            const auto buffer_size = in_wave_buffer.buffer_size;
 | 
			
		||||
            if (in_wave_buffer.start_sample_offset < 0 || in_wave_buffer.end_sample_offset < 0 ||
 | 
			
		||||
                (buffer_size < (sizeof(s16) * in_wave_buffer.start_sample_offset)) ||
 | 
			
		||||
                (buffer_size < (sizeof(s16) * in_wave_buffer.end_sample_offset))) {
 | 
			
		||||
                // TODO(ogniK): Write error info
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // TODO(ogniK): ADPCM Size error
 | 
			
		||||
 | 
			
		||||
        out_wavebuffer.sent_to_dsp = false;
 | 
			
		||||
        out_wavebuffer.start_sample_offset = in_wave_buffer.start_sample_offset;
 | 
			
		||||
        out_wavebuffer.end_sample_offset = in_wave_buffer.end_sample_offset;
 | 
			
		||||
        out_wavebuffer.is_looping = in_wave_buffer.is_looping;
 | 
			
		||||
        out_wavebuffer.end_of_stream = in_wave_buffer.end_of_stream;
 | 
			
		||||
 | 
			
		||||
        out_wavebuffer.buffer_address = in_wave_buffer.buffer_address;
 | 
			
		||||
        out_wavebuffer.buffer_size = in_wave_buffer.buffer_size;
 | 
			
		||||
        out_wavebuffer.context_address = in_wave_buffer.context_address;
 | 
			
		||||
        out_wavebuffer.context_size = in_wave_buffer.context_size;
 | 
			
		||||
        in_params.buffer_mapped =
 | 
			
		||||
            in_wave_buffer.buffer_address != 0 && in_wave_buffer.buffer_size != 0;
 | 
			
		||||
        // TODO(ogniK): Pool mapper attachment
 | 
			
		||||
        // TODO(ogniK): IsAdpcmLoopContextBugFixed
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerVoiceInfo::WriteOutStatus(
 | 
			
		||||
    VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
 | 
			
		||||
    std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states) {
 | 
			
		||||
    if (voice_in.is_new) {
 | 
			
		||||
        in_params.is_new = true;
 | 
			
		||||
        voice_out.wave_buffer_consumed = 0;
 | 
			
		||||
        voice_out.played_sample_count = 0;
 | 
			
		||||
        voice_out.voice_dropped = false;
 | 
			
		||||
    } else if (!in_params.is_new) {
 | 
			
		||||
        voice_out.wave_buffer_consumed = voice_states[0]->wave_buffer_consumed;
 | 
			
		||||
        voice_out.played_sample_count = voice_states[0]->played_sample_count;
 | 
			
		||||
        voice_out.voice_dropped = in_params.voice_drop_flag;
 | 
			
		||||
    } else {
 | 
			
		||||
        voice_out.wave_buffer_consumed = 0;
 | 
			
		||||
        voice_out.played_sample_count = 0;
 | 
			
		||||
        voice_out.voice_dropped = false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() const {
 | 
			
		||||
    return in_params;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerVoiceInfo::InParams& ServerVoiceInfo::GetInParams() {
 | 
			
		||||
    return in_params;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() const {
 | 
			
		||||
    return out_params;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerVoiceInfo::OutParams& ServerVoiceInfo::GetOutParams() {
 | 
			
		||||
    return out_params;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerVoiceInfo::ShouldSkip() const {
 | 
			
		||||
    // TODO(ogniK): Handle unmapped wave buffers or parameters
 | 
			
		||||
    return !in_params.in_use || (in_params.wave_buffer_count == 0) || in_params.voice_drop_flag;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerVoiceInfo::UpdateForCommandGeneration(VoiceContext& voice_context) {
 | 
			
		||||
    std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT> dsp_voice_states{};
 | 
			
		||||
    if (in_params.is_new) {
 | 
			
		||||
        ResetResources(voice_context);
 | 
			
		||||
        in_params.last_volume = in_params.volume;
 | 
			
		||||
        in_params.is_new = false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const s32 channel_count = in_params.channel_count;
 | 
			
		||||
    for (s32 i = 0; i < channel_count; i++) {
 | 
			
		||||
        const auto channel_resource = in_params.voice_channel_resource_id[i];
 | 
			
		||||
        dsp_voice_states[i] =
 | 
			
		||||
            &voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
 | 
			
		||||
    }
 | 
			
		||||
    return UpdateParametersForCommandGeneration(dsp_voice_states);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerVoiceInfo::ResetResources(VoiceContext& voice_context) {
 | 
			
		||||
    const s32 channel_count = in_params.channel_count;
 | 
			
		||||
    for (s32 i = 0; i < channel_count; i++) {
 | 
			
		||||
        const auto channel_resource = in_params.voice_channel_resource_id[i];
 | 
			
		||||
        auto& dsp_state =
 | 
			
		||||
            voice_context.GetDspSharedState(static_cast<std::size_t>(channel_resource));
 | 
			
		||||
        std::memset(&dsp_state, 0, sizeof(VoiceState));
 | 
			
		||||
        voice_context.GetChannelResource(static_cast<std::size_t>(channel_resource))
 | 
			
		||||
            .UpdateLastMixVolumes();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerVoiceInfo::UpdateParametersForCommandGeneration(
 | 
			
		||||
    std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states) {
 | 
			
		||||
    const s32 channel_count = in_params.channel_count;
 | 
			
		||||
    if (in_params.wave_buffer_flush_request_count > 0) {
 | 
			
		||||
        FlushWaveBuffers(in_params.wave_buffer_flush_request_count, dsp_voice_states,
 | 
			
		||||
                         channel_count);
 | 
			
		||||
        in_params.wave_buffer_flush_request_count = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    switch (in_params.current_playstate) {
 | 
			
		||||
    case ServerPlayState::Play: {
 | 
			
		||||
        for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
 | 
			
		||||
            if (!in_params.wave_buffer[i].sent_to_dsp) {
 | 
			
		||||
                for (s32 channel = 0; channel < channel_count; channel++) {
 | 
			
		||||
                    dsp_voice_states[channel]->is_wave_buffer_valid[i] = true;
 | 
			
		||||
                }
 | 
			
		||||
                in_params.wave_buffer[i].sent_to_dsp = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        in_params.should_depop = false;
 | 
			
		||||
        return HasValidWaveBuffer(dsp_voice_states[0]);
 | 
			
		||||
    }
 | 
			
		||||
    case ServerPlayState::Paused:
 | 
			
		||||
    case ServerPlayState::Stop: {
 | 
			
		||||
        in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
 | 
			
		||||
        return in_params.should_depop;
 | 
			
		||||
    }
 | 
			
		||||
    case ServerPlayState::RequestStop: {
 | 
			
		||||
        for (std::size_t i = 0; i < AudioCommon::MAX_WAVE_BUFFERS; i++) {
 | 
			
		||||
            in_params.wave_buffer[i].sent_to_dsp = true;
 | 
			
		||||
            for (s32 channel = 0; channel < channel_count; channel++) {
 | 
			
		||||
                auto* dsp_state = dsp_voice_states[channel];
 | 
			
		||||
 | 
			
		||||
                if (dsp_state->is_wave_buffer_valid[i]) {
 | 
			
		||||
                    dsp_state->wave_buffer_index =
 | 
			
		||||
                        (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
 | 
			
		||||
                    dsp_state->wave_buffer_consumed++;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                dsp_state->is_wave_buffer_valid[i] = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (s32 channel = 0; channel < channel_count; channel++) {
 | 
			
		||||
            auto* dsp_state = dsp_voice_states[channel];
 | 
			
		||||
            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));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        in_params.current_playstate = ServerPlayState::Stop;
 | 
			
		||||
        in_params.should_depop = in_params.last_playstate == ServerPlayState::Play;
 | 
			
		||||
        return in_params.should_depop;
 | 
			
		||||
    }
 | 
			
		||||
    default:
 | 
			
		||||
        UNREACHABLE_MSG("Invalid playstate {}", in_params.current_playstate);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ServerVoiceInfo::FlushWaveBuffers(
 | 
			
		||||
    u8 flush_count, std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
 | 
			
		||||
    s32 channel_count) {
 | 
			
		||||
    auto wave_head = in_params.wave_bufffer_head;
 | 
			
		||||
 | 
			
		||||
    for (u8 i = 0; i < flush_count; i++) {
 | 
			
		||||
        in_params.wave_buffer[wave_head].sent_to_dsp = true;
 | 
			
		||||
        for (s32 channel = 0; channel < channel_count; channel++) {
 | 
			
		||||
            auto* dsp_state = dsp_voice_states[channel];
 | 
			
		||||
            dsp_state->wave_buffer_consumed++;
 | 
			
		||||
            dsp_state->is_wave_buffer_valid[wave_head] = false;
 | 
			
		||||
            dsp_state->wave_buffer_index =
 | 
			
		||||
                (dsp_state->wave_buffer_index + 1) % AudioCommon::MAX_WAVE_BUFFERS;
 | 
			
		||||
        }
 | 
			
		||||
        wave_head = (wave_head + 1) % AudioCommon::MAX_WAVE_BUFFERS;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ServerVoiceInfo::HasValidWaveBuffer(const VoiceState* state) const {
 | 
			
		||||
    const auto& valid_wb = state->is_wave_buffer_valid;
 | 
			
		||||
    return std::find(valid_wb.begin(), valid_wb.end(), true) != valid_wb.end();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VoiceContext::VoiceContext(std::size_t voice_count) : voice_count(voice_count) {
 | 
			
		||||
    for (std::size_t i = 0; i < voice_count; i++) {
 | 
			
		||||
        voice_channel_resources.emplace_back(static_cast<s32>(i));
 | 
			
		||||
        sorted_voice_info.push_back(&voice_info.emplace_back());
 | 
			
		||||
        voice_states.emplace_back();
 | 
			
		||||
        dsp_voice_states.emplace_back();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VoiceContext::~VoiceContext() {
 | 
			
		||||
    sorted_voice_info.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::size_t VoiceContext::GetVoiceCount() const {
 | 
			
		||||
    return voice_count;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return voice_channel_resources.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerVoiceChannelResource& VoiceContext::GetChannelResource(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return voice_channel_resources.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VoiceState& VoiceContext::GetState(std::size_t i) {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return voice_states.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const VoiceState& VoiceContext::GetState(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return voice_states.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VoiceState& VoiceContext::GetDspSharedState(std::size_t i) {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return dsp_voice_states.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const VoiceState& VoiceContext::GetDspSharedState(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return dsp_voice_states.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return voice_info.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerVoiceInfo& VoiceContext::GetInfo(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return voice_info.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return *sorted_voice_info.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ServerVoiceInfo& VoiceContext::GetSortedInfo(std::size_t i) const {
 | 
			
		||||
    ASSERT(i < voice_count);
 | 
			
		||||
    return *sorted_voice_info.at(i);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
s32 VoiceContext::DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
 | 
			
		||||
                              s32 channel_count, s32 buffer_offset, s32 sample_count,
 | 
			
		||||
                              Core::Memory::Memory& memory) {
 | 
			
		||||
    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) - buffer_offset;
 | 
			
		||||
    const auto start_offset = (wave_buffer->start_sample_offset + buffer_offset) * channel_count;
 | 
			
		||||
    const auto buffer_pos = wave_buffer->buffer_address + start_offset;
 | 
			
		||||
 | 
			
		||||
    s16* buffer_data = reinterpret_cast<s16*>(memory.GetPointer(buffer_pos));
 | 
			
		||||
 | 
			
		||||
    const auto samples_processed = std::min(sample_count, samples_remaining);
 | 
			
		||||
 | 
			
		||||
    // Fast path
 | 
			
		||||
    if (channel_count == 1) {
 | 
			
		||||
        for (std::size_t i = 0; i < samples_processed; i++) {
 | 
			
		||||
            output_buffer[i] = buffer_data[i];
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        for (std::size_t i = 0; i < samples_processed; i++) {
 | 
			
		||||
            output_buffer[i] = buffer_data[i * channel_count + channel];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return samples_processed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void VoiceContext::SortInfo() {
 | 
			
		||||
    for (std::size_t i = 0; i < voice_count; i++) {
 | 
			
		||||
        sorted_voice_info[i] = &voice_info[i];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::sort(sorted_voice_info.begin(), sorted_voice_info.end(),
 | 
			
		||||
              [](const ServerVoiceInfo* lhs, const ServerVoiceInfo* rhs) {
 | 
			
		||||
                  const auto& lhs_in = lhs->GetInParams();
 | 
			
		||||
                  const auto& rhs_in = rhs->GetInParams();
 | 
			
		||||
                  // Sort by priority
 | 
			
		||||
                  if (lhs_in.priority != rhs_in.priority) {
 | 
			
		||||
                      return lhs_in.priority > rhs_in.priority;
 | 
			
		||||
                  } else {
 | 
			
		||||
                      // If the priorities match, sort by sorting order
 | 
			
		||||
                      return lhs_in.sorting_order > rhs_in.sorting_order;
 | 
			
		||||
                  }
 | 
			
		||||
              });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void VoiceContext::UpdateStateByDspShared() {
 | 
			
		||||
    std::memcpy(voice_states.data(), dsp_voice_states.data(),
 | 
			
		||||
                sizeof(VoiceState) * dsp_voice_states.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
							
								
								
									
										291
									
								
								src/audio_core/voice_context.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								src/audio_core/voice_context.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,291 @@
 | 
			
		||||
// Copyright 2020 yuzu Emulator Project
 | 
			
		||||
// Licensed under GPLv2 or any later version
 | 
			
		||||
// Refer to the license.txt file included.
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include "audio_core/algorithm/interpolate.h"
 | 
			
		||||
#include "audio_core/codec.h"
 | 
			
		||||
#include "audio_core/common.h"
 | 
			
		||||
#include "common/bit_field.h"
 | 
			
		||||
#include "common/common_funcs.h"
 | 
			
		||||
#include "common/common_types.h"
 | 
			
		||||
 | 
			
		||||
namespace Core::Memory {
 | 
			
		||||
class Memory;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
namespace AudioCore {
 | 
			
		||||
 | 
			
		||||
class BehaviorInfo;
 | 
			
		||||
class VoiceContext;
 | 
			
		||||
 | 
			
		||||
enum class SampleFormat : u8 {
 | 
			
		||||
    Invalid = 0,
 | 
			
		||||
    Pcm8 = 1,
 | 
			
		||||
    Pcm16 = 2,
 | 
			
		||||
    Pcm24 = 3,
 | 
			
		||||
    Pcm32 = 4,
 | 
			
		||||
    PcmFloat = 5,
 | 
			
		||||
    Adpcm = 6,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class PlayState : u8 {
 | 
			
		||||
    Started = 0,
 | 
			
		||||
    Stopped = 1,
 | 
			
		||||
    Paused = 2,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum class ServerPlayState {
 | 
			
		||||
    Play = 0,
 | 
			
		||||
    Stop = 1,
 | 
			
		||||
    RequestStop = 2,
 | 
			
		||||
    Paused = 3,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct BiquadFilterParameter {
 | 
			
		||||
    bool enabled{};
 | 
			
		||||
    INSERT_PADDING_BYTES(1);
 | 
			
		||||
    std::array<s16, 3> numerator{};
 | 
			
		||||
    std::array<s16, 2> denominator{};
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(BiquadFilterParameter) == 0xc, "BiquadFilterParameter is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct WaveBuffer {
 | 
			
		||||
    u64_le buffer_address{};
 | 
			
		||||
    u64_le buffer_size{};
 | 
			
		||||
    s32_le start_sample_offset{};
 | 
			
		||||
    s32_le end_sample_offset{};
 | 
			
		||||
    u8 is_looping{};
 | 
			
		||||
    u8 end_of_stream{};
 | 
			
		||||
    u8 sent_to_server{};
 | 
			
		||||
    INSERT_PADDING_BYTES(5);
 | 
			
		||||
    u64 context_address{};
 | 
			
		||||
    u64 context_size{};
 | 
			
		||||
    INSERT_PADDING_BYTES(8);
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct ServerWaveBuffer {
 | 
			
		||||
    VAddr buffer_address{};
 | 
			
		||||
    std::size_t buffer_size{};
 | 
			
		||||
    s32 start_sample_offset{};
 | 
			
		||||
    s32 end_sample_offset{};
 | 
			
		||||
    bool is_looping{};
 | 
			
		||||
    bool end_of_stream{};
 | 
			
		||||
    VAddr context_address{};
 | 
			
		||||
    std::size_t context_size{};
 | 
			
		||||
    bool sent_to_dsp{true};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct BehaviorFlags {
 | 
			
		||||
    BitField<0, 1, u16> is_played_samples_reset_at_loop_point;
 | 
			
		||||
    BitField<1, 1, u16> is_pitch_and_src_skipped;
 | 
			
		||||
};
 | 
			
		||||
static_assert(sizeof(BehaviorFlags) == 0x4, "BehaviorFlags is an invalid size");
 | 
			
		||||
 | 
			
		||||
struct VoiceState {
 | 
			
		||||
    s64 played_sample_count{};
 | 
			
		||||
    s32 offset{};
 | 
			
		||||
    s32 wave_buffer_index{};
 | 
			
		||||
    std::array<bool, AudioCommon::MAX_WAVE_BUFFERS> is_wave_buffer_valid{};
 | 
			
		||||
    s32 wave_buffer_consumed{};
 | 
			
		||||
    std::array<s32, AudioCommon::MAX_SAMPLE_HISTORY> sample_history{};
 | 
			
		||||
    s32 fraction{};
 | 
			
		||||
    VAddr context_address{};
 | 
			
		||||
    Codec::ADPCM_Coeff coeff{};
 | 
			
		||||
    Codec::ADPCMState context{};
 | 
			
		||||
    std::array<s64, 2> biquad_filter_state{};
 | 
			
		||||
    std::array<s32, AudioCommon::MAX_MIX_BUFFERS> previous_samples{};
 | 
			
		||||
    u32 external_context_size{};
 | 
			
		||||
    bool is_external_context_used{};
 | 
			
		||||
    bool voice_dropped{};
 | 
			
		||||
    // TODO(ogniK): Hack until ADPCM streaming is implemented
 | 
			
		||||
    std::vector<s16> adpcm_samples{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class VoiceChannelResource {
 | 
			
		||||
public:
 | 
			
		||||
    struct InParams {
 | 
			
		||||
        s32_le id{};
 | 
			
		||||
        std::array<float_le, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
 | 
			
		||||
        bool in_use{};
 | 
			
		||||
        INSERT_PADDING_BYTES(11);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(VoiceChannelResource::InParams) == 0x70, "InParams is an invalid size");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ServerVoiceChannelResource {
 | 
			
		||||
public:
 | 
			
		||||
    explicit ServerVoiceChannelResource(s32 id);
 | 
			
		||||
    ~ServerVoiceChannelResource();
 | 
			
		||||
 | 
			
		||||
    bool InUse() const;
 | 
			
		||||
    float GetCurrentMixVolumeAt(std::size_t i) const;
 | 
			
		||||
    float GetLastMixVolumeAt(std::size_t i) const;
 | 
			
		||||
    void Update(VoiceChannelResource::InParams& in_params);
 | 
			
		||||
    void UpdateLastMixVolumes();
 | 
			
		||||
 | 
			
		||||
    const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetCurrentMixVolume() const;
 | 
			
		||||
    const std::array<float, AudioCommon::MAX_MIX_BUFFERS>& GetLastMixVolume() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    s32 id{};
 | 
			
		||||
    std::array<float, AudioCommon::MAX_MIX_BUFFERS> mix_volume{};
 | 
			
		||||
    std::array<float, AudioCommon::MAX_MIX_BUFFERS> last_mix_volume{};
 | 
			
		||||
    bool in_use{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class VoiceInfo {
 | 
			
		||||
public:
 | 
			
		||||
    struct InParams {
 | 
			
		||||
        s32_le id{};
 | 
			
		||||
        u32_le node_id{};
 | 
			
		||||
        u8 is_new{};
 | 
			
		||||
        u8 is_in_use{};
 | 
			
		||||
        PlayState play_state{};
 | 
			
		||||
        SampleFormat sample_format{};
 | 
			
		||||
        s32_le sample_rate{};
 | 
			
		||||
        s32_le priority{};
 | 
			
		||||
        s32_le sorting_order{};
 | 
			
		||||
        s32_le channel_count{};
 | 
			
		||||
        float_le pitch{};
 | 
			
		||||
        float_le volume{};
 | 
			
		||||
        std::array<BiquadFilterParameter, 2> biquad_filter{};
 | 
			
		||||
        s32_le wave_buffer_count{};
 | 
			
		||||
        s16_le wave_buffer_head{};
 | 
			
		||||
        INSERT_PADDING_BYTES(6);
 | 
			
		||||
        u64_le additional_params_address{};
 | 
			
		||||
        u64_le additional_params_size{};
 | 
			
		||||
        s32_le mix_id{};
 | 
			
		||||
        s32_le splitter_info_id{};
 | 
			
		||||
        std::array<WaveBuffer, 4> wave_buffer{};
 | 
			
		||||
        std::array<u32_le, 6> voice_channel_resource_ids{};
 | 
			
		||||
        // TODO(ogniK): Remaining flags
 | 
			
		||||
        u8 is_voice_drop_flag_clear_requested{};
 | 
			
		||||
        u8 wave_buffer_flush_request_count{};
 | 
			
		||||
        INSERT_PADDING_BYTES(2);
 | 
			
		||||
        BehaviorFlags behavior_flags{};
 | 
			
		||||
        INSERT_PADDING_BYTES(16);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(VoiceInfo::InParams) == 0x170, "InParams is an invalid size");
 | 
			
		||||
 | 
			
		||||
    struct OutParams {
 | 
			
		||||
        u64_le played_sample_count{};
 | 
			
		||||
        u32_le wave_buffer_consumed{};
 | 
			
		||||
        u8 voice_dropped{};
 | 
			
		||||
        INSERT_PADDING_BYTES(3);
 | 
			
		||||
    };
 | 
			
		||||
    static_assert(sizeof(VoiceInfo::OutParams) == 0x10, "OutParams is an invalid size");
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class ServerVoiceInfo {
 | 
			
		||||
public:
 | 
			
		||||
    struct InParams {
 | 
			
		||||
        bool in_use{};
 | 
			
		||||
        bool is_new{};
 | 
			
		||||
        bool should_depop{};
 | 
			
		||||
        SampleFormat sample_format{};
 | 
			
		||||
        s32 sample_rate{};
 | 
			
		||||
        s32 channel_count{};
 | 
			
		||||
        s32 id{};
 | 
			
		||||
        s32 node_id{};
 | 
			
		||||
        s32 mix_id{};
 | 
			
		||||
        ServerPlayState current_playstate{};
 | 
			
		||||
        ServerPlayState last_playstate{};
 | 
			
		||||
        s32 priority{};
 | 
			
		||||
        s32 sorting_order{};
 | 
			
		||||
        float pitch{};
 | 
			
		||||
        float volume{};
 | 
			
		||||
        float last_volume{};
 | 
			
		||||
        std::array<BiquadFilterParameter, AudioCommon::MAX_BIQUAD_FILTERS> biquad_filter{};
 | 
			
		||||
        s32 wave_buffer_count{};
 | 
			
		||||
        s16 wave_bufffer_head{};
 | 
			
		||||
        INSERT_PADDING_BYTES(2);
 | 
			
		||||
        BehaviorFlags behavior_flags{};
 | 
			
		||||
        VAddr additional_params_address{};
 | 
			
		||||
        std::size_t additional_params_size{};
 | 
			
		||||
        std::array<ServerWaveBuffer, AudioCommon::MAX_WAVE_BUFFERS> wave_buffer{};
 | 
			
		||||
        std::array<s32, AudioCommon::MAX_CHANNEL_COUNT> voice_channel_resource_id{};
 | 
			
		||||
        s32 splitter_info_id{};
 | 
			
		||||
        u8 wave_buffer_flush_request_count{};
 | 
			
		||||
        bool voice_drop_flag{};
 | 
			
		||||
        bool buffer_mapped{};
 | 
			
		||||
        std::array<bool, AudioCommon::MAX_BIQUAD_FILTERS> was_biquad_filter_enabled{};
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    struct OutParams {
 | 
			
		||||
        s64 played_sample_count{};
 | 
			
		||||
        s32 wave_buffer_consumed{};
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    ServerVoiceInfo();
 | 
			
		||||
    ~ServerVoiceInfo();
 | 
			
		||||
    void Initialize();
 | 
			
		||||
    void UpdateParameters(const VoiceInfo::InParams& voice_in, BehaviorInfo& behavior_info);
 | 
			
		||||
    void UpdateWaveBuffers(const VoiceInfo::InParams& voice_in,
 | 
			
		||||
                           std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states,
 | 
			
		||||
                           BehaviorInfo& behavior_info);
 | 
			
		||||
    void UpdateWaveBuffer(ServerWaveBuffer& out_wavebuffer, const WaveBuffer& in_wave_buffer,
 | 
			
		||||
                          SampleFormat sample_format, bool is_buffer_valid,
 | 
			
		||||
                          BehaviorInfo& behavior_info);
 | 
			
		||||
    void WriteOutStatus(VoiceInfo::OutParams& voice_out, VoiceInfo::InParams& voice_in,
 | 
			
		||||
                        std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& voice_states);
 | 
			
		||||
 | 
			
		||||
    const InParams& GetInParams() const;
 | 
			
		||||
    InParams& GetInParams();
 | 
			
		||||
 | 
			
		||||
    const OutParams& GetOutParams() const;
 | 
			
		||||
    OutParams& GetOutParams();
 | 
			
		||||
 | 
			
		||||
    bool ShouldSkip() const;
 | 
			
		||||
    bool UpdateForCommandGeneration(VoiceContext& voice_context);
 | 
			
		||||
    void ResetResources(VoiceContext& voice_context);
 | 
			
		||||
    bool UpdateParametersForCommandGeneration(
 | 
			
		||||
        std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states);
 | 
			
		||||
    void FlushWaveBuffers(u8 flush_count,
 | 
			
		||||
                          std::array<VoiceState*, AudioCommon::MAX_CHANNEL_COUNT>& dsp_voice_states,
 | 
			
		||||
                          s32 channel_count);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::vector<s16> stored_samples;
 | 
			
		||||
    InParams in_params{};
 | 
			
		||||
    OutParams out_params{};
 | 
			
		||||
 | 
			
		||||
    bool HasValidWaveBuffer(const VoiceState* state) const;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class VoiceContext {
 | 
			
		||||
public:
 | 
			
		||||
    VoiceContext(std::size_t voice_count);
 | 
			
		||||
    ~VoiceContext();
 | 
			
		||||
 | 
			
		||||
    std::size_t GetVoiceCount() const;
 | 
			
		||||
    ServerVoiceChannelResource& GetChannelResource(std::size_t i);
 | 
			
		||||
    const ServerVoiceChannelResource& GetChannelResource(std::size_t i) const;
 | 
			
		||||
    VoiceState& GetState(std::size_t i);
 | 
			
		||||
    const VoiceState& GetState(std::size_t i) const;
 | 
			
		||||
    VoiceState& GetDspSharedState(std::size_t i);
 | 
			
		||||
    const VoiceState& GetDspSharedState(std::size_t i) const;
 | 
			
		||||
    ServerVoiceInfo& GetInfo(std::size_t i);
 | 
			
		||||
    const ServerVoiceInfo& GetInfo(std::size_t i) const;
 | 
			
		||||
    ServerVoiceInfo& GetSortedInfo(std::size_t i);
 | 
			
		||||
    const ServerVoiceInfo& GetSortedInfo(std::size_t i) const;
 | 
			
		||||
 | 
			
		||||
    s32 DecodePcm16(s32* output_buffer, ServerWaveBuffer* wave_buffer, s32 channel,
 | 
			
		||||
                    s32 channel_count, s32 buffer_offset, s32 sample_count,
 | 
			
		||||
                    Core::Memory::Memory& memory);
 | 
			
		||||
    void SortInfo();
 | 
			
		||||
    void UpdateStateByDspShared();
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    std::size_t voice_count{};
 | 
			
		||||
    std::vector<ServerVoiceChannelResource> voice_channel_resources{};
 | 
			
		||||
    std::vector<VoiceState> voice_states{};
 | 
			
		||||
    std::vector<VoiceState> dsp_voice_states{};
 | 
			
		||||
    std::vector<ServerVoiceInfo> voice_info{};
 | 
			
		||||
    std::vector<ServerVoiceInfo*> sorted_voice_info{};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
} // namespace AudioCore
 | 
			
		||||
@@ -26,7 +26,7 @@ namespace Service::Audio {
 | 
			
		||||
 | 
			
		||||
class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
 | 
			
		||||
public:
 | 
			
		||||
    explicit IAudioRenderer(Core::System& system, AudioCore::AudioRendererParameter audren_params,
 | 
			
		||||
    explicit IAudioRenderer(Core::System& system, AudioCommon::AudioRendererParameter audren_params,
 | 
			
		||||
                            const std::size_t instance_number)
 | 
			
		||||
        : ServiceFramework("IAudioRenderer") {
 | 
			
		||||
        // clang-format off
 | 
			
		||||
@@ -94,14 +94,15 @@ private:
 | 
			
		||||
    void RequestUpdateImpl(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
        LOG_DEBUG(Service_Audio, "(STUBBED) called");
 | 
			
		||||
 | 
			
		||||
        auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer());
 | 
			
		||||
        std::vector<u8> output_params(ctx.GetWriteBufferSize());
 | 
			
		||||
        auto result = renderer->UpdateAudioRenderer(ctx.ReadBuffer(), output_params);
 | 
			
		||||
 | 
			
		||||
        if (result.Succeeded()) {
 | 
			
		||||
            ctx.WriteBuffer(result.Unwrap());
 | 
			
		||||
        if (result.IsSuccess()) {
 | 
			
		||||
            ctx.WriteBuffer(output_params);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        IPC::ResponseBuilder rb{ctx, 2};
 | 
			
		||||
        rb.Push(result.Code());
 | 
			
		||||
        rb.Push(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Start(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
@@ -346,7 +347,7 @@ void AudRenU::OpenAudioRenderer(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    OpenAudioRendererImpl(ctx);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static u64 CalculateNumPerformanceEntries(const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
static u64 CalculateNumPerformanceEntries(const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
    // +1 represents the final mix.
 | 
			
		||||
    return u64{params.effect_count} + params.submix_count + params.sink_count + params.voice_count +
 | 
			
		||||
           1;
 | 
			
		||||
@@ -375,7 +376,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    constexpr u64 upsampler_manager_size = 0x48;
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size that relates to mix buffers.
 | 
			
		||||
    const auto calculate_mix_buffer_sizes = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
    const auto calculate_mix_buffer_sizes = [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
        // As of 8.0.0 this is the maximum on voice channels.
 | 
			
		||||
        constexpr u64 max_voice_channels = 6;
 | 
			
		||||
 | 
			
		||||
@@ -397,7 +398,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Calculates the portion of the size related to the mix data (and the sorting thereof).
 | 
			
		||||
    const auto calculate_mix_info_size = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
    const auto calculate_mix_info_size = [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
        // The size of the mixing info data structure.
 | 
			
		||||
        constexpr u64 mix_info_size = 0x940;
 | 
			
		||||
 | 
			
		||||
@@ -447,7 +448,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size related to voice channel info.
 | 
			
		||||
    const auto calculate_voice_info_size = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
    const auto calculate_voice_info_size = [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
        constexpr u64 voice_info_size = 0x220;
 | 
			
		||||
        constexpr u64 voice_resource_size = 0xD0;
 | 
			
		||||
 | 
			
		||||
@@ -461,7 +462,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size related to memory pools.
 | 
			
		||||
    const auto calculate_memory_pools_size = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
    const auto calculate_memory_pools_size = [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
        const u64 num_memory_pools = sizeof(s32) * (u64{params.effect_count} + params.voice_count);
 | 
			
		||||
        const u64 memory_pool_info_size = 0x20;
 | 
			
		||||
        return Common::AlignUp(num_memory_pools * memory_pool_info_size, info_field_alignment_size);
 | 
			
		||||
@@ -469,7 +470,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size related to the splitter context.
 | 
			
		||||
    const auto calculate_splitter_context_size =
 | 
			
		||||
        [](const AudioCore::AudioRendererParameter& params) -> u64 {
 | 
			
		||||
        [](const AudioCommon::AudioRendererParameter& params) -> u64 {
 | 
			
		||||
        if (!IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
 | 
			
		||||
            return 0;
 | 
			
		||||
        }
 | 
			
		||||
@@ -488,27 +489,29 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size related to the upsampler info.
 | 
			
		||||
    const auto calculate_upsampler_info_size = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
        constexpr u64 upsampler_info_size = 0x280;
 | 
			
		||||
        // Yes, using the buffer size over info alignment size is intentional here.
 | 
			
		||||
        return Common::AlignUp(upsampler_info_size * (u64{params.submix_count} + params.sink_count),
 | 
			
		||||
                               buffer_alignment_size);
 | 
			
		||||
    };
 | 
			
		||||
    const auto calculate_upsampler_info_size =
 | 
			
		||||
        [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
            constexpr u64 upsampler_info_size = 0x280;
 | 
			
		||||
            // Yes, using the buffer size over info alignment size is intentional here.
 | 
			
		||||
            return Common::AlignUp(upsampler_info_size *
 | 
			
		||||
                                       (u64{params.submix_count} + params.sink_count),
 | 
			
		||||
                                   buffer_alignment_size);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size related to effect info.
 | 
			
		||||
    const auto calculate_effect_info_size = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
    const auto calculate_effect_info_size = [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
        constexpr u64 effect_info_size = 0x2B0;
 | 
			
		||||
        return Common::AlignUp(effect_info_size * params.effect_count, info_field_alignment_size);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size related to audio sink info.
 | 
			
		||||
    const auto calculate_sink_info_size = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
    const auto calculate_sink_info_size = [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
        const u64 sink_info_size = 0x170;
 | 
			
		||||
        return Common::AlignUp(sink_info_size * params.sink_count, info_field_alignment_size);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size related to voice state info.
 | 
			
		||||
    const auto calculate_voice_state_size = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
    const auto calculate_voice_state_size = [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
        const u64 voice_state_size = 0x100;
 | 
			
		||||
        const u64 additional_size = buffer_alignment_size - 1;
 | 
			
		||||
        return Common::AlignUp(voice_state_size * params.voice_count + additional_size,
 | 
			
		||||
@@ -516,7 +519,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size related to performance statistics.
 | 
			
		||||
    const auto calculate_perf_size = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
    const auto calculate_perf_size = [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
        // Extra size value appended to the end of the calculation.
 | 
			
		||||
        constexpr u64 appended = 128;
 | 
			
		||||
 | 
			
		||||
@@ -543,79 +546,81 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // Calculates the part of the size that relates to the audio command buffer.
 | 
			
		||||
    const auto calculate_command_buffer_size = [](const AudioCore::AudioRendererParameter& params) {
 | 
			
		||||
        constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
 | 
			
		||||
    const auto calculate_command_buffer_size =
 | 
			
		||||
        [](const AudioCommon::AudioRendererParameter& params) {
 | 
			
		||||
            constexpr u64 alignment = (buffer_alignment_size - 1) * 2;
 | 
			
		||||
 | 
			
		||||
        if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
 | 
			
		||||
            constexpr u64 command_buffer_size = 0x18000;
 | 
			
		||||
            if (!IsFeatureSupported(AudioFeatures::VariadicCommandBuffer, params.revision)) {
 | 
			
		||||
                constexpr u64 command_buffer_size = 0x18000;
 | 
			
		||||
 | 
			
		||||
            return command_buffer_size + alignment;
 | 
			
		||||
        }
 | 
			
		||||
                return command_buffer_size + alignment;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        // When the variadic command buffer is supported, this means
 | 
			
		||||
        // the command generator for the audio renderer can issue commands
 | 
			
		||||
        // that are (as one would expect), variable in size. So what we need to do
 | 
			
		||||
        // is determine the maximum possible size for a few command data structures
 | 
			
		||||
        // then multiply them by the amount of present commands indicated by the given
 | 
			
		||||
        // respective audio parameters.
 | 
			
		||||
            // When the variadic command buffer is supported, this means
 | 
			
		||||
            // the command generator for the audio renderer can issue commands
 | 
			
		||||
            // that are (as one would expect), variable in size. So what we need to do
 | 
			
		||||
            // is determine the maximum possible size for a few command data structures
 | 
			
		||||
            // then multiply them by the amount of present commands indicated by the given
 | 
			
		||||
            // respective audio parameters.
 | 
			
		||||
 | 
			
		||||
        constexpr u64 max_biquad_filters = 2;
 | 
			
		||||
        constexpr u64 max_mix_buffers = 24;
 | 
			
		||||
            constexpr u64 max_biquad_filters = 2;
 | 
			
		||||
            constexpr u64 max_mix_buffers = 24;
 | 
			
		||||
 | 
			
		||||
        constexpr u64 biquad_filter_command_size = 0x2C;
 | 
			
		||||
            constexpr u64 biquad_filter_command_size = 0x2C;
 | 
			
		||||
 | 
			
		||||
        constexpr u64 depop_mix_command_size = 0x24;
 | 
			
		||||
        constexpr u64 depop_setup_command_size = 0x50;
 | 
			
		||||
            constexpr u64 depop_mix_command_size = 0x24;
 | 
			
		||||
            constexpr u64 depop_setup_command_size = 0x50;
 | 
			
		||||
 | 
			
		||||
        constexpr u64 effect_command_max_size = 0x540;
 | 
			
		||||
            constexpr u64 effect_command_max_size = 0x540;
 | 
			
		||||
 | 
			
		||||
        constexpr u64 mix_command_size = 0x1C;
 | 
			
		||||
        constexpr u64 mix_ramp_command_size = 0x24;
 | 
			
		||||
        constexpr u64 mix_ramp_grouped_command_size = 0x13C;
 | 
			
		||||
            constexpr u64 mix_command_size = 0x1C;
 | 
			
		||||
            constexpr u64 mix_ramp_command_size = 0x24;
 | 
			
		||||
            constexpr u64 mix_ramp_grouped_command_size = 0x13C;
 | 
			
		||||
 | 
			
		||||
        constexpr u64 perf_command_size = 0x28;
 | 
			
		||||
            constexpr u64 perf_command_size = 0x28;
 | 
			
		||||
 | 
			
		||||
        constexpr u64 sink_command_size = 0x130;
 | 
			
		||||
            constexpr u64 sink_command_size = 0x130;
 | 
			
		||||
 | 
			
		||||
        constexpr u64 submix_command_max_size =
 | 
			
		||||
            depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
 | 
			
		||||
            constexpr u64 submix_command_max_size =
 | 
			
		||||
                depop_mix_command_size + (mix_command_size * max_mix_buffers) * max_mix_buffers;
 | 
			
		||||
 | 
			
		||||
        constexpr u64 volume_command_size = 0x1C;
 | 
			
		||||
        constexpr u64 volume_ramp_command_size = 0x20;
 | 
			
		||||
            constexpr u64 volume_command_size = 0x1C;
 | 
			
		||||
            constexpr u64 volume_ramp_command_size = 0x20;
 | 
			
		||||
 | 
			
		||||
        constexpr u64 voice_biquad_filter_command_size =
 | 
			
		||||
            biquad_filter_command_size * max_biquad_filters;
 | 
			
		||||
        constexpr u64 voice_data_command_size = 0x9C;
 | 
			
		||||
        const u64 voice_command_max_size =
 | 
			
		||||
            (params.splitter_count * depop_setup_command_size) +
 | 
			
		||||
            (voice_data_command_size + voice_biquad_filter_command_size + volume_ramp_command_size +
 | 
			
		||||
             mix_ramp_grouped_command_size);
 | 
			
		||||
            constexpr u64 voice_biquad_filter_command_size =
 | 
			
		||||
                biquad_filter_command_size * max_biquad_filters;
 | 
			
		||||
            constexpr u64 voice_data_command_size = 0x9C;
 | 
			
		||||
            const u64 voice_command_max_size =
 | 
			
		||||
                (params.splitter_count * depop_setup_command_size) +
 | 
			
		||||
                (voice_data_command_size + voice_biquad_filter_command_size +
 | 
			
		||||
                 volume_ramp_command_size + mix_ramp_grouped_command_size);
 | 
			
		||||
 | 
			
		||||
        // Now calculate the individual elements that comprise the size and add them together.
 | 
			
		||||
        const u64 effect_commands_size = params.effect_count * effect_command_max_size;
 | 
			
		||||
            // Now calculate the individual elements that comprise the size and add them together.
 | 
			
		||||
            const u64 effect_commands_size = params.effect_count * effect_command_max_size;
 | 
			
		||||
 | 
			
		||||
        const u64 final_mix_commands_size =
 | 
			
		||||
            depop_mix_command_size + volume_command_size * max_mix_buffers;
 | 
			
		||||
            const u64 final_mix_commands_size =
 | 
			
		||||
                depop_mix_command_size + volume_command_size * max_mix_buffers;
 | 
			
		||||
 | 
			
		||||
        const u64 perf_commands_size =
 | 
			
		||||
            perf_command_size * (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
 | 
			
		||||
            const u64 perf_commands_size =
 | 
			
		||||
                perf_command_size *
 | 
			
		||||
                (CalculateNumPerformanceEntries(params) + max_perf_detail_entries);
 | 
			
		||||
 | 
			
		||||
        const u64 sink_commands_size = params.sink_count * sink_command_size;
 | 
			
		||||
            const u64 sink_commands_size = params.sink_count * sink_command_size;
 | 
			
		||||
 | 
			
		||||
        const u64 splitter_commands_size =
 | 
			
		||||
            params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
 | 
			
		||||
            const u64 splitter_commands_size =
 | 
			
		||||
                params.num_splitter_send_channels * max_mix_buffers * mix_ramp_command_size;
 | 
			
		||||
 | 
			
		||||
        const u64 submix_commands_size = params.submix_count * submix_command_max_size;
 | 
			
		||||
            const u64 submix_commands_size = params.submix_count * submix_command_max_size;
 | 
			
		||||
 | 
			
		||||
        const u64 voice_commands_size = params.voice_count * voice_command_max_size;
 | 
			
		||||
            const u64 voice_commands_size = params.voice_count * voice_command_max_size;
 | 
			
		||||
 | 
			
		||||
        return effect_commands_size + final_mix_commands_size + perf_commands_size +
 | 
			
		||||
               sink_commands_size + splitter_commands_size + submix_commands_size +
 | 
			
		||||
               voice_commands_size + alignment;
 | 
			
		||||
    };
 | 
			
		||||
            return effect_commands_size + final_mix_commands_size + perf_commands_size +
 | 
			
		||||
                   sink_commands_size + splitter_commands_size + submix_commands_size +
 | 
			
		||||
                   voice_commands_size + alignment;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    IPC::RequestParser rp{ctx};
 | 
			
		||||
    const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
 | 
			
		||||
    const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
 | 
			
		||||
 | 
			
		||||
    u64 size = 0;
 | 
			
		||||
    size += calculate_mix_buffer_sizes(params);
 | 
			
		||||
@@ -681,7 +686,7 @@ void AudRenU::GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& c
 | 
			
		||||
 | 
			
		||||
void AudRenU::OpenAudioRendererImpl(Kernel::HLERequestContext& ctx) {
 | 
			
		||||
    IPC::RequestParser rp{ctx};
 | 
			
		||||
    const auto params = rp.PopRaw<AudioCore::AudioRendererParameter>();
 | 
			
		||||
    const auto params = rp.PopRaw<AudioCommon::AudioRendererParameter>();
 | 
			
		||||
    IPC::ResponseBuilder rb{ctx, 2, 0, 1};
 | 
			
		||||
 | 
			
		||||
    rb.Push(RESULT_SUCCESS);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user